WordPress 4.2
[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          * @return null Null on failure with no changes to parameters.
131          */
132         public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
133
134                 if ( !$element )
135                         return;
136
137                 $id_field = $this->db_fields['id'];
138                 $id       = $element->$id_field;
139
140                 //display this element
141                 $this->has_children = ! empty( $children_elements[ $id ] );
142                 if ( isset( $args[0] ) && is_array( $args[0] ) ) {
143                         $args[0]['has_children'] = $this->has_children; // Backwards compatibility.
144                 }
145
146                 $cb_args = array_merge( array(&$output, $element, $depth), $args);
147                 call_user_func_array(array($this, 'start_el'), $cb_args);
148
149                 // descend only when the depth is right and there are childrens for this element
150                 if ( ($max_depth == 0 || $max_depth > $depth+1 ) && isset( $children_elements[$id]) ) {
151
152                         foreach( $children_elements[ $id ] as $child ){
153
154                                 if ( !isset($newlevel) ) {
155                                         $newlevel = true;
156                                         //start the child delimiter
157                                         $cb_args = array_merge( array(&$output, $depth), $args);
158                                         call_user_func_array(array($this, 'start_lvl'), $cb_args);
159                                 }
160                                 $this->display_element( $child, $children_elements, $max_depth, $depth + 1, $args, $output );
161                         }
162                         unset( $children_elements[ $id ] );
163                 }
164
165                 if ( isset($newlevel) && $newlevel ){
166                         //end the child delimiter
167                         $cb_args = array_merge( array(&$output, $depth), $args);
168                         call_user_func_array(array($this, 'end_lvl'), $cb_args);
169                 }
170
171                 //end this element
172                 $cb_args = array_merge( array(&$output, $element, $depth), $args);
173                 call_user_func_array(array($this, 'end_el'), $cb_args);
174         }
175
176         /**
177          * Display array of elements hierarchically.
178          *
179          * Does not assume any existing order of elements.
180          *
181          * $max_depth = -1 means flatly display every element.
182          * $max_depth = 0 means display all levels.
183          * $max_depth > 0 specifies the number of display levels.
184          *
185          * @since 2.1.0
186          *
187          * @param array $elements  An array of elements.
188          * @param int   $max_depth The maximum hierarchical depth.
189          * @return string The hierarchical item output.
190          */
191         public function walk( $elements, $max_depth) {
192
193                 $args = array_slice(func_get_args(), 2);
194                 $output = '';
195
196                 if ($max_depth < -1) //invalid parameter
197                         return $output;
198
199                 if (empty($elements)) //nothing to walk
200                         return $output;
201
202                 $parent_field = $this->db_fields['parent'];
203
204                 // flat display
205                 if ( -1 == $max_depth ) {
206                         $empty_array = array();
207                         foreach ( $elements as $e )
208                                 $this->display_element( $e, $empty_array, 1, 0, $args, $output );
209                         return $output;
210                 }
211
212                 /*
213                  * Need to display in hierarchical order.
214                  * Separate elements into two buckets: top level and children elements.
215                  * Children_elements is two dimensional array, eg.
216                  * Children_elements[10][] contains all sub-elements whose parent is 10.
217                  */
218                 $top_level_elements = array();
219                 $children_elements  = array();
220                 foreach ( $elements as $e) {
221                         if ( 0 == $e->$parent_field )
222                                 $top_level_elements[] = $e;
223                         else
224                                 $children_elements[ $e->$parent_field ][] = $e;
225                 }
226
227                 /*
228                  * When none of the elements is top level.
229                  * Assume the first one must be root of the sub elements.
230                  */
231                 if ( empty($top_level_elements) ) {
232
233                         $first = array_slice( $elements, 0, 1 );
234                         $root = $first[0];
235
236                         $top_level_elements = array();
237                         $children_elements  = array();
238                         foreach ( $elements as $e) {
239                                 if ( $root->$parent_field == $e->$parent_field )
240                                         $top_level_elements[] = $e;
241                                 else
242                                         $children_elements[ $e->$parent_field ][] = $e;
243                         }
244                 }
245
246                 foreach ( $top_level_elements as $e )
247                         $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
248
249                 /*
250                  * If we are displaying all levels, and remaining children_elements is not empty,
251                  * then we got orphans, which should be displayed regardless.
252                  */
253                 if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
254                         $empty_array = array();
255                         foreach ( $children_elements as $orphans )
256                                 foreach( $orphans as $op )
257                                         $this->display_element( $op, $empty_array, 1, 0, $args, $output );
258                  }
259
260                  return $output;
261         }
262
263         /**
264          * paged_walk() - produce a page of nested elements
265          *
266          * Given an array of hierarchical elements, the maximum depth, a specific page number,
267          * and number of elements per page, this function first determines all top level root elements
268          * belonging to that page, then lists them and all of their children in hierarchical order.
269          *
270          * $max_depth = 0 means display all levels.
271          * $max_depth > 0 specifies the number of display levels.
272          *
273          * @since 2.7.0
274          *
275          * @param int $max_depth The maximum hierarchical depth.
276          * @param int $page_num  The specific page number, beginning with 1.
277          * @return string XHTML of the specified page of elements
278          */
279         public function paged_walk( $elements, $max_depth, $page_num, $per_page ) {
280
281                 /* sanity check */
282                 if ( empty($elements) || $max_depth < -1 )
283                         return '';
284
285                 $args = array_slice( func_get_args(), 4 );
286                 $output = '';
287
288                 $parent_field = $this->db_fields['parent'];
289
290                 $count = -1;
291                 if ( -1 == $max_depth )
292                         $total_top = count( $elements );
293                 if ( $page_num < 1 || $per_page < 0  ) {
294                         // No paging
295                         $paging = false;
296                         $start = 0;
297                         if ( -1 == $max_depth )
298                                 $end = $total_top;
299                         $this->max_pages = 1;
300                 } else {
301                         $paging = true;
302                         $start = ( (int)$page_num - 1 ) * (int)$per_page;
303                         $end   = $start + $per_page;
304                         if ( -1 == $max_depth )
305                                 $this->max_pages = ceil($total_top / $per_page);
306                 }
307
308                 // flat display
309                 if ( -1 == $max_depth ) {
310                         if ( !empty($args[0]['reverse_top_level']) ) {
311                                 $elements = array_reverse( $elements );
312                                 $oldstart = $start;
313                                 $start = $total_top - $end;
314                                 $end = $total_top - $oldstart;
315                         }
316
317                         $empty_array = array();
318                         foreach ( $elements as $e ) {
319                                 $count++;
320                                 if ( $count < $start )
321                                         continue;
322                                 if ( $count >= $end )
323                                         break;
324                                 $this->display_element( $e, $empty_array, 1, 0, $args, $output );
325                         }
326                         return $output;
327                 }
328
329                 /*
330                  * Separate elements into two buckets: top level and children elements.
331                  * Children_elements is two dimensional array, e.g.
332                  * $children_elements[10][] contains all sub-elements whose parent is 10.
333                  */
334                 $top_level_elements = array();
335                 $children_elements  = array();
336                 foreach ( $elements as $e) {
337                         if ( 0 == $e->$parent_field )
338                                 $top_level_elements[] = $e;
339                         else
340                                 $children_elements[ $e->$parent_field ][] = $e;
341                 }
342
343                 $total_top = count( $top_level_elements );
344                 if ( $paging )
345                         $this->max_pages = ceil($total_top / $per_page);
346                 else
347                         $end = $total_top;
348
349                 if ( !empty($args[0]['reverse_top_level']) ) {
350                         $top_level_elements = array_reverse( $top_level_elements );
351                         $oldstart = $start;
352                         $start = $total_top - $end;
353                         $end = $total_top - $oldstart;
354                 }
355                 if ( !empty($args[0]['reverse_children']) ) {
356                         foreach ( $children_elements as $parent => $children )
357                                 $children_elements[$parent] = array_reverse( $children );
358                 }
359
360                 foreach ( $top_level_elements as $e ) {
361                         $count++;
362
363                         // For the last page, need to unset earlier children in order to keep track of orphans.
364                         if ( $end >= $total_top && $count < $start )
365                                         $this->unset_children( $e, $children_elements );
366
367                         if ( $count < $start )
368                                 continue;
369
370                         if ( $count >= $end )
371                                 break;
372
373                         $this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
374                 }
375
376                 if ( $end >= $total_top && count( $children_elements ) > 0 ) {
377                         $empty_array = array();
378                         foreach ( $children_elements as $orphans )
379                                 foreach( $orphans as $op )
380                                         $this->display_element( $op, $empty_array, 1, 0, $args, $output );
381                 }
382
383                 return $output;
384         }
385
386         public function get_number_of_root_elements( $elements ){
387
388                 $num = 0;
389                 $parent_field = $this->db_fields['parent'];
390
391                 foreach ( $elements as $e) {
392                         if ( 0 == $e->$parent_field )
393                                 $num++;
394                 }
395                 return $num;
396         }
397
398         // Unset all the children for a given top level element.
399         public function unset_children( $e, &$children_elements ){
400
401                 if ( !$e || !$children_elements )
402                         return;
403
404                 $id_field = $this->db_fields['id'];
405                 $id = $e->$id_field;
406
407                 if ( !empty($children_elements[$id]) && is_array($children_elements[$id]) )
408                         foreach ( (array) $children_elements[$id] as $child )
409                                 $this->unset_children( $child, $children_elements );
410
411                 if ( isset($children_elements[$id]) )
412                         unset( $children_elements[$id] );
413
414         }
415
416 } // Walker