html sitemap via recursive function

I'm trying to render my page structure as an ordered list (ol) showing the hierarchy using nested ordered lists. So it should look something like

ol
  li
    About
    ol
      li
        Leadership
        ol
          liCEO/li
          liCOO/li
        /ol
      /li
    /ol
  /li
  liServices/li
  liProducts/li
/ol

or

  1. About
    1. Leadership
      1. CEO
      2. COO
  2. Services
  3. Products

I created a function to get 1 level of pages which I call recursively for any pages with child pages.

function aiv_get_sibling_pages($cur_page = null) {
    $front_page_id = get_option('page_on_front');
    $next_page = $cur_page ? $cur_page : get_post($front_page_id);

    $out = '';

    $pages_args = array(
        'exclude' = '', /* ID of pages to be excluded, separated by comma */
        'post_type' = 'page',
        'post_status' = 'publish',
        'parent' = $next_page - post_parent
    );
    $pages = get_pages($pages_args);

    $out .= 'ol';
    foreach($pages as $page) {
        $next_page = $page;

        $out .=  'li';
        $out .= $page - post_title;
        $child_pages = get_pages('child_of=' . $page-ID);
        if(count($child_pages)  0) {
            $out .= aiv_get_sibling_pages($next_page);
        }
        $out .= '/li';   
    }
    $out .= '/ol';

    return $out;
}

Everything works as expected until the recursive part: aiv_get_sibling_pages($next_page);

This produces the error:

PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 4096 bytes) in /home/anupadmin/example.com/wp-includes/post.php on line 5781

I'm not entirely sure what that means, but I'm guessing if I'm running out of memory that I'm doing something inefficiently. Can anyone point me towards a better way to do this or shed some light on why Im getting the error?

UPDATE

HEre's a cleaned up, working version implementing solution from accepted answer below:

function aiv_get_sibling_pages($parent_id = 0) {
    $out = '';
    $pages_args = array(
        'exclude' = '',
        'post_type' = 'page',
        'post_status' = 'publish',
        'parent' = $parent_id
    );
    $pages = get_pages($pages_args);    
    if($pages) {
        $extra_className = $parent_id  0 ? ' child-list' : '';
        $out .= 'ol class=sitemap__list--ordered ' . $extra_className . '';
        foreach($pages as $page) {
            $out .=  'li class=sitemap__item';
            $out .= $page - post_title;
            $out .= aiv_get_sibling_pages($page - ID);
            $out .= '/li';   
        }
        $out .= '/ol';
    }
    return $out;
}

Topic recursive sitemap Wordpress

Category Web


The "memory exhausted" error in question happened because of this part in your function: 'parent' => $next_page -> post_parent, which should actually be 'parent' => $next_page -> ID (or I'd omit the unnecessary spaces, i.e. I'd use 'parent' => $next_page->ID).

And the error can be illustrated like so:

$cur_page = get_post( 123 ); // assume the post_parent is 45
aiv_get_sibling_pages( $cur_page );
// 1. get_pages() is called with 'parent' set to 45.

// 2. Assume get_pages() returned page 123 as the 1st item.
// 3. Your `foreach` calls aiv_get_sibling_pages( <the post object for page 123> )

// 4. .. and then the loop restarts at step 1 and never ends..
// eventually, PHP exits with the "memory exhausted" error :(

So as I said earlier, use 'parent' => $next_page->ID and the memory error would be gone.

But I would also make these changes:

  1. Before the $out .= '<ol>';, add this:

    // Don't add the OL if there are no more pages.
    if ( empty( $pages ) ) {
        return '';
    }
    
  2. The get_pages() in the foreach is not necessary. So,

    // Replace this:
    $child_pages = get_pages('child_of=' . $page->ID);
    if(count($child_pages) > 0) {
        $out .= aiv_get_sibling_pages($next_page);
    }
    
    // with just:
    $out .= aiv_get_sibling_pages( $next_page );
    

And actually, you could use wp_list_pages()..

Something like this:

$list = wp_list_pages( array(
    'child_of' => 123, // just change 123 to the correct ID
    'title_li' => '',
    'echo'     => false,
) );

echo '<ol>' . str_replace(
    [ '<ul>', '<ul ', '</ul>' ],
    [ '<ol>', '<ol ', '</ol>' ],
    $list
) . '</ol>';

But yes, if you want full control over the HTML, then your custom function would be easier in that case.

About

Geeks Mental is a community that publishes articles and tutorials about Web, Android, Data Science, new techniques and Linux security.