Walker::start_el()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 1
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 5
dl 0
loc 1
rs 10
c 0
b 0
f 0
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
	 */
131
	public function display_element( $element, &$children_elements, $max_depth, $depth, $args, &$output ) {
132
		if ( ! $element ) {
133
			return;
134
		}
135
136
		$id_field = $this->db_fields['id'];
137
		$id       = $element->$id_field;
138
139
		//display this element
140
		$this->has_children = ! empty( $children_elements[ $id ] );
141 View Code Duplication
		if ( isset( $args[0] ) && is_array( $args[0] ) ) {
142
			$args[0]['has_children'] = $this->has_children; // Back-compat.
143
		}
144
145
		$cb_args = array_merge( array(&$output, $element, $depth), $args);
146
		call_user_func_array(array($this, 'start_el'), $cb_args);
147
148
		// descend only when the depth is right and there are childrens for this element
149
		if ( ($max_depth == 0 || $max_depth > $depth+1 ) && isset( $children_elements[$id]) ) {
150
151
			foreach ( $children_elements[ $id ] as $child ){
152
153 View Code Duplication
				if ( !isset($newlevel) ) {
154
					$newlevel = true;
155
					//start the child delimiter
156
					$cb_args = array_merge( array(&$output, $depth), $args);
157
					call_user_func_array(array($this, 'start_lvl'), $cb_args);
158
				}
159
				$this->display_element( $child, $children_elements, $max_depth, $depth + 1, $args, $output );
160
			}
161
			unset( $children_elements[ $id ] );
162
		}
163
164 View Code Duplication
		if ( isset($newlevel) && $newlevel ){
165
			//end the child delimiter
166
			$cb_args = array_merge( array(&$output, $depth), $args);
167
			call_user_func_array(array($this, 'end_lvl'), $cb_args);
168
		}
169
170
		//end this element
171
		$cb_args = array_merge( array(&$output, $element, $depth), $args);
172
		call_user_func_array(array($this, 'end_el'), $cb_args);
173
	}
174
175
	/**
176
	 * Display array of elements hierarchically.
177
	 *
178
	 * Does not assume any existing order of elements.
179
	 *
180
	 * $max_depth = -1 means flatly display every element.
181
	 * $max_depth = 0 means display all levels.
182
	 * $max_depth > 0 specifies the number of display levels.
183
	 *
184
	 * @since 2.1.0
185
	 *
186
	 * @param array $elements  An array of elements.
187
	 * @param int   $max_depth The maximum hierarchical depth.
188
	 * @return string The hierarchical item output.
189
	 */
190
	public function walk( $elements, $max_depth ) {
191
		$args = array_slice(func_get_args(), 2);
192
		$output = '';
193
194
		//invalid parameter or nothing to walk
195
		if ( $max_depth < -1 || empty( $elements ) ) {
196
			return $output;
197
		}
198
199
		$parent_field = $this->db_fields['parent'];
200
201
		// flat display
202
		if ( -1 == $max_depth ) {
203
			$empty_array = array();
204
			foreach ( $elements as $e )
205
				$this->display_element( $e, $empty_array, 1, 0, $args, $output );
206
			return $output;
207
		}
208
209
		/*
210
		 * Need to display in hierarchical order.
211
		 * Separate elements into two buckets: top level and children elements.
212
		 * Children_elements is two dimensional array, eg.
213
		 * Children_elements[10][] contains all sub-elements whose parent is 10.
214
		 */
215
		$top_level_elements = array();
216
		$children_elements  = array();
217 View Code Duplication
		foreach ( $elements as $e) {
218
			if ( empty( $e->$parent_field ) )
219
				$top_level_elements[] = $e;
220
			else
221
				$children_elements[ $e->$parent_field ][] = $e;
222
		}
223
224
		/*
225
		 * When none of the elements is top level.
226
		 * Assume the first one must be root of the sub elements.
227
		 */
228
		if ( empty($top_level_elements) ) {
229
230
			$first = array_slice( $elements, 0, 1 );
231
			$root = $first[0];
232
233
			$top_level_elements = array();
234
			$children_elements  = array();
235 View Code Duplication
			foreach ( $elements as $e) {
236
				if ( $root->$parent_field == $e->$parent_field )
237
					$top_level_elements[] = $e;
238
				else
239
					$children_elements[ $e->$parent_field ][] = $e;
240
			}
241
		}
242
243
		foreach ( $top_level_elements as $e )
244
			$this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
245
246
		/*
247
		 * If we are displaying all levels, and remaining children_elements is not empty,
248
		 * then we got orphans, which should be displayed regardless.
249
		 */
250 View Code Duplication
		if ( ( $max_depth == 0 ) && count( $children_elements ) > 0 ) {
251
			$empty_array = array();
252
			foreach ( $children_elements as $orphans )
253
				foreach ( $orphans as $op )
254
					$this->display_element( $op, $empty_array, 1, 0, $args, $output );
255
		 }
256
257
		 return $output;
258
	}
259
260
	/**
261
 	 * paged_walk() - produce a page of nested elements
262
 	 *
263
 	 * Given an array of hierarchical elements, the maximum depth, a specific page number,
264
 	 * and number of elements per page, this function first determines all top level root elements
265
 	 * belonging to that page, then lists them and all of their children in hierarchical order.
266
 	 *
267
	 * $max_depth = 0 means display all levels.
268
	 * $max_depth > 0 specifies the number of display levels.
269
	 *
270
 	 * @since 2.7.0
271
	 *
272
	 * @param array $elements
273
	 * @param int   $max_depth The maximum hierarchical depth.
274
	 * @param int   $page_num The specific page number, beginning with 1.
275
	 * @param int   $per_page
276
	 * @return string XHTML of the specified page of elements
277
	 */
278
	public function paged_walk( $elements, $max_depth, $page_num, $per_page ) {
279
		if ( empty( $elements ) || $max_depth < -1 ) {
280
			return '';
281
		}
282
283
		$args = array_slice( func_get_args(), 4 );
284
		$output = '';
285
286
		$parent_field = $this->db_fields['parent'];
287
288
		$count = -1;
289
		if ( -1 == $max_depth )
290
			$total_top = count( $elements );
291
		if ( $page_num < 1 || $per_page < 0  ) {
292
			// No paging
293
			$paging = false;
294
			$start = 0;
295
			if ( -1 == $max_depth )
296
				$end = $total_top;
0 ignored issues
show
Bug introduced by
The variable $total_top does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
297
			$this->max_pages = 1;
298
		} else {
299
			$paging = true;
300
			$start = ( (int)$page_num - 1 ) * (int)$per_page;
301
			$end   = $start + $per_page;
302
			if ( -1 == $max_depth )
303
				$this->max_pages = ceil($total_top / $per_page);
0 ignored issues
show
Documentation Bug introduced by
The property $max_pages was declared of type integer, but ceil($total_top / $per_page) is of type double. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
304
		}
305
306
		// flat display
307
		if ( -1 == $max_depth ) {
308 View Code Duplication
			if ( !empty($args[0]['reverse_top_level']) ) {
309
				$elements = array_reverse( $elements );
310
				$oldstart = $start;
311
				$start = $total_top - $end;
0 ignored issues
show
Bug introduced by
The variable $end does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
312
				$end = $total_top - $oldstart;
313
			}
314
315
			$empty_array = array();
316
			foreach ( $elements as $e ) {
317
				$count++;
318
				if ( $count < $start )
319
					continue;
320
				if ( $count >= $end )
321
					break;
322
				$this->display_element( $e, $empty_array, 1, 0, $args, $output );
323
			}
324
			return $output;
325
		}
326
327
		/*
328
		 * Separate elements into two buckets: top level and children elements.
329
		 * Children_elements is two dimensional array, e.g.
330
		 * $children_elements[10][] contains all sub-elements whose parent is 10.
331
		 */
332
		$top_level_elements = array();
333
		$children_elements  = array();
334 View Code Duplication
		foreach ( $elements as $e) {
335
			if ( 0 == $e->$parent_field )
336
				$top_level_elements[] = $e;
337
			else
338
				$children_elements[ $e->$parent_field ][] = $e;
339
		}
340
341
		$total_top = count( $top_level_elements );
342
		if ( $paging )
343
			$this->max_pages = ceil($total_top / $per_page);
344
		else
345
			$end = $total_top;
346
347 View Code Duplication
		if ( !empty($args[0]['reverse_top_level']) ) {
348
			$top_level_elements = array_reverse( $top_level_elements );
349
			$oldstart = $start;
350
			$start = $total_top - $end;
351
			$end = $total_top - $oldstart;
352
		}
353
		if ( !empty($args[0]['reverse_children']) ) {
354
			foreach ( $children_elements as $parent => $children )
355
				$children_elements[$parent] = array_reverse( $children );
356
		}
357
358
		foreach ( $top_level_elements as $e ) {
359
			$count++;
360
361
			// For the last page, need to unset earlier children in order to keep track of orphans.
362
			if ( $end >= $total_top && $count < $start )
363
					$this->unset_children( $e, $children_elements );
364
365
			if ( $count < $start )
366
				continue;
367
368
			if ( $count >= $end )
369
				break;
370
371
			$this->display_element( $e, $children_elements, $max_depth, 0, $args, $output );
372
		}
373
374 View Code Duplication
		if ( $end >= $total_top && count( $children_elements ) > 0 ) {
375
			$empty_array = array();
376
			foreach ( $children_elements as $orphans )
377
				foreach ( $orphans as $op )
378
					$this->display_element( $op, $empty_array, 1, 0, $args, $output );
379
		}
380
381
		return $output;
382
	}
383
384
	/**
385
	 * Calculates the total number of root elements.
386
	 *
387
	 * @since 2.7.0
388
	 * @access public
389
	 *
390
	 * @param array $elements Elements to list.
391
	 * @return int Number of root elements.
392
	 */
393
	public function get_number_of_root_elements( $elements ){
394
		$num = 0;
395
		$parent_field = $this->db_fields['parent'];
396
397
		foreach ( $elements as $e) {
398
			if ( 0 == $e->$parent_field )
399
				$num++;
400
		}
401
		return $num;
402
	}
403
404
	/**
405
	 * Unset all the children for a given top level element.
406
	 *
407
	 * @param object $e
408
	 * @param array $children_elements
409
	 */
410
	public function unset_children( $e, &$children_elements ){
411
		if ( ! $e || ! $children_elements ) {
412
			return;
413
		}
414
415
		$id_field = $this->db_fields['id'];
416
		$id = $e->$id_field;
417
418
		if ( !empty($children_elements[$id]) && is_array($children_elements[$id]) )
419
			foreach ( (array) $children_elements[$id] as $child )
420
				$this->unset_children( $child, $children_elements );
421
422
		unset( $children_elements[ $id ] );
423
	}
424
425
} // Walker
426