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