admin管理员组

文章数量:1122846

I use wp_list_pages() to output all the child pages of the currently viewed page. This works well enough but I wanted to add the comment feed URL with an icon next to each child page.

My solution was to brute force it with a regular expression and a cheeky str_replace hack. It works. But it feels wrong that I was not able to identify a more WordPress way to do what I did. Like say, use some sort of basic template for the output.

Here's my current solution.

$string = '';
$meta_id = get_post_meta($post->ID,'meta',true);
$parent_meta_id = get_post_meta($post->post_parent,'meta',true);
if ( is_page() ){
    $childpages = wp_list_pages( 'sort_column=post_name&title_li=&child_of=' . $post->ID . '&echo=0&depth=1&exclude='.$meta_id.','.$parent_meta_id );
}
if ( $childpages ) {
    $rows = explode('</li>',$childpages);
    $string = '<div class="subtopics container"><ul class="om_page_list list-unstyled card-columns" style="column-count: 3;">';
    foreach($rows as $row){
        $match = array();
        if(''!=trim($row)){
            preg_match_all('#\bhttps?://[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $row, $match);
            if(0<count($match)){
                $feedlink = '<a href="'.$match[0][0].'feed/"><img src=".svg" style="width:1em;" title="Topic by RSS Feed"/></a>' ;
                $row = str_replace('<a href', $feedlink.' <a href', $row);
            }
            $string .= $row;   
        }
    }
    $string.='</ul></div>';
} 

You can see that I cludged the preg hack into the middle to add the extra link before the link. I noticed that there is a parameter link_before but I could not figure out how to make that also carry the URL of the page (the feed URL is that plus "feed/"). I could have used it as a str_replace target but the string "<a href" does that well enough if you remember to put it back after.

Is there a WordPress native way to get the same result?

I use wp_list_pages() to output all the child pages of the currently viewed page. This works well enough but I wanted to add the comment feed URL with an icon next to each child page.

My solution was to brute force it with a regular expression and a cheeky str_replace hack. It works. But it feels wrong that I was not able to identify a more WordPress way to do what I did. Like say, use some sort of basic template for the output.

Here's my current solution.

$string = '';
$meta_id = get_post_meta($post->ID,'meta',true);
$parent_meta_id = get_post_meta($post->post_parent,'meta',true);
if ( is_page() ){
    $childpages = wp_list_pages( 'sort_column=post_name&title_li=&child_of=' . $post->ID . '&echo=0&depth=1&exclude='.$meta_id.','.$parent_meta_id );
}
if ( $childpages ) {
    $rows = explode('</li>',$childpages);
    $string = '<div class="subtopics container"><ul class="om_page_list list-unstyled card-columns" style="column-count: 3;">';
    foreach($rows as $row){
        $match = array();
        if(''!=trim($row)){
            preg_match_all('#\bhttps?://[^,\s()<>]+(?:\([\w\d]+\)|([^,[:punct:]\s]|/))#', $row, $match);
            if(0<count($match)){
                $feedlink = '<a href="'.$match[0][0].'feed/"><img src="https://openmentions.com/wp-content/themes/openmention/Feed-icon.svg" style="width:1em;" title="Topic by RSS Feed"/></a>' ;
                $row = str_replace('<a href', $feedlink.' <a href', $row);
            }
            $string .= $row;   
        }
    }
    $string.='</ul></div>';
} 

You can see that I cludged the preg hack into the middle to add the extra link before the link. I noticed that there is a parameter link_before but I could not figure out how to make that also carry the URL of the page (the feed URL is that plus "feed/"). I could have used it as a str_replace target but the string "<a href" does that well enough if you remember to put it back after.

Is there a WordPress native way to get the same result?

Share Improve this question edited Jan 9, 2024 at 21:55 fuxia 107k38 gold badges255 silver badges459 bronze badges asked Jan 9, 2024 at 21:24 Matthew Brown aka Lord MattMatthew Brown aka Lord Matt 1,0683 gold badges13 silver badges34 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

wp_list_pages() forwards the found pages array to walk_page_tree(), which uses Walker_Page to render the list, unless a custom walker instance is passed to wp_list_pages() with array( 'walker' => $instance ) argument.

Walker_Page is just an extension of WordPress' Walker class, which is used "for displaying various tree-like structures". The class has overwritable public methods for rendering the tree levels and elements - their starts and ends.

This means that we can create our own extension class from Walker_Page and overwrite and modify the start_el() method to match our needs.

Here is an example where I first use the parent's method to render the list item to a temporary helper variable, then do a simple str_replace() to add the feed link before the page link, and finally append the complete element html to the actual output, which is passed to the method by reference.

class WPSE_421764_Custom_Page_Walker extends Walker_Page
{
    public function start_el( &$output, $data_object, $depth = 0, $args = array(), $current_object_id = 0 )
    {
        $element = '';
        parent::start_el( $element, $data_object, $depth, $args, $current_object_id );

        $page = $data_object;

        $feed_link = sprintf(
            '><a href="%1$s/feed">
                <img src="https://openmentions.com/wp-content/themes/openmention/Feed-icon.svg" style="width:1em;" title="Topic by RSS Feed"/>
            </a><a',
            esc_attr( get_permalink( $page->ID ) )
        );

        $output .= str_replace( '><a', $feed_link, $element );
    }
}

Walker_Page uses <li%s><a%s>%s%s%s</a> format with sprintf() to create the element start and this is where ><a comes from for the above str_replace().

I use string replacement here to avoid rewriting the whole method. If you want to customize the method from top to bottom, then I'd recommend using the Walker_Page::start_el() source code as a reference and for inspiration.

And to make things a little cleaner, you can create a helper function to wrap the list rendering into a nice package.

function wpse_421764_child_pages_list( int $post_id, int $parent_id = 0 ): string {
    $meta_id = get_post_meta( $post_id, 'meta', true );
    $parent_meta_id = get_post_meta( $parent_id, 'meta', true );

    $childpages = is_page( $post_id ) ? wp_list_pages( array(
        'sort_column' => 'post_name',
        'title_li' => '',
        'child_of' => $post_id,
        'echo' => 0,
        'depth' => 1,
        'walker' => new WPSE_421764_Custom_Page_Walker(),
        'exclude' => implode( ',', array_filter( array( $meta_id, $parent_meta_id ) ) ),
    ) ) : '';

    return $childpages ? sprintf(
            '<div class="subtopics container">
                <ul class="om_page_list list-unstyled card-columns" style="column-count: 3;">%s</ul>
            </div>',
            $childpages
        ) : '';
}

Usage,

echo wpse_421764_child_pages_list( $post->ID, $post->post_parent );

本文标签: