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