[wp-trac] [WordPress Trac] #28801: Walker::walk makes an incorrect assumption if $top_level_elements is empty.

WordPress Trac noreply at wordpress.org
Wed Jul 9 16:02:57 UTC 2014


#28801: Walker::walk makes an incorrect assumption if $top_level_elements is empty.
----------------------------+-----------------------------
 Reporter:  rob-castlegate  |      Owner:
     Type:  defect (bug)    |     Status:  new
 Priority:  normal          |  Milestone:  Awaiting Review
Component:  General         |    Version:  trunk
 Severity:  normal          |   Keywords:
  Focuses:                  |
----------------------------+-----------------------------
 A colleague of mine was generating a sidebar sub-navigation for one of his
 projects.  The subnavigation contained second-level and third-level
 navigation elements.

 The problem my colleague was having was that occasionally third-level
 elements would not be nested underneath their parent element (also in the
 list of elements) on some pages.

 My colleague was calling wp_list_pages with an array of page IDs that he
 wanted to render in the sub-navigation, wp_list_pages then turned the list
 of page IDs into a list of Page objects, and it sorted the page objects by
 their 'menu_order' attribute; the third-level navigational elements all
 had their 'menu_order' set to 0, whereas the second-level navigational
 elements all had 'menu_order' set to something more than 0 - causing the
 third-level elements to be the first elements in the list.

 wp_list_pages later made a call to Walker::walk, passing along that list
 of pages.  Here is a relevant code snippet from Walker::walk:


 {{{
 /*
                  * When none of the elements is top level.
                  * Assume the first one must be root of the sub elements.
                  */
                 if ( empty($top_level_elements) ) {

                         $first = array_slice( $elements, 0, 1 );
                         $root = $first[0];

                         $top_level_elements = array();
                         $children_elements  = array();
                         foreach ( $elements as $e) {
                                 if ( $root->$parent_field ==
 $e->$parent_field )
                                         $top_level_elements[] = $e;
                                 else
                                         $children_elements[
 $e->$parent_field ][] = $e;
                         }
                 }
 }}}

 '''The bug is this code's assumption that the first item in $elements is a
 suitable root-element for the entire list''' (sentence emboldened for
 anybody not wanting to read the wall of text).  wp_list_pages ordered our
 list by 'menu_order' which put our 3rd-level elements at the top of the
 list - causing a 3rd-level element to be treated as the navigation's root.

 I wrote up a quick fix for this (I'm not sure if it's the best fix, I'm
 not overly experienced in Wordpress), and for our project we'll use
 wp_list_pages with a custom walker class that implements my fix.

 Here is the patch of my fix:


 {{{
 Index: public_html/wp-includes/class-wp-walker.php
 IDEA additional info:
 Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
 <+>UTF-8
 ===================================================================
 --- public_html/wp-includes/class-wp-walker.php (date 1404915904000)
 +++ public_html/wp-includes/class-wp-walker.php (revision )
 @@ -217,12 +217,34 @@

                 /*
                  * When none of the elements is top level.
 -                * Assume the first one must be root of the sub elements.
 +                * ~~Assume the first one must be root of the sub
 elements.~~ Disregard - RJ CGIT 2014-07-09
 +                *
 +                * ----------
 +                *
 +                * Modified by Rob Jackson, Castlegate IT; 2014-07-09:
 +                *  Do not assume the first element is root, instead loop
 through the elements
 +                *  until we find one whose parent is _not_ in the list of
 elements.  If that fails,
 +                *  just fall back to the default behaviour of using the
 first element.
                  */
                 if ( empty($top_level_elements) ) {
 +            $root = false;
 +            $element_ids = array_map(function($element){ return
 $element->ID; }, $elements);

 +            foreach($elements as $element)
 +            {
 +                if (!in_array($element->post_parent, $element_ids))
 +                {
 +                    $root = $element;
 +                    break;
 +                }
 +            }
 +            unset($element);
 +
 +            if ($root === false)
 +            {
 -                       $first = array_slice( $elements, 0, 1 );
 -                       $root = $first[0];
 +                $first = array_slice( $elements, 0, 1 );
 +                $root = $first[0];
 +            }

                         $top_level_elements = array();
                         $children_elements  = array();

 }}}

 Kind regards,
 Rob

--
Ticket URL: <https://core.trac.wordpress.org/ticket/28801>
WordPress Trac <https://core.trac.wordpress.org/>
WordPress publishing platform


More information about the wp-trac mailing list