Completed
Push — 3.7 ( 677d28...81b2d8 )
by Robbie
15:41 queued 06:22
created

Hierarchy::getChildrenAsUL()   C

Complexity

Conditions 20
Paths 100

Size

Total Lines 78
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 45
nc 100
nop 9
dl 0
loc 78
rs 5.111
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * DataObjects that use the Hierarchy extension can be be organised as a hierarchy, with children and parents. The most
4
 * obvious example of this is SiteTree.
5
 *
6
 * @package framework
7
 * @subpackage model
8
 *
9
 * @property int        ParentID
10
 * @property DataObject owner
11
 * @method   DataObject Parent
12
 */
13
class Hierarchy extends DataExtension {
14
15
	protected $markedNodes;
16
17
	protected $markingFilter;
18
19
	/** @var int */
20
	protected $_cache_numChildren;
21
22
	/**
23
	 * The lower bounds for the amount of nodes to mark. If set, the logic will expand nodes until it reaches at least
24
	 * this number, and then stops. Root nodes will always show regardless of this settting. Further nodes can be
25
	 * lazy-loaded via ajax. This isn't a hard limit. Example: On a value of 10, with 20 root nodes, each having 30
26
	 * children, the actual node count will be 50 (all root nodes plus first expanded child).
27
	 *
28
	 * @config
29
	 * @var int
30
	 */
31
	private static $node_threshold_total = 50;
32
33
	/**
34
	 * Limit on the maximum children a specific node can display. Serves as a hard limit to avoid exceeding available
35
	 * server resources in generating the tree, and browser resources in rendering it. Nodes with children exceeding
36
	 * this value typically won't display any children, although this is configurable through the $nodeCountCallback
37
	 * parameter in {@link getChildrenAsUL()}. "Root" nodes will always show all children, regardless of this setting.
38
	 *
39
	 * @config
40
	 * @var int
41
	 */
42
	private static $node_threshold_leaf = 250;
43
44
	/**
45
	 * A list of classnames to exclude from display in both the CMS and front end
46
	 * displays. ->Children() and ->AllChildren affected.
47
	 * Especially useful for big sets of pages like listings
48
	 * If you use this, and still need the classes to be editable
49
	 * then add a model admin for the class
50
	 * Note: Does not filter subclasses (non-inheriting)
51
	 *
52
	 * @var array
53
	 * @config
54
	 */
55
	private static $hide_from_hierarchy = array();
56
57
	/**
58
	 * A list of classnames to exclude from display in the page tree views of the CMS,
59
	 * unlike $hide_from_hierarchy above which effects both CMS and front end.
60
	 * Especially useful for big sets of pages like listings
61
	 * If you use this, and still need the classes to be editable
62
	 * then add a model admin for the class
63
	 * Note: Does not filter subclasses (non-inheriting)
64
	 *
65
	 * @var array
66
	 * @config
67
	 */
68
	private static $hide_from_cms_tree = array();
69
70
	public static function get_extra_config($class, $extension, $args) {
71
		return array(
72
			'has_one' => array('Parent' => $class)
73
		);
74
	}
75
76
	/**
77
	 * Validate the owner object - check for existence of infinite loops.
78
	 *
79
	 * @param ValidationResult $validationResult
80
	 */
81
	public function validate(ValidationResult $validationResult) {
82
		// The object is new, won't be looping.
83
		if (!$this->owner->ID) return;
84
		// The object has no parent, won't be looping.
85
		if (!$this->owner->ParentID) return;
86
		// The parent has not changed, skip the check for performance reasons.
87
		if (!$this->owner->isChanged('ParentID')) return;
88
89
		// Walk the hierarchy upwards until we reach the top, or until we reach the originating node again.
90
		$node = $this->owner;
91
		while($node) {
92
			if ($node->ParentID==$this->owner->ID) {
93
				// Hierarchy is looping.
94
				$validationResult->error(
95
					_t(
96
						'Hierarchy.InfiniteLoopNotAllowed',
97
						'Infinite loop found within the "{type}" hierarchy. Please change the parent to resolve this',
98
						'First argument is the class that makes up the hierarchy.',
99
						array('type' => $this->owner->class)
100
					),
101
					'INFINITE_LOOP'
102
				);
103
				break;
104
			}
105
			$node = $node->ParentID ? $node->Parent() : null;
106
		}
107
108
		// At this point the $validationResult contains the response.
109
	}
110
111
	/**
112
	 * Returns the children of this DataObject as an XHTML UL. This will be called recursively on each child, so if they
113
	 * have children they will be displayed as a UL inside a LI.
114
	 *
115
	 * @param string          $attributes         Attributes to add to the UL
116
	 * @param string|callable $titleEval          PHP code to evaluate to start each child - this should include '<li>'
117
	 * @param string          $extraArg           Extra arguments that will be passed on to children, for if they
118
	 *                                            overload this function
119
	 * @param bool            $limitToMarked      Display only marked children
120
	 * @param string          $childrenMethod     The name of the method used to get children from each object
121
	 * @param bool            $rootCall           Set to true for this first call, and then to false for calls inside
122
	 *                                            the recursion. You should not change this.
123
	 * @param int             $nodeCountThreshold See {@link self::$node_threshold_total}
124
	 * @param callable        $nodeCountCallback  Called with the node count, which gives the callback an opportunity to
125
	 *                                            intercept the query. Useful e.g. to avoid excessive children listings
126
	 *                                            (Arguments: $parent, $numChildren)
127
	 *
128
	 * @return string
129
	 */
130
	public function getChildrenAsUL($attributes = "", $titleEval = '"<li>" . $child->Title', $extraArg = null,
131
			$limitToMarked = false, $childrenMethod = "AllChildrenIncludingDeleted",
132
			$numChildrenMethod = "numChildren", $rootCall = true,
133
			$nodeCountThreshold = null, $nodeCountCallback = null) {
134
135
		if(!is_numeric($nodeCountThreshold)) {
136
			$nodeCountThreshold = Config::inst()->get('Hierarchy', 'node_threshold_total');
137
		}
138
139
		if($limitToMarked && $rootCall) {
140
			$this->markingFinished($numChildrenMethod);
141
		}
142
143
144
		if($nodeCountCallback) {
145
			$nodeCountWarning = $nodeCountCallback($this->owner, $this->owner->$numChildrenMethod());
146
			if($nodeCountWarning) return $nodeCountWarning;
147
		}
148
149
150
		if($this->owner->hasMethod($childrenMethod)) {
151
			$children = $this->owner->$childrenMethod($extraArg);
152
		} else {
153
			user_error(sprintf("Can't find the method '%s' on class '%s' for getting tree children",
154
				$childrenMethod, get_class($this->owner)), E_USER_ERROR);
155
		}
156
157
		if($children) {
158
159
			if($attributes) {
160
				$attributes = " $attributes";
161
			}
162
163
			$output = "<ul$attributes>\n";
164
165
			foreach($children as $child) {
0 ignored issues
show
Bug introduced by
The variable $children 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...
166
				if(!$limitToMarked || $child->isMarked()) {
167
					$foundAChild = true;
168
					if(is_callable($titleEval)) {
169
						$output .= $titleEval($child, $numChildrenMethod);
170
					} else {
171
						$output .= eval("return $titleEval;");
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
172
					}
173
					$output .= "\n";
174
175
					$numChildren = $child->$numChildrenMethod();
176
177
					if(
178
						// Always traverse into opened nodes (they might be exposed as parents of search results)
179
						$child->isExpanded()
180
						// Only traverse into children if we haven't reached the maximum node count already.
181
						// Otherwise, the remaining nodes are lazy loaded via ajax.
182
						&& $child->isMarked()
183
					) {
184
						// Additionally check if node count requirements are met
185
						$nodeCountWarning = $nodeCountCallback ? $nodeCountCallback($child, $numChildren) : null;
186
						if($nodeCountWarning) {
187
							$output .= $nodeCountWarning;
188
							$child->markClosed();
189
						} else {
190
							$output .= $child->getChildrenAsUL("", $titleEval, $extraArg, $limitToMarked,
191
								$childrenMethod,	$numChildrenMethod, false, $nodeCountThreshold);
192
						}
193
					} elseif($child->isTreeOpened()) {
194
						// Since we're not loading children, don't mark it as open either
195
						$child->markClosed();
196
					}
197
					$output .= "</li>\n";
198
				}
199
			}
200
201
			$output .= "</ul>\n";
202
		}
203
204
		if(isset($foundAChild) && $foundAChild) {
205
			return $output;
0 ignored issues
show
Bug introduced by
The variable $output 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...
206
		}
207
	}
208
209
	/**
210
	 * Mark a segment of the tree, by calling mark().
211
	 *
212
	 * The method performs a breadth-first traversal until the number of nodes is more than minCount. This is used to
213
	 * get a limited number of tree nodes to show in the CMS initially.
214
	 *
215
	 * This method returns the number of nodes marked.  After this method is called other methods can check
216
	 * {@link isExpanded()} and {@link isMarked()} on individual nodes.
217
	 *
218
	 * @param int $nodeCountThreshold See {@link getChildrenAsUL()}
219
	 * @return int The actual number of nodes marked.
220
	 */
221
	public function markPartialTree($nodeCountThreshold = null, $context = null,
222
			$childrenMethod = "AllChildrenIncludingDeleted", $numChildrenMethod = "numChildren") {
223
224
		if(!is_numeric($nodeCountThreshold)) {
225
			$nodeCountThreshold = Config::inst()->get('Hierarchy', 'node_threshold_total');
226
		}
227
228
		$this->markedNodes = array($this->owner->ID => $this->owner);
229
		$this->owner->markUnexpanded();
230
231
		$node = reset($this->markedNodes);
232
233
		// foreach can't handle an ever-growing $nodes list
234
		do {
235
			$children = $this->markChildren($node, $context, $childrenMethod, $numChildrenMethod);
236
			if($nodeCountThreshold && sizeof($this->markedNodes) > $nodeCountThreshold) {
237
				// Undo marking children as opened since they're lazy loaded
238
				if($children) foreach($children as $child) $child->markClosed();
239
				break;
240
			}
241
		} while (false !== ($node = next($this->markedNodes)));
242
		return sizeof($this->markedNodes);
243
	}
244
245
	/**
246
	 * Filter the marking to only those object with $node->$parameterName == $parameterValue
247
	 *
248
	 * @param string $parameterName  The parameter on each node to check when marking.
249
	 * @param mixed  $parameterValue The value the parameter must be to be marked.
250
	 */
251
	public function setMarkingFilter($parameterName, $parameterValue) {
252
		$this->markingFilter = array(
253
			"parameter" => $parameterName,
254
			"value" => $parameterValue
255
		);
256
	}
257
258
	/**
259
	 * Filter the marking to only those where the function returns true. The node in question will be passed to the
260
	 * function.
261
	 *
262
	 * @param string $funcName The name of the function to call
263
	 */
264
	public function setMarkingFilterFunction($funcName) {
265
		$this->markingFilter = array(
266
			"func" => $funcName,
267
		);
268
	}
269
270
	/**
271
	 * Returns true if the marking filter matches on the given node.
272
	 *
273
	 * @param DataObject $node Node to check
274
	 * @return bool
275
	 */
276
	public function markingFilterMatches($node) {
277
		if(!$this->markingFilter) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->markingFilter of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
278
			return true;
279
		}
280
281
		if(isset($this->markingFilter['parameter']) && $parameterName = $this->markingFilter['parameter']) {
282
			if(is_array($this->markingFilter['value'])){
283
				$ret = false;
284
				foreach($this->markingFilter['value'] as $value) {
285
					$ret = $ret||$node->$parameterName==$value;
286
					if($ret == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
287
						break;
288
					}
289
				}
290
				return $ret;
291
			} else {
292
				return ($node->$parameterName == $this->markingFilter['value']);
293
			}
294
		} else if ($func = $this->markingFilter['func']) {
295
			return call_user_func($func, $node);
296
		}
297
	}
298
299
	/**
300
	 * Mark all children of the given node that match the marking filter.
301
	 *
302
	 * @param DataObject $node              Parent node
303
	 * @param mixed      $context
304
	 * @param string     $childrenMethod    The name of the instance method to call to get the object's list of children
305
	 * @param string     $numChildrenMethod The name of the instance method to call to count the object's children
306
	 * @return DataList
307
	 */
308
	public function markChildren($node, $context = null, $childrenMethod = "AllChildrenIncludingDeleted",
309
			$numChildrenMethod = "numChildren") {
310
		if($node->hasMethod($childrenMethod)) {
311
			$children = $node->$childrenMethod($context);
312
		} else {
313
			user_error(sprintf("Can't find the method '%s' on class '%s' for getting tree children",
314
				$childrenMethod, get_class($node)), E_USER_ERROR);
315
		}
316
317
		$node->markExpanded();
318
		if($children) {
319
			foreach($children as $child) {
0 ignored issues
show
Bug introduced by
The variable $children 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...
320
				$markingMatches = $this->markingFilterMatches($child);
321
				if($markingMatches) {
322
					// Mark a child node as unexpanded if it has children and has not already been expanded
323
					if($child->$numChildrenMethod() && !$child->isExpanded()) {
324
						$child->markUnexpanded();
325
					} else {
326
						$child->markExpanded();
327
					}
328
					$this->markedNodes[$child->ID] = $child;
329
				}
330
			}
331
		}
332
333
		return $children;
334
	}
335
336
	/**
337
	 * Ensure marked nodes that have children are also marked expanded. Call this after marking but before iterating
338
	 * over the tree.
339
	 *
340
	 * @param string $numChildrenMethod The name of the instance method to call to count the object's children
341
	 */
342
	protected function markingFinished($numChildrenMethod = "numChildren") {
343
		// Mark childless nodes as expanded.
344
		if($this->markedNodes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->markedNodes of type DataObject[] is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
345
			foreach($this->markedNodes as $id => $node) {
346
				if(!$node->isExpanded() && !$node->$numChildrenMethod()) {
347
					$node->markExpanded();
348
				}
349
			}
350
		}
351
	}
352
353
	/**
354
	 * Return CSS classes of 'unexpanded', 'closed', both, or neither, as well as a 'jstree-*' state depending on the
355
	 * marking of this DataObject.
356
	 *
357
	 * @param string $numChildrenMethod The name of the instance method to call to count the object's children
358
	 * @return string
359
	 */
360
	public function markingClasses($numChildrenMethod="numChildren") {
361
		$classes = '';
362
		if(!$this->isExpanded()) {
363
			$classes .= " unexpanded";
364
		}
365
366
		// Set jstree open state, or mark it as a leaf (closed) if there are no children
367
		if(!$this->owner->$numChildrenMethod()) {
368
			$classes .= " jstree-leaf closed";
369
		} elseif($this->isTreeOpened()) {
370
			$classes .= " jstree-open";
371
		} else {
372
			$classes .= " jstree-closed closed";
373
		}
374
		return $classes;
375
	}
376
377
	/**
378
	 * Mark the children of the DataObject with the given ID.
379
	 *
380
	 * @param int  $id   ID of parent node
381
	 * @param bool $open If this is true, mark the parent node as opened
382
	 * @return bool
383
	 */
384
	public function markById($id, $open = false) {
385
		if(isset($this->markedNodes[$id])) {
386
			$this->markChildren($this->markedNodes[$id]);
387
			if($open) {
388
				$this->markedNodes[$id]->markOpened();
389
			}
390
			return true;
391
		} else {
392
			return false;
393
		}
394
	}
395
396
	/**
397
	 * Expose the given object in the tree, by marking this page and all it ancestors.
398
	 *
399
	 * @param DataObject $childObj
400
	 */
401
	public function markToExpose($childObj) {
402
		if(is_object($childObj)){
403
			$stack = array_reverse($childObj->parentStack());
404
			foreach($stack as $stackItem) {
405
				$this->markById($stackItem->ID, true);
406
			}
407
		}
408
	}
409
410
	/**
411
	 * Return the IDs of all the marked nodes.
412
	 *
413
	 * @return array
414
	 */
415
	public function markedNodeIDs() {
416
		return array_keys($this->markedNodes);
417
	}
418
419
	/**
420
	 * Return an array of this page and its ancestors, ordered item -> root.
421
	 *
422
	 * @return SiteTree[]
423
	 */
424
	public function parentStack() {
425
		$p = $this->owner;
426
427
		while($p) {
428
			$stack[] = $p;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$stack was never initialized. Although not strictly required by PHP, it is generally a good practice to add $stack = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
429
			$p = $p->ParentID ? $p->Parent() : null;
430
		}
431
432
		return $stack;
0 ignored issues
show
Bug introduced by
The variable $stack 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...
433
	}
434
435
	/**
436
	 * Cache of DataObjects' marked statuses: [ClassName][ID] = bool
437
	 * @var array
438
	 */
439
	protected static $marked = array();
440
441
	/**
442
	 * Cache of DataObjects' expanded statuses: [ClassName][ID] = bool
443
	 * @var array
444
	 */
445
	protected static $expanded = array();
446
447
	/**
448
	 * Cache of DataObjects' opened statuses: [ClassName][ID] = bool
449
	 * @var array
450
	 */
451
	protected static $treeOpened = array();
452
453
	/**
454
	 * Mark this DataObject as expanded.
455
	 */
456
	public function markExpanded() {
457
		self::$marked[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
458
		self::$expanded[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
459
	}
460
461
	/**
462
	 * Mark this DataObject as unexpanded.
463
	 */
464
	public function markUnexpanded() {
465
		self::$marked[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
466
		self::$expanded[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = false;
467
	}
468
469
	/**
470
	 * Mark this DataObject's tree as opened.
471
	 */
472
	public function markOpened() {
473
		self::$marked[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
474
		self::$treeOpened[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID] = true;
475
	}
476
477
	/**
478
	 * Mark this DataObject's tree as closed.
479
	 */
480
	public function markClosed() {
481
		if(isset(self::$treeOpened[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID])) {
482
			unset(self::$treeOpened[ClassInfo::baseDataClass($this->owner->class)][$this->owner->ID]);
483
		}
484
	}
485
486
	/**
487
	 * Check if this DataObject is marked.
488
	 *
489
	 * @return bool
490
	 */
491
	public function isMarked() {
492
		$baseClass = ClassInfo::baseDataClass($this->owner->class);
493
		$id = $this->owner->ID;
494
		return isset(self::$marked[$baseClass][$id]) ? self::$marked[$baseClass][$id] : false;
495
	}
496
497
	/**
498
	 * Check if this DataObject is expanded.
499
	 *
500
	 * @return bool
501
	 */
502
	public function isExpanded() {
503
		$baseClass = ClassInfo::baseDataClass($this->owner->class);
504
		$id = $this->owner->ID;
505
		return isset(self::$expanded[$baseClass][$id]) ? self::$expanded[$baseClass][$id] : false;
506
	}
507
508
	/**
509
	 * Check if this DataObject's tree is opened.
510
	 *
511
	 * @return bool
512
	 */
513
	public function isTreeOpened() {
514
		$baseClass = ClassInfo::baseDataClass($this->owner->class);
515
		$id = $this->owner->ID;
516
		return isset(self::$treeOpened[$baseClass][$id]) ? self::$treeOpened[$baseClass][$id] : false;
517
	}
518
519
	/**
520
	 * Get a list of this DataObject's and all it's descendants IDs.
521
	 *
522
	 * @return int[]
523
	 */
524
	public function getDescendantIDList() {
525
		$idList = array();
526
		$this->loadDescendantIDListInto($idList);
527
		return $idList;
528
	}
529
530
	/**
531
	 * Get a list of this DataObject's and all it's descendants ID, and put them in $idList.
532
	 *
533
	 * @param array $idList Array to put results in.
534
	 */
535
	public function loadDescendantIDListInto(&$idList) {
536
		if($children = $this->AllChildren()) {
537
			foreach($children as $child) {
538
				if(in_array($child->ID, $idList)) {
539
					continue;
540
				}
541
				$idList[] = $child->ID;
542
				$ext = $child->getExtensionInstance('Hierarchy');
543
				$ext->setOwner($child);
544
				$ext->loadDescendantIDListInto($idList);
545
				$ext->clearOwner();
546
			}
547
		}
548
	}
549
550
	/**
551
	 * Get the children for this DataObject.
552
	 *
553
	 * @return DataList
554
	 */
555
	public function Children() {
556
		if(!(isset($this->_cache_children) && $this->_cache_children)) {
557
			$result = $this->owner->stageChildren(false);
558
			$children = array();
559
			foreach ($result as $record) {
560
				if ($record->canView()) {
561
					$children[] = $record;
562
				}
563
			}
564
			$this->_cache_children = new ArrayList($children);
0 ignored issues
show
Bug introduced by
The property _cache_children does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
565
		}
566
		return $this->_cache_children;
567
	}
568
569
	/**
570
	 * Return all children, including those 'not in menus'.
571
	 *
572
	 * @return DataList
573
	 */
574
	public function AllChildren() {
575
		return $this->owner->stageChildren(true);
576
	}
577
578
	/**
579
	 * Return all children, including those that have been deleted but are still in live.
580
	 * - Deleted children will be marked as "DeletedFromStage"
581
	 * - Added children will be marked as "AddedToStage"
582
	 * - Modified children will be marked as "ModifiedOnStage"
583
	 * - Everything else has "SameOnStage" set, as an indicator that this information has been looked up.
584
	 *
585
	 * @param mixed $context
586
	 * @return ArrayList
587
	 */
588
	public function AllChildrenIncludingDeleted($context = null) {
589
		return $this->doAllChildrenIncludingDeleted($context);
590
	}
591
592
	/**
593
	 * @see AllChildrenIncludingDeleted
594
	 *
595
	 * @param mixed $context
596
	 * @return ArrayList
597
	 */
598
	public function doAllChildrenIncludingDeleted($context = null) {
599
		if(!$this->owner) user_error('Hierarchy::doAllChildrenIncludingDeleted() called without $this->owner');
600
601
		$baseClass = ClassInfo::baseDataClass($this->owner->class);
602
		if($baseClass) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $baseClass of type null|string is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
603
			$stageChildren = $this->owner->stageChildren(true);
604
605
			// Add live site content that doesn't exist on the stage site, if required.
606
			if($this->owner->hasExtension('Versioned')) {
607
				// Next, go through the live children.  Only some of these will be listed
608
				$liveChildren = $this->owner->liveChildren(true, true);
609
				if($liveChildren) {
610
					$merged = new ArrayList();
611
					$merged->merge($stageChildren);
612
					$merged->merge($liveChildren);
613
					$stageChildren = $merged;
614
				}
615
			}
616
617
			$this->owner->extend("augmentAllChildrenIncludingDeleted", $stageChildren, $context);
618
619
		} else {
620
			user_error("Hierarchy::AllChildren() Couldn't determine base class for '{$this->owner->class}'",
621
				E_USER_ERROR);
622
		}
623
624
		return $stageChildren;
0 ignored issues
show
Bug introduced by
The variable $stageChildren 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...
625
	}
626
627
	/**
628
	 * Return all the children that this page had, including pages that were deleted from both stage & live.
629
	 *
630
	 * @return DataList
631
	 * @throws Exception
632
	 */
633
	public function AllHistoricalChildren() {
634
		if(!$this->owner->hasExtension('Versioned')) {
635
			throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
636
		}
637
638
		$baseClass=ClassInfo::baseDataClass($this->owner->class);
639
		return Versioned::get_including_deleted($baseClass,
640
			"\"ParentID\" = " . (int)$this->owner->ID, "\"$baseClass\".\"ID\" ASC");
641
	}
642
643
	/**
644
	 * Return the number of children that this page ever had, including pages that were deleted.
645
	 *
646
	 * @return int
647
	 * @throws Exception
648
	 */
649
	public function numHistoricalChildren() {
650
		if(!$this->owner->hasExtension('Versioned')) {
651
			throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
652
		}
653
654
		return Versioned::get_including_deleted(ClassInfo::baseDataClass($this->owner->class),
655
			"\"ParentID\" = " . (int)$this->owner->ID)->count();
656
	}
657
658
	/**
659
	 * Return the number of direct children. By default, values are cached after the first invocation. Can be
660
	 * augumented by {@link augmentNumChildrenCountQuery()}.
661
	 *
662
	 * @param bool $cache Whether to retrieve values from cache
663
	 * @return int
664
	 */
665
	public function numChildren($cache = true) {
666
		// Build the cache for this class if it doesn't exist.
667
		if(!$cache || !is_numeric($this->_cache_numChildren)) {
668
			// Hey, this is efficient now!
669
			// We call stageChildren(), because Children() has canView() filtering
670
			$this->_cache_numChildren = (int)$this->owner->stageChildren(true)->Count();
671
		}
672
673
		// If theres no value in the cache, it just means that it doesn't have any children.
674
		return $this->_cache_numChildren;
675
	}
676
677
	/**
678
	 * Checks if we're on a controller where we should filter. ie. Are we loading the SiteTree?
679
	 *
680
	 * @return bool
681
	 */
682
	public function showingCMSTree() {
683
		if (!Controller::has_curr()) return false;
684
		$controller = Controller::curr();
685
		return $controller instanceof LeftAndMain
686
			&& in_array($controller->getAction(), array("treeview", "listview", "getsubtree"));
687
	}
688
689
	/**
690
	 * Return children in the stage site.
691
	 *
692
	 * @param bool $showAll Include all of the elements, even those not shown in the menus. Only applicable when
693
	 *                      extension is applied to {@link SiteTree}.
694
	 * @return DataList
695
	 */
696
	public function stageChildren($showAll = false) {
697
		$baseClass = ClassInfo::baseDataClass($this->owner->class);
698
		$hide_from_hierarchy = $this->owner->config()->hide_from_hierarchy;
0 ignored issues
show
Documentation introduced by
The property hide_from_hierarchy does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
699
		$hide_from_cms_tree = $this->owner->config()->hide_from_cms_tree;
0 ignored issues
show
Documentation introduced by
The property hide_from_cms_tree does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
700
		$staged = $baseClass::get()
701
				->filter('ParentID', (int)$this->owner->ID)
702
				->exclude('ID', (int)$this->owner->ID);
703
		if ($hide_from_hierarchy) {
704
			$staged = $staged->exclude('ClassName', $hide_from_hierarchy);
705
		}
706
		if ($hide_from_cms_tree && $this->showingCMSTree()) {
707
			$staged = $staged->exclude('ClassName', $hide_from_cms_tree);
708
		}
709
		if (!$showAll && $this->owner->db('ShowInMenus')) {
710
			$staged = $staged->filter('ShowInMenus', 1);
711
		}
712
		$this->owner->extend("augmentStageChildren", $staged, $showAll);
713
		return $staged;
714
	}
715
716
	/**
717
	 * Return children in the live site, if it exists.
718
	 *
719
	 * @param bool $showAll              Include all of the elements, even those not shown in the menus. Only
720
	 *                                   applicable when extension is applied to {@link SiteTree}.
721
	 * @param bool $onlyDeletedFromStage Only return items that have been deleted from stage
722
	 * @return DataList
723
	 * @throws Exception
724
	 */
725
	public function liveChildren($showAll = false, $onlyDeletedFromStage = false) {
726
		if(!$this->owner->hasExtension('Versioned')) {
727
			throw new Exception('Hierarchy->liveChildren() only works with Versioned extension applied');
728
		}
729
730
		$baseClass = ClassInfo::baseDataClass($this->owner->class);
731
		$hide_from_hierarchy = $this->owner->config()->hide_from_hierarchy;
0 ignored issues
show
Documentation introduced by
The property hide_from_hierarchy does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
732
		$hide_from_cms_tree = $this->owner->config()->hide_from_cms_tree;
0 ignored issues
show
Documentation introduced by
The property hide_from_cms_tree does not exist on object<Config_ForClass>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
733
		$children = $baseClass::get()
734
			->filter('ParentID', (int)$this->owner->ID)
735
			->exclude('ID', (int)$this->owner->ID)
736
			->setDataQueryParam(array(
737
				'Versioned.mode' => $onlyDeletedFromStage ? 'stage_unique' : 'stage',
738
				'Versioned.stage' => 'Live'
739
			));
740
		if ($hide_from_hierarchy) {
741
			$children = $children->exclude('ClassName', $hide_from_hierarchy);
742
		}
743
		if ($hide_from_cms_tree && $this->showingCMSTree()) {
744
			$children = $children->exclude('ClassName', $hide_from_cms_tree);
745
		}
746
		if(!$showAll && $this->owner->db('ShowInMenus')) $children = $children->filter('ShowInMenus', 1);
747
748
		return $children;
749
	}
750
751
	/**
752
	 * Get this object's parent, optionally filtered by an SQL clause. If the clause doesn't match the parent, nothing
753
	 * is returned.
754
	 *
755
	 * @param string $filter
756
	 * @return DataObject
757
	 */
758
	public function getParent($filter = null) {
759
		if($p = $this->owner->__get("ParentID")) {
760
			$tableClasses = ClassInfo::dataClassesFor($this->owner->class);
761
			$baseClass = array_shift($tableClasses);
762
			return DataObject::get_one($this->owner->class, array(
763
				array("\"$baseClass\".\"ID\"" => $p),
764
				$filter
765
			));
766
		}
767
	}
768
769
	/**
770
	 * Return all the parents of this class in a set ordered from the lowest to highest parent.
771
	 *
772
	 * @return ArrayList
773
	 */
774
	public function getAncestors() {
775
		$ancestors = new ArrayList();
776
		$object    = $this->owner;
777
778
		while($object = $object->getParent()) {
779
			$ancestors->push($object);
780
		}
781
782
		return $ancestors;
783
	}
784
785
	/**
786
	 * Returns a human-readable, flattened representation of the path to the object, using its {@link Title} attribute.
787
	 *
788
	 * @param string $separator
789
	 * @return string
790
	 */
791
	public function getBreadcrumbs($separator = ' &raquo; ') {
792
		$crumbs = array();
793
		$ancestors = array_reverse($this->owner->getAncestors()->toArray());
794
		foreach($ancestors as $ancestor) $crumbs[] = $ancestor->Title;
795
		$crumbs[] = $this->owner->Title;
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<DataObject>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

Since the property has write access only, you can use the @property-write annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
796
		return implode($separator, $crumbs);
797
	}
798
799
	/**
800
	 * Get the next node in the tree of the type. If there is no instance of the className descended from this node,
801
	 * then search the parents.
802
	 *
803
	 * @todo Write!
804
	 *
805
	 * @param string     $className Class name of the node to find
806
	 * @param DataObject $afterNode Used for recursive calls to this function
807
	 * @return DataObject
808
	 */
809
	public function naturalPrev($className, $afterNode = null ) {
0 ignored issues
show
Unused Code introduced by
The parameter $className is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $afterNode is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
810
		return null;
811
	}
812
813
	/**
814
	 * Get the next node in the tree of the type. If there is no instance of the className descended from this node,
815
	 * then search the parents.
816
	 * @param string     $className Class name of the node to find.
817
	 * @param string|int $root      ID/ClassName of the node to limit the search to
818
	 * @param DataObject $afterNode Used for recursive calls to this function
819
	 * @return DataObject
820
	 */
821
	public function naturalNext($className = null, $root = 0, $afterNode = null ) {
822
		// If this node is not the node we are searching from, then we can possibly return this node as a solution
823
		if($afterNode && $afterNode->ID != $this->owner->ID) {
824
			if(!$className || ($className && $this->owner->class == $className)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $className of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
825
				return $this->owner;
826
			}
827
		}
828
829
		$nextNode = null;
830
		$baseClass = ClassInfo::baseDataClass($this->owner->class);
831
832
		$children = $baseClass::get()
833
			->filter('ParentID', (int)$this->owner->ID)
834
			->sort('"Sort"', 'ASC');
835
		if ($afterNode) {
836
			$children = $children->filter('Sort:GreaterThan', $afterNode->Sort);
837
		}
838
839
		// Try all the siblings of this node after the given node
840
		/*if( $siblings = DataObject::get( ClassInfo::baseDataClass($this->owner->class),
0 ignored issues
show
Unused Code Comprehensibility introduced by
52% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
841
		"\"ParentID\"={$this->owner->ParentID}" . ( $afterNode ) ? "\"Sort\"
842
		> {$afterNode->Sort}" : "" , '\"Sort\" ASC' ) ) $searchNodes->merge( $siblings );*/
843
844
		if($children) {
845
			foreach($children as $node) {
846
				if($nextNode = $node->naturalNext($className, $node->ID, $this->owner)) {
847
					break;
848
				}
849
			}
850
851
			if($nextNode) {
852
				return $nextNode;
853
			}
854
		}
855
856
		// if this is not an instance of the root class or has the root id, search the parent
857
		if(!(is_numeric($root) && $root == $this->owner->ID || $root == $this->owner->class)
858
				&& ($parent = $this->owner->Parent())) {
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on DataObject. Did you maybe mean parentClass()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
859
860
			return $parent->naturalNext( $className, $root, $this->owner );
861
		}
862
863
		return null;
864
	}
865
866
	/**
867
	 * Flush all Hierarchy caches:
868
	 * - Children (instance)
869
	 * - NumChildren (instance)
870
	 * - Marked (global)
871
	 * - Expanded (global)
872
	 * - TreeOpened (global)
873
	 */
874
	public function flushCache() {
875
		$this->_cache_children = null;
876
		$this->_cache_numChildren = null;
877
		self::$marked = array();
878
		self::$expanded = array();
879
		self::$treeOpened = array();
880
	}
881
882
	/**
883
	 * Reset global Hierarchy caches:
884
	 * - Marked
885
	 * - Expanded
886
	 * - TreeOpened
887
	 */
888
	public static function reset() {
889
		self::$marked = array();
890
		self::$expanded = array();
891
		self::$treeOpened = array();
892
	}
893
894
}
895