Wordpress 4.6
[autoinstalls/wordpress.git] / wp-includes / class-wp-walker.php
1 <?php
2 /**
3  * A class for displaying various tree-like structures.
4  *
5  * Extend the Walker class to use it, see examples below. Child classes
6  * do not need to implement all of the abstract methods in the class. The child
7  * only needs to implement the methods that are needed.
8  *
9  * @since 2.1.0
10  *
11  * @package WordPress
12  * @abstract
13  */
14 class Walker {
15         /**
16          * What the class handles.
17          *
18          * @since 2.1.0
19          * @access public
20          * @var string
21          */
22         public $tree_type;
23
24         /**
25          * DB fields to use.
26          *
27          * @since 2.1.0
28          * @var array
29          */
30         public $db_fields;
31
32         /**
33          * Max number of pages walked by the paged walker
34          *
35          * @since 2.7.0
36          * @var int
37          */
38         public $max_pages = 1;
39
40         /**
41          * Whether the current element has children or not.
42          *
43          * To be used in start_el().
44          *
45          * @since 4.0.0
46          * @var bool
47          */
48         public $has_children;
49
50         /**
51          * Starts the list before the elements are added.
52          *
53          * The $args parameter holds additional values that may be used with the child
54          * class methods. This method is called at the start of the output list.
55          *
56          * @since 2.1.0
57          * @abstract
58          *
59          * @param string $output Passed by reference. Used to append additional content.
60          * @param int    $depth  Depth of the item.
61          * @param array  $args   An array of additional arguments.
62          */
63         public function start_lvl( &$output, $depth = 0, $args = array() ) {}
64
65         /**
66          * Ends the list of after the elements are added.
67          *
68          * The $args parameter holds additional values that may be used with the child
69          * class methods. This method finishes the list at the end of output of the elements.
70          *
71          * @since 2.1.0
72          * @abstract
73          *
74          * @param string $output Passed by reference. Used to append additional content.
75          * @param int    $depth  Depth of the item.
76          * @param array  $args   An array of additional arguments.
77          */
78         public function end_lvl( &$output, $depth = 0, $args = array() ) {}
79
80         /**
81          * Start the element output.
82          *
83          * The $args parameter holds additional values that may be used with the child
84          * class methods. Includes the element output also.
85          *
86          * @since 2.1.0
87          * @abstract
88          *
89          * @param string $output            Passed by reference. Used to append additional content.
90          * @param object $object            The data object.
91          * @param int    $depth             Depth of the item.
92          * @param array  $args              An array of additional arguments.
93          * @param int    $current_object_id ID of the current item.
94          */
95         public function start_el( &$output, $object, $depth = 0, $args = array(), $current_object_id = 0 ) {}
96
97         /**
98          * Ends the element output, if needed.
99          *
100          * The $args parameter holds additional values that may be used with the child class methods.
101          *
102          * @since 2.1.0
103          * @abstract
104          *
105          * @param string $output Passed by reference. Used to append additional content.
106          * @param object $object The data object.
107          * @param int    $depth  Depth of the item.
108          * @param array  $args   An array of additional arguments.
109          */
110         public function end_el( &$output, $object, $depth = 0, $args = array() ) {}
111
112         /**
113          * Traverse elements to create list from elements.
114          *
115          * Display one element if the element doesn't have any children otherwise,
116          * display the element and its children. Will only traverse up to the max
117          * depth and no ignore elements under that depth. It is possible to set the
118          * max depth to include all depths, see walk() method.
119          *
120          * This method should not be called directly, use the walk() method instead.
121          *
122          * @since 2.5.0
123          *
124          * @param object $element           Data object.
125          * @param array  $children_elements List of elements to continue traversing.
126          * @param int    $max_depth         Max depth to traverse.
127          * @param int    $depth             Depth of current element.
128          * @param array  $args              An array of arguments.
129          * @param string $output            Passed by reference. Used to append additional content.
130          */
131         public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
132                 if ( ! $element ) {
133                         return;
134                 }
135
136                 $id_field = $this->db_fields['id'];
137                 $id       = $element->$id_field;
138
139                 //display this element
140                 $this->has_children = ! empty( $children_elements[ $id ] );
141                 if ( isset( $args[0] ) && is_array( $args[0] ) ) {
142                         $args[0]['has_children'] = $this->has_children; // Back-compat.
143                 }
144
145                 $cb_args = array_merge( array(&$output, $element, $depth), $args);
146                 call_user_func_array(array($this, 'start_el'), $cb_args);
147
148                 // descend only when the depth is right and there are childrens for this element
149                 if ( ($max_depth == 0 || $max_depth > $depth+1 ) && isset( $children_elements[$id]) ) {
150
151                         foreach ( $children_elements[ $id ] as $child ){
152
153                                 if ( !isset($newlevel) ) {
154                                         $newlevel = true;
155                                         //start the child delimiter
156                                         $cb_args = array_merge( array(&$output, $depth), $args);
157                                         call_user_func_array(array($this, 'start_lvl'), $cb_args);
158                                 }
159                                 $this->display_element( $child, $children_elements, $max_depth, $depth + 1, $args, $output );
160                         }
161                         unset( $children_elements[ $id ] );
162                 }
163
164                 if ( isset($newlevel) && $newlevel ){
165                         //end the child delimiter
166                         $cb_args = array_merge( array(&$output, $depth), $args);
167                         call_user_func_array(array($this, 'end_lvl'), $cb_args);
168                 }
169
170                 //end this element
171                 $cb_args = array_merge( array(&$output, $element, $depth), $args);
172                 call_user_func_array(array($this, 'end_el'), $cb_args);
173         }
174
175         /**
176          * Display array of elements hierarchically.
177          *
178          * Does not assume any existing order of elements.
179          *
180          * $max_depth = -1 means flatly display every element.
181          * $max_depth = 0 means display all levels.
182          * $max_depth > 0 specifies the number of display levels.
183          *
184          * @since 2.1.0
185          *
186          * @param array $elements  An array of elements.
187          * @param int   $max_depth The maximum hierarchical depth.
188          * @return string The hierarchical item output.
189          */
190         public function walk( $elements, $max_depth ) {
191                 $args = array_slice(func_get_args(), 2);
192                 $output = '';
193
194                 //invalid parameter or nothing to walk
195                 if ( $max_depth < -1 || empty( $elements ) ) {
196                         return $output;
197                 }
198
199                 $parent_field = $this->db_fields['parent'];
200
201                 // flat display
202                 if ( -1 == $max_depth ) {
203                         $empty_array = array();
204                         foreach ( $elements as $e )
205                                 $this->display_element( $e, $empty_array, 1, 0, $args, $output );
206                         return $output;
207                 }
208
209                 /*
210                  * Need to display in hierarchical order.
211                  * Separate elements into two buckets: top level and children elements.
212                  * Children_elements is two dimensional array, eg.
213                  * Children_elements[10][] contains all sub-elements whose parent is 10.
214                  */
215                 $top_level_elements = array();
216                 $children_elements  = array();
217                 foreach ( $elements as $e) {
218                         if ( empty( $e->$parent_field ) )
219                                 $top_level_elements[] = $e;
220                         else
221                                 $children_elements[ $e->$parent_field ][] = $e;
222                 }
223
224                 /*
225                  * When none of the elements is top level.
226                  * Assume the first one must be root of the sub elements.
227                  */
228                 if ( empty($top_level_elements) ) {
229
230                         $first = array_slice( $elements, 0, 1 );
231                         $root = $first[0];
232
233                         $top_level_elements = array();
234                         $children_elements  = array();
235                         foreach ( $elements as $e) {
236                                 if ( $root->$parent_field == $e->$parent_field )
237                                         $top_level_elements[] = $e;
238                                 else
239                                         $children_elements[ $e->$parent_field ][] = $e;
240                         }
241                 }
242
243                 foreach ( $top_level_elements as $e )
244                         $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
245
246                 /*
247                  * If we are displaying all levels, and remaining children_elements is not empty,
248                  * then we got orphans, which should be displayed regardless.
249                  */
250                 if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
251                         $empty_array = array();
252                         foreach ( $children_elements as $orphans )
253                                 foreach ( $orphans as $op )
254                                         $this->display_element( $op, $empty_array, 1, 0, $args, $output );
255                  }
256
257                  return $output;
258         }
259
260         /**
261          * paged_walk() - produce a page of nested elements
262          *
263          * Given an array of hierarchical elements, the maximum depth, a specific page number,
264          * and number of elements per page, this function first determines all top level root elements
265          * belonging to that page, then lists them and all of their children in hierarchical order.
266          *
267          * $max_depth = 0 means display all levels.
268          * $max_depth > 0 specifies the number of display levels.
269          *
270          * @since 2.7.0
271          *
272          * @param array $elements
273          * @param int   $max_depth The maximum hierarchical depth.
274          * @param int   $page_num The specific page number, beginning with 1.
275          * @param int   $per_page
276          * @return string XHTML of the specified page of elements
277          */
278         public function paged_walk( $elements, $max_depth, $page_num, $per_page ) {
279                 if ( empty( $elements ) || $max_depth < -1 ) {
280                         return '';
281                 }
282
283                 $args = array_slice( func_get_args(), 4 );
284                 $output = '';
285
286                 $parent_field = $this->db_fields['parent'];
287
288                 $count = -1;
289                 if ( -1 == $max_depth )
290                         $total_top = count( $elements );
291                 if ( $page_num < 1 || $per_page < 0  ) {
292                         // No paging
293                         $paging = false;
294                         $start = 0;
295                         if ( -1 == $max_depth )
296                                 $end = $total_top;
297                         $this->max_pages = 1;
298                 } else {
299                         $paging = true;
300                         $start = ( (int)$page_num - 1 ) * (int)$per_page;
301                         $end   = $start + $per_page;
302                         if ( -1 == $max_depth )
303                                 $this->max_pages = ceil($total_top / $per_page);
304                 }
305
306                 // flat display
307                 if ( -1 == $max_depth ) {
308                         if ( !empty($args[0]['reverse_top_level']) ) {
309                                 $elements = array_reverse( $elements );
310                                 $oldstart = $start;
311                                 $start = $total_top - $end;
312                                 $end = $total_top - $oldstart;
313                         }
314
315                         $empty_array = array();
316                         foreach ( $elements as $e ) {
317                                 $count++;
318                                 if ( $count < $start )
319                                         continue;
320                                 if ( $count >= $end )
321                                         break;
322                                 $this->display_element( $e, $empty_array, 1, 0, $args, $output );
323                         }
324                         return $output;
325                 }
326
327                 /*
328                  * Separate elements into two buckets: top level and children elements.
329                  * Children_elements is two dimensional array, e.g.
330                  * $children_elements[10][] contains all sub-elements whose parent is 10.
331                  */
332                 $top_level_elements = array();
333                 $children_elements  = array();
334                 foreach ( $elements as $e) {
335                         if ( 0 == $e->$parent_field )
336                                 $top_level_elements[] = $e;
337                         else
338                                 $children_elements[ $e->$parent_field ][] = $e;
339                 }
340
341                 $total_top = count( $top_level_elements );
342                 if ( $paging )
343                         $this->max_pages = ceil($total_top / $per_page);
344                 else
345                         $end = $total_top;
346
347                 if ( !empty($args[0]['reverse_top_level']) ) {
348                         $top_level_elements = array_reverse( $top_level_elements );
349                         $oldstart = $start;
350                         $start = $total_top - $end;
351                         $end = $total_top - $oldstart;
352                 }
353                 if ( !empty($args[0]['reverse_children']) ) {
354                         foreach ( $children_elements as $parent => $children )
355                                 $children_elements[$parent] = array_reverse( $children );
356                 }
357
358                 foreach ( $top_level_elements as $e ) {
359                         $count++;
360
361                         // For the last page, need to unset earlier children in order to keep track of orphans.
362                         if ( $end >= $total_top && $count < $start )
363                                         $this->unset_children( $e, $children_elements );
364
365                         if ( $count < $start )
366                                 continue;
367
368                         if ( $count >= $end )
369                                 break;
370
371                         $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
372                 }
373
374                 if ( $end >= $total_top && count( $children_elements ) > 0 ) {
375                         $empty_array = array();
376                         foreach ( $children_elements as $orphans )
377                                 foreach ( $orphans as $op )
378                                         $this->display_element( $op, $empty_array, 1, 0, $args, $output );
379                 }
380
381                 return $output;
382         }
383
384         /**
385          * Calculates the total number of root elements.
386          *
387          * @since 2.7.0
388          * @access public
389          *
390          * @param array $elements Elements to list.
391          * @return int Number of root elements.
392          */
393         public function get_number_of_root_elements( $elements ){
394                 $num = 0;
395                 $parent_field = $this->db_fields['parent'];
396
397                 foreach ( $elements as $e) {
398                         if ( 0 == $e->$parent_field )
399                                 $num++;
400                 }
401                 return $num;
402         }
403
404         /**
405          * Unset all the children for a given top level element.
406          *
407          * @param object $e
408          * @param array $children_elements
409          */
410         public function unset_children( $e, &$children_elements ){
411                 if ( ! $e || ! $children_elements ) {
412                         return;
413                 }
414
415                 $id_field = $this->db_fields['id'];
416                 $id = $e->$id_field;
417
418                 if ( !empty($children_elements[$id]) && is_array($children_elements[$id]) )
419                         foreach ( (array) $children_elements[$id] as $child )
420                                 $this->unset_children( $child, $children_elements );
421
422                 unset( $children_elements[ $id ] );
423         }
424
425 } // Walker