~canonical-sysadmins/wordpress/4.7.4

« back to all changes in this revision

Viewing changes to wp-includes/class-wp-walker.php

  • Committer: Jacek Nykis
  • Date: 2015-01-05 16:17:05 UTC
  • Revision ID: jacek.nykis@canonical.com-20150105161705-w544l1h5mcg7u4w9
Initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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