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