Completed
Pull Request — master (#5741)
by Damian
12:40
created

Hierarchy::isExpanded()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 0
dl 0
loc 5
rs 9.4285
1
<?php
2
3
namespace SilverStripe\ORM\Hierarchy;
4
5
use Config;
6
use Exception;
7
use Controller;
8
use LeftAndMain;
9
use ClassInfo;
10
use SilverStripe\ORM\ValidationResult;
11
use SilverStripe\ORM\ArrayList;
12
use SilverStripe\ORM\DataObject;
13
use SilverStripe\ORM\DataExtension;
14
use SilverStripe\ORM\Versioning\Versioned;
15
16
/**
17
 * DataObjects that use the Hierarchy extension can be be organised as a hierarchy, with children and parents. The most
18
 * obvious example of this is SiteTree.
19
 *
20
 * @package framework
21
 * @subpackage orm
22
 *
23
 * @property int        ParentID
24
 * @property DataObject owner
25
 * @method   DataObject Parent
26
 */
27
class Hierarchy extends DataExtension {
28
29
	protected $markedNodes;
30
31
	protected $markingFilter;
32
33
	/** @var int */
34
	protected $_cache_numChildren;
35
36
	/**
37
	 * The lower bounds for the amount of nodes to mark. If set, the logic will expand nodes until it reaches at least
38
	 * this number, and then stops. Root nodes will always show regardless of this settting. Further nodes can be
39
	 * lazy-loaded via ajax. This isn't a hard limit. Example: On a value of 10, with 20 root nodes, each having 30
40
	 * children, the actual node count will be 50 (all root nodes plus first expanded child).
41
	 *
42
	 * @config
43
	 * @var int
44
	 */
45
	private static $node_threshold_total = 50;
46
47
	/**
48
	 * Limit on the maximum children a specific node can display. Serves as a hard limit to avoid exceeding available
49
	 * server resources in generating the tree, and browser resources in rendering it. Nodes with children exceeding
50
	 * this value typically won't display any children, although this is configurable through the $nodeCountCallback
51
	 * parameter in {@link getChildrenAsUL()}. "Root" nodes will always show all children, regardless of this setting.
52
	 *
53
	 * @config
54
	 * @var int
55
	 */
56
	private static $node_threshold_leaf = 250;
57
58
	/**
59
	 * A list of classnames to exclude from display in both the CMS and front end
60
	 * displays. ->Children() and ->AllChildren affected.
61
	 * Especially useful for big sets of pages like listings
62
	 * If you use this, and still need the classes to be editable
63
	 * then add a model admin for the class
64
	 * Note: Does not filter subclasses (non-inheriting)
65
	 *
66
	 * @var array
67
	 * @config
68
	 */
69
	private static $hide_from_hierarchy = array();
70
71
	/**
72
	 * A list of classnames to exclude from display in the page tree views of the CMS,
73
	 * unlike $hide_from_hierarchy above which effects both CMS and front end.
74
	 * Especially useful for big sets of pages like listings
75
	 * If you use this, and still need the classes to be editable
76
	 * then add a model admin for the class
77
	 * Note: Does not filter subclasses (non-inheriting)
78
	 *
79
	 * @var array
80
	 * @config
81
	 */
82
	private static $hide_from_cms_tree = array();
83
84
	public static function get_extra_config($class, $extension, $args) {
0 ignored issues
show
Unused Code introduced by
The parameter $extension 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 $args 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...
85
		return array(
86
			'has_one' => array('Parent' => $class)
87
		);
88
	}
89
90
	/**
91
	 * Validate the owner object - check for existence of infinite loops.
92
	 *
93
	 * @param ValidationResult $validationResult
94
	 */
95
	public function validate(ValidationResult $validationResult) {
96
		// The object is new, won't be looping.
97
		if (!$this->owner->ID) return;
98
		// The object has no parent, won't be looping.
99
		if (!$this->owner->ParentID) return;
100
		// The parent has not changed, skip the check for performance reasons.
101
		if (!$this->owner->isChanged('ParentID')) return;
102
103
		// Walk the hierarchy upwards until we reach the top, or until we reach the originating node again.
104
		$node = $this->owner;
105
		while($node) {
106
			if ($node->ParentID==$this->owner->ID) {
107
				// Hierarchy is looping.
108
				$validationResult->error(
109
					_t(
110
						'Hierarchy.InfiniteLoopNotAllowed',
111
						'Infinite loop found within the "{type}" hierarchy. Please change the parent to resolve this',
112
						'First argument is the class that makes up the hierarchy.',
113
						array('type' => $this->owner->class)
114
					),
115
					'INFINITE_LOOP'
116
				);
117
				break;
118
			}
119
			$node = $node->ParentID ? $node->Parent() : null;
120
		}
121
122
		// At this point the $validationResult contains the response.
123
	}
124
125
	/**
126
	 * Returns the children of this DataObject as an XHTML UL. This will be called recursively on each child, so if they
127
	 * have children they will be displayed as a UL inside a LI.
128
	 *
129
	 * @param string          $attributes         Attributes to add to the UL
130
	 * @param string|callable $titleEval          PHP code to evaluate to start each child - this should include '<li>'
131
	 * @param string          $extraArg           Extra arguments that will be passed on to children, for if they
132
	 *                                            overload this function
133
	 * @param bool            $limitToMarked      Display only marked children
134
	 * @param string          $childrenMethod     The name of the method used to get children from each object
135
	 * @param bool            $rootCall           Set to true for this first call, and then to false for calls inside
136
	 *                                            the recursion. You should not change this.
137
	 * @param int             $nodeCountThreshold See {@link self::$node_threshold_total}
138
	 * @param callable        $nodeCountCallback  Called with the node count, which gives the callback an opportunity to
139
	 *                                            intercept the query. Useful e.g. to avoid excessive children listings
140
	 *                                            (Arguments: $parent, $numChildren)
141
	 *
142
	 * @return string
143
	 */
144
	public function getChildrenAsUL($attributes = "", $titleEval = '"<li>" . $child->Title', $extraArg = null,
145
			$limitToMarked = false, $childrenMethod = "AllChildrenIncludingDeleted",
146
			$numChildrenMethod = "numChildren", $rootCall = true,
147
			$nodeCountThreshold = null, $nodeCountCallback = null) {
148
149
		if(!is_numeric($nodeCountThreshold)) {
150
			$nodeCountThreshold = Config::inst()->get('SilverStripe\ORM\Hierarchy\Hierarchy', 'node_threshold_total');
151
		}
152
153
		if($limitToMarked && $rootCall) {
154
			$this->markingFinished($numChildrenMethod);
155
		}
156
157
158
		if($nodeCountCallback) {
159
			$nodeCountWarning = $nodeCountCallback($this->owner, $this->owner->$numChildrenMethod());
160
			if($nodeCountWarning) return $nodeCountWarning;
161
		}
162
163
164
		if($this->owner->hasMethod($childrenMethod)) {
165
			$children = $this->owner->$childrenMethod($extraArg);
166
		} else {
167
			user_error(sprintf("Can't find the method '%s' on class '%s' for getting tree children",
168
				$childrenMethod, get_class($this->owner)), E_USER_ERROR);
169
		}
170
171
		if($children) {
172
173
			if($attributes) {
174
				$attributes = " $attributes";
175
			}
176
177
			$output = "<ul$attributes>\n";
178
179
			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...
180
				if(!$limitToMarked || $child->isMarked()) {
181
					$foundAChild = true;
182
					if(is_callable($titleEval)) {
183
						$output .= $titleEval($child, $numChildrenMethod);
184
					} else {
185
						$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...
186
					}
187
					$output .= "\n";
188
189
					$numChildren = $child->$numChildrenMethod();
190
191
					if(
192
						// Always traverse into opened nodes (they might be exposed as parents of search results)
193
						$child->isExpanded()
194
						// Only traverse into children if we haven't reached the maximum node count already.
195
						// Otherwise, the remaining nodes are lazy loaded via ajax.
196
						&& $child->isMarked()
197
					) {
198
						// Additionally check if node count requirements are met
199
						$nodeCountWarning = $nodeCountCallback ? $nodeCountCallback($child, $numChildren) : null;
200
						if($nodeCountWarning) {
201
							$output .= $nodeCountWarning;
202
							$child->markClosed();
203
						} else {
204
							$output .= $child->getChildrenAsUL("", $titleEval, $extraArg, $limitToMarked,
205
								$childrenMethod,	$numChildrenMethod, false, $nodeCountThreshold);
206
						}
207
					} elseif($child->isTreeOpened()) {
208
						// Since we're not loading children, don't mark it as open either
209
						$child->markClosed();
210
					}
211
					$output .= "</li>\n";
212
				}
213
			}
214
215
			$output .= "</ul>\n";
216
		}
217
218
		if(isset($foundAChild) && $foundAChild) {
219
			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...
220
		}
221
	}
222
223
	/**
224
	 * Mark a segment of the tree, by calling mark().
225
	 *
226
	 * The method performs a breadth-first traversal until the number of nodes is more than minCount. This is used to
227
	 * get a limited number of tree nodes to show in the CMS initially.
228
	 *
229
	 * This method returns the number of nodes marked.  After this method is called other methods can check
230
	 * {@link isExpanded()} and {@link isMarked()} on individual nodes.
231
	 *
232
	 * @param int $nodeCountThreshold See {@link getChildrenAsUL()}
233
	 * @return int The actual number of nodes marked.
234
	 */
235
	public function markPartialTree($nodeCountThreshold = 30, $context = null,
236
			$childrenMethod = "AllChildrenIncludingDeleted", $numChildrenMethod = "numChildren") {
237
238
		if(!is_numeric($nodeCountThreshold)) $nodeCountThreshold = 30;
239
240
		$this->markedNodes = array($this->owner->ID => $this->owner);
241
		$this->owner->markUnexpanded();
242
243
		// foreach can't handle an ever-growing $nodes list
244
		while(list($id, $node) = each($this->markedNodes)) {
0 ignored issues
show
Unused Code introduced by
The assignment to $id is unused. Consider omitting it like so list($first,,$third).

This checks looks for assignemnts to variables using the list(...) function, where not all assigned variables are subsequently used.

Consider the following code example.

<?php

function returnThreeValues() {
    return array('a', 'b', 'c');
}

list($a, $b, $c) = returnThreeValues();

print $a . " - " . $c;

Only the variables $a and $c are used. There was no need to assign $b.

Instead, the list call could have been.

list($a,, $c) = returnThreeValues();
Loading history...
245
			$children = $this->markChildren($node, $context, $childrenMethod, $numChildrenMethod);
246
			if($nodeCountThreshold && sizeof($this->markedNodes) > $nodeCountThreshold) {
247
				// Undo marking children as opened since they're lazy loaded
248
				if($children) foreach($children as $child) $child->markClosed();
249
				break;
250
			}
251
		}
252
		return sizeof($this->markedNodes);
253
	}
254
255
	/**
256
	 * Filter the marking to only those object with $node->$parameterName == $parameterValue
257
	 *
258
	 * @param string $parameterName  The parameter on each node to check when marking.
259
	 * @param mixed  $parameterValue The value the parameter must be to be marked.
260
	 */
261
	public function setMarkingFilter($parameterName, $parameterValue) {
262
		$this->markingFilter = array(
263
			"parameter" => $parameterName,
264
			"value" => $parameterValue
265
		);
266
	}
267
268
	/**
269
	 * Filter the marking to only those where the function returns true. The node in question will be passed to the
270
	 * function.
271
	 *
272
	 * @param string $funcName The name of the function to call
273
	 */
274
	public function setMarkingFilterFunction($funcName) {
275
		$this->markingFilter = array(
276
			"func" => $funcName,
277
		);
278
	}
279
280
	/**
281
	 * Returns true if the marking filter matches on the given node.
282
	 *
283
	 * @param DataObject $node Node to check
284
	 * @return bool
285
	 */
286
	public function markingFilterMatches($node) {
287
		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...
288
			return true;
289
		}
290
291
		if(isset($this->markingFilter['parameter']) && $parameterName = $this->markingFilter['parameter']) {
292
			if(is_array($this->markingFilter['value'])){
293
				$ret = false;
294
				foreach($this->markingFilter['value'] as $value) {
295
					$ret = $ret||$node->$parameterName==$value;
296
					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...
297
						break;
298
					}
299
				}
300
				return $ret;
301
			} else {
302
				return ($node->$parameterName == $this->markingFilter['value']);
303
			}
304
		} else if ($func = $this->markingFilter['func']) {
305
			return call_user_func($func, $node);
306
		}
307
	}
308
309
	/**
310
	 * Mark all children of the given node that match the marking filter.
311
	 *
312
	 * @param DataObject $node              Parent node
313
	 * @param mixed      $context
314
	 * @param string     $childrenMethod    The name of the instance method to call to get the object's list of children
315
	 * @param string     $numChildrenMethod The name of the instance method to call to count the object's children
316
	 * @return DataList
317
	 */
318
	public function markChildren($node, $context = null, $childrenMethod = "AllChildrenIncludingDeleted",
319
			$numChildrenMethod = "numChildren") {
320
		if($node->hasMethod($childrenMethod)) {
321
			$children = $node->$childrenMethod($context);
322
		} else {
323
			user_error(sprintf("Can't find the method '%s' on class '%s' for getting tree children",
324
				$childrenMethod, get_class($node)), E_USER_ERROR);
325
		}
326
327
		$node->markExpanded();
328
		if($children) {
329
			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...
330
				$markingMatches = $this->markingFilterMatches($child);
331
				if($markingMatches) {
332
					if($child->$numChildrenMethod()) {
333
						$child->markUnexpanded();
334
					} else {
335
						$child->markExpanded();
336
					}
337
					$this->markedNodes[$child->ID] = $child;
338
				}
339
			}
340
		}
341
342
		return $children;
343
	}
344
345
	/**
346
	 * Ensure marked nodes that have children are also marked expanded. Call this after marking but before iterating
347
	 * over the tree.
348
	 *
349
	 * @param string $numChildrenMethod The name of the instance method to call to count the object's children
350
	 */
351
	protected function markingFinished($numChildrenMethod = "numChildren") {
352
		// Mark childless nodes as expanded.
353
		if($this->markedNodes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->markedNodes of type SilverStripe\ORM\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...
354
			foreach($this->markedNodes as $id => $node) {
355
				if(!$node->isExpanded() && !$node->$numChildrenMethod()) {
356
					$node->markExpanded();
357
				}
358
			}
359
		}
360
	}
361
362
	/**
363
	 * Return CSS classes of 'unexpanded', 'closed', both, or neither, as well as a 'jstree-*' state depending on the
364
	 * marking of this DataObject.
365
	 *
366
	 * @param string $numChildrenMethod The name of the instance method to call to count the object's children
367
	 * @return string
368
	 */
369
	public function markingClasses($numChildrenMethod="numChildren") {
370
		$classes = '';
371
		if(!$this->isExpanded()) {
372
			$classes .= " unexpanded";
373
		}
374
375
		// Set jstree open state, or mark it as a leaf (closed) if there are no children
376
		if(!$this->owner->$numChildrenMethod()) {
377
			$classes .= " jstree-leaf closed";
378
		} elseif($this->isTreeOpened()) {
379
			$classes .= " jstree-open";
380
		} else {
381
			$classes .= " jstree-closed closed";
382
		}
383
		return $classes;
384
	}
385
386
	/**
387
	 * Mark the children of the DataObject with the given ID.
388
	 *
389
	 * @param int  $id   ID of parent node
390
	 * @param bool $open If this is true, mark the parent node as opened
391
	 * @return bool
392
	 */
393
	public function markById($id, $open = false) {
394
		if(isset($this->markedNodes[$id])) {
395
			$this->markChildren($this->markedNodes[$id]);
396
			if($open) {
397
				$this->markedNodes[$id]->markOpened();
398
			}
399
			return true;
400
		} else {
401
			return false;
402
		}
403
	}
404
405
	/**
406
	 * Expose the given object in the tree, by marking this page and all it ancestors.
407
	 *
408
	 * @param DataObject $childObj
409
	 */
410
	public function markToExpose($childObj) {
411
		if(is_object($childObj)){
412
			$stack = array_reverse($childObj->parentStack());
413
			foreach($stack as $stackItem) {
414
				$this->markById($stackItem->ID, true);
415
			}
416
		}
417
	}
418
419
	/**
420
	 * Return the IDs of all the marked nodes.
421
	 *
422
	 * @return array
423
	 */
424
	public function markedNodeIDs() {
425
		return array_keys($this->markedNodes);
426
	}
427
428
	/**
429
	 * Return an array of this page and its ancestors, ordered item -> root.
430
	 *
431
	 * @return SiteTree[]
432
	 */
433
	public function parentStack() {
434
		$p = $this->owner;
435
436
		while($p) {
437
			$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...
438
			$p = $p->ParentID ? $p->Parent() : null;
439
		}
440
441
		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...
442
	}
443
444
	/**
445
	 * Cache of DataObjects' marked statuses: [ClassName][ID] = bool
446
	 * @var array
447
	 */
448
	protected static $marked = array();
449
450
	/**
451
	 * Cache of DataObjects' expanded statuses: [ClassName][ID] = bool
452
	 * @var array
453
	 */
454
	protected static $expanded = array();
455
456
	/**
457
	 * Cache of DataObjects' opened statuses: [ClassName][ID] = bool
458
	 * @var array
459
	 */
460
	protected static $treeOpened = array();
461
462
	/**
463
	 * Mark this DataObject as expanded.
464
	 */
465
	public function markExpanded() {
466
		self::$marked[$this->owner->baseClass()][$this->owner->ID] = true;
467
		self::$expanded[$this->owner->baseClass()][$this->owner->ID] = true;
468
	}
469
470
	/**
471
	 * Mark this DataObject as unexpanded.
472
	 */
473
	public function markUnexpanded() {
474
		self::$marked[$this->owner->baseClass()][$this->owner->ID] = true;
475
		self::$expanded[$this->owner->baseClass()][$this->owner->ID] = false;
476
	}
477
478
	/**
479
	 * Mark this DataObject's tree as opened.
480
	 */
481
	public function markOpened() {
482
		self::$marked[$this->owner->baseClass()][$this->owner->ID] = true;
483
		self::$treeOpened[$this->owner->baseClass()][$this->owner->ID] = true;
484
	}
485
486
	/**
487
	 * Mark this DataObject's tree as closed.
488
	 */
489
	public function markClosed() {
490
		if(isset(self::$treeOpened[$this->owner->baseClass()][$this->owner->ID])) {
491
			unset(self::$treeOpened[$this->owner->baseClass()][$this->owner->ID]);
492
		}
493
	}
494
495
	/**
496
	 * Check if this DataObject is marked.
497
	 *
498
	 * @return bool
499
	 */
500
	public function isMarked() {
501
		$baseClass = $this->owner->baseClass();
502
		$id = $this->owner->ID;
503
		return isset(self::$marked[$baseClass][$id]) ? self::$marked[$baseClass][$id] : false;
504
	}
505
506
	/**
507
	 * Check if this DataObject is expanded.
508
	 *
509
	 * @return bool
510
	 */
511
	public function isExpanded() {
512
		$baseClass = $this->owner->baseClass();
513
		$id = $this->owner->ID;
514
		return isset(self::$expanded[$baseClass][$id]) ? self::$expanded[$baseClass][$id] : false;
515
	}
516
517
	/**
518
	 * Check if this DataObject's tree is opened.
519
	 *
520
	 * @return bool
521
	 */
522
	public function isTreeOpened() {
523
		$baseClass = $this->owner->baseClass();
524
		$id = $this->owner->ID;
525
		return isset(self::$treeOpened[$baseClass][$id]) ? self::$treeOpened[$baseClass][$id] : false;
526
	}
527
528
	/**
529
	 * Get a list of this DataObject's and all it's descendants IDs.
530
	 *
531
	 * @return int[]
532
	 */
533
	public function getDescendantIDList() {
534
		$idList = array();
535
		$this->loadDescendantIDListInto($idList);
536
		return $idList;
537
	}
538
539
	/**
540
	 * Get a list of this DataObject's and all it's descendants ID, and put them in $idList.
541
	 *
542
	 * @param array $idList Array to put results in.
543
	 */
544
	public function loadDescendantIDListInto(&$idList) {
545
		if($children = $this->AllChildren()) {
546
			foreach($children as $child) {
547
				if(in_array($child->ID, $idList)) {
548
					continue;
549
				}
550
				$idList[] = $child->ID;
551
				$ext = $child->getExtensionInstance('SilverStripe\ORM\Hierarchy\Hierarchy');
552
				$ext->setOwner($child);
553
				$ext->loadDescendantIDListInto($idList);
554
				$ext->clearOwner();
555
			}
556
		}
557
	}
558
559
	/**
560
	 * Get the children for this DataObject.
561
	 *
562
	 * @return DataList
563
	 */
564
	public function Children() {
565
		if(!(isset($this->_cache_children) && $this->_cache_children)) {
566
			$result = $this->owner->stageChildren(false);
567
			$children = array();
568
			foreach ($result as $record) {
569
				if ($record->canView()) {
570
					$children[] = $record;
571
				}
572
			}
573
			$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...
574
		}
575
		return $this->_cache_children;
576
	}
577
578
	/**
579
	 * Return all children, including those 'not in menus'.
580
	 *
581
	 * @return DataList
582
	 */
583
	public function AllChildren() {
584
		return $this->owner->stageChildren(true);
585
	}
586
587
	/**
588
	 * Return all children, including those that have been deleted but are still in live.
589
	 * - Deleted children will be marked as "DeletedFromStage"
590
	 * - Added children will be marked as "AddedToStage"
591
	 * - Modified children will be marked as "ModifiedOnStage"
592
	 * - Everything else has "SameOnStage" set, as an indicator that this information has been looked up.
593
	 *
594
	 * @param mixed $context
595
	 * @return ArrayList
596
	 */
597
	public function AllChildrenIncludingDeleted($context = null) {
598
		return $this->doAllChildrenIncludingDeleted($context);
599
	}
600
601
	/**
602
	 * @see AllChildrenIncludingDeleted
603
	 *
604
	 * @param mixed $context
605
	 * @return ArrayList
606
	 */
607
	public function doAllChildrenIncludingDeleted($context = null) {
608
		if(!$this->owner) user_error('Hierarchy::doAllChildrenIncludingDeleted() called without $this->owner');
609
610
		$baseClass = $this->owner->baseClass();
611
		if($baseClass) {
612
			$stageChildren = $this->owner->stageChildren(true);
613
614
			// Add live site content that doesn't exist on the stage site, if required.
615
			if($this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
616
				// Next, go through the live children.  Only some of these will be listed
617
				$liveChildren = $this->owner->liveChildren(true, true);
618
				if($liveChildren) {
619
					$merged = new ArrayList();
620
					$merged->merge($stageChildren);
621
					$merged->merge($liveChildren);
622
					$stageChildren = $merged;
623
				}
624
			}
625
626
			$this->owner->extend("augmentAllChildrenIncludingDeleted", $stageChildren, $context);
627
628
		} else {
629
			user_error("Hierarchy::AllChildren() Couldn't determine base class for '{$this->owner->class}'",
630
				E_USER_ERROR);
631
		}
632
633
		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...
634
	}
635
636
	/**
637
	 * Return all the children that this page had, including pages that were deleted from both stage & live.
638
	 *
639
	 * @return DataList
640
	 * @throws Exception
641
	 */
642
	public function AllHistoricalChildren() {
643
		if(!$this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
644
			throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
645
		}
646
647
		$baseTable = $this->owner->baseTable();
648
		$parentIDColumn = $this->owner->getSchema()->sqlColumnForField($this->owner, 'ParentID');
649
		return Versioned::get_including_deleted(
650
			$this->owner->baseClass(),
651
			[ $parentIDColumn => $this->owner->ID ],
652
			"\"{$baseTable}\".\"ID\" ASC"
653
		);
654
	}
655
656
	/**
657
	 * Return the number of children that this page ever had, including pages that were deleted.
658
	 *
659
	 * @return int
660
	 * @throws Exception
661
	 */
662
	public function numHistoricalChildren() {
663
		if(!$this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
664
			throw new Exception('Hierarchy->AllHistoricalChildren() only works with Versioned extension applied');
665
		}
666
667
		return $this->AllHistoricalChildren()->count();
668
	}
669
670
	/**
671
	 * Return the number of direct children. By default, values are cached after the first invocation. Can be
672
	 * augumented by {@link augmentNumChildrenCountQuery()}.
673
	 *
674
	 * @param bool $cache Whether to retrieve values from cache
675
	 * @return int
676
	 */
677
	public function numChildren($cache = true) {
678
		// Build the cache for this class if it doesn't exist.
679
		if(!$cache || !is_numeric($this->_cache_numChildren)) {
680
			// Hey, this is efficient now!
681
			// We call stageChildren(), because Children() has canView() filtering
682
			$this->_cache_numChildren = (int)$this->owner->stageChildren(true)->Count();
683
		}
684
685
		// If theres no value in the cache, it just means that it doesn't have any children.
686
		return $this->_cache_numChildren;
687
	}
688
689
	/**
690
	 * Checks if we're on a controller where we should filter. ie. Are we loading the SiteTree?
691
	 *
692
	 * @return bool
693
	 */
694
	public function showingCMSTree() {
695
		if (!Controller::has_curr()) return false;
696
		$controller = Controller::curr();
697
		return $controller instanceof LeftAndMain
698
			&& in_array($controller->getAction(), array("treeview", "listview", "getsubtree"));
699
	}
700
701
	/**
702
	 * Return children in the stage site.
703
	 *
704
	 * @param bool $showAll Include all of the elements, even those not shown in the menus. Only applicable when
705
	 *                      extension is applied to {@link SiteTree}.
706
	 * @return DataList
707
	 */
708
	public function stageChildren($showAll = false) {
709
		$baseClass = $this->owner->baseClass();
710
		$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...
711
		$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...
712
		$staged = $baseClass::get()
713
				->filter('ParentID', (int)$this->owner->ID)
714
				->exclude('ID', (int)$this->owner->ID);
715
		if ($hide_from_hierarchy) {
716
			$staged = $staged->exclude('ClassName', $hide_from_hierarchy);
717
		}
718
		if ($hide_from_cms_tree && $this->showingCMSTree()) {
719
			$staged = $staged->exclude('ClassName', $hide_from_cms_tree);
720
		}
721
		if (!$showAll && $this->owner->db('ShowInMenus')) {
722
			$staged = $staged->filter('ShowInMenus', 1);
723
		}
724
		$this->owner->extend("augmentStageChildren", $staged, $showAll);
725
		return $staged;
726
	}
727
728
	/**
729
	 * Return children in the live site, if it exists.
730
	 *
731
	 * @param bool $showAll              Include all of the elements, even those not shown in the menus. Only
732
	 *                                   applicable when extension is applied to {@link SiteTree}.
733
	 * @param bool $onlyDeletedFromStage Only return items that have been deleted from stage
734
	 * @return DataList
735
	 * @throws Exception
736
	 */
737
	public function liveChildren($showAll = false, $onlyDeletedFromStage = false) {
738
		if(!$this->owner->hasExtension('SilverStripe\ORM\Versioning\Versioned')) {
739
			throw new Exception('Hierarchy->liveChildren() only works with Versioned extension applied');
740
		}
741
742
		$baseClass = $this->owner->baseClass();
743
		$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...
744
		$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...
745
		$children = $baseClass::get()
746
			->filter('ParentID', (int)$this->owner->ID)
747
			->exclude('ID', (int)$this->owner->ID)
748
			->setDataQueryParam(array(
749
				'Versioned.mode' => $onlyDeletedFromStage ? 'stage_unique' : 'stage',
750
				'Versioned.stage' => 'Live'
751
			));
752
		if ($hide_from_hierarchy) {
753
			$children = $children->exclude('ClassName', $hide_from_hierarchy);
754
		}
755
		if ($hide_from_cms_tree && $this->showingCMSTree()) {
756
			$children = $children->exclude('ClassName', $hide_from_cms_tree);
757
		}
758
		if(!$showAll && $this->owner->db('ShowInMenus')) $children = $children->filter('ShowInMenus', 1);
759
760
		return $children;
761
	}
762
763
	/**
764
	 * Get this object's parent, optionally filtered by an SQL clause. If the clause doesn't match the parent, nothing
765
	 * is returned.
766
	 *
767
	 * @param string $filter
768
	 * @return DataObject
769
	 */
770
	public function getParent($filter = null) {
771
		$parentID = $this->owner->ParentID;
0 ignored issues
show
Documentation introduced by
The property ParentID does not exist on object<SilverStripe\ORM\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...
772
		if(empty($parentID)) {
773
			return null;
774
		}
775
		$idSQL = $this->owner->getSchema()->sqlColumnForField($this->owner, 'ID');
776
		return DataObject::get_one($this->owner->class, array(
777
			array($idSQL => $parentID),
778
			$filter
779
		));
780
	}
781
782
	/**
783
	 * Return all the parents of this class in a set ordered from the lowest to highest parent.
784
	 *
785
	 * @return ArrayList
786
	 */
787
	public function getAncestors() {
788
		$ancestors = new ArrayList();
789
		$object    = $this->owner;
790
791
		while($object = $object->getParent()) {
792
			$ancestors->push($object);
793
		}
794
795
		return $ancestors;
796
	}
797
798
	/**
799
	 * Returns a human-readable, flattened representation of the path to the object, using its {@link Title} attribute.
800
	 *
801
	 * @param string $separator
802
	 * @return string
803
	 */
804
	public function getBreadcrumbs($separator = ' &raquo; ') {
805
		$crumbs = array();
806
		$ancestors = array_reverse($this->owner->getAncestors()->toArray());
807
		foreach($ancestors as $ancestor) $crumbs[] = $ancestor->Title;
808
		$crumbs[] = $this->owner->Title;
0 ignored issues
show
Documentation introduced by
The property Title does not exist on object<SilverStripe\ORM\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...
809
		return implode($separator, $crumbs);
810
	}
811
812
	/**
813
	 * Get the next node in the tree of the type. If there is no instance of the className descended from this node,
814
	 * then search the parents.
815
	 *
816
	 * @todo Write!
817
	 *
818
	 * @param string     $className Class name of the node to find
819
	 * @param DataObject $afterNode Used for recursive calls to this function
820
	 * @return DataObject
821
	 */
822
	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...
823
		return null;
824
	}
825
826
	/**
827
	 * Get the next node in the tree of the type. If there is no instance of the className descended from this node,
828
	 * then search the parents.
829
	 * @param string     $className Class name of the node to find.
830
	 * @param string|int $root      ID/ClassName of the node to limit the search to
831
	 * @param DataObject $afterNode Used for recursive calls to this function
832
	 * @return DataObject
833
	 */
834
	public function naturalNext($className = null, $root = 0, $afterNode = null ) {
835
		// If this node is not the node we are searching from, then we can possibly return this node as a solution
836
		if($afterNode && $afterNode->ID != $this->owner->ID) {
837
			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...
838
				return $this->owner;
839
			}
840
		}
841
842
		$nextNode = null;
843
		$baseClass = $this->owner->baseClass();
844
845
		$children = $baseClass::get()
846
			->filter('ParentID', (int)$this->owner->ID)
847
			->sort('"Sort"', 'ASC');
848
		if ($afterNode) {
849
			$children = $children->filter('Sort:GreaterThan', $afterNode->Sort);
850
		}
851
852
		// Try all the siblings of this node after the given node
853
		/*if( $siblings = DataObject::get( $this->owner->baseClass(),
0 ignored issues
show
Unused Code Comprehensibility introduced by
53% 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...
854
		"\"ParentID\"={$this->owner->ParentID}" . ( $afterNode ) ? "\"Sort\"
855
		> {$afterNode->Sort}" : "" , '\"Sort\" ASC' ) ) $searchNodes->merge( $siblings );*/
856
857
		if($children) {
858
			foreach($children as $node) {
859
				if($nextNode = $node->naturalNext($className, $node->ID, $this->owner)) {
860
					break;
861
				}
862
			}
863
864
			if($nextNode) {
865
				return $nextNode;
866
			}
867
		}
868
869
		// if this is not an instance of the root class or has the root id, search the parent
870
		if(!(is_numeric($root) && $root == $this->owner->ID || $root == $this->owner->class)
871
				&& ($parent = $this->owner->Parent())) {
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on SilverStripe\ORM\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...
872
873
			return $parent->naturalNext( $className, $root, $this->owner );
874
		}
875
876
		return null;
877
	}
878
879
	/**
880
	 * Flush all Hierarchy caches:
881
	 * - Children (instance)
882
	 * - NumChildren (instance)
883
	 * - Marked (global)
884
	 * - Expanded (global)
885
	 * - TreeOpened (global)
886
	 */
887
	public function flushCache() {
888
		$this->_cache_children = null;
889
		$this->_cache_numChildren = null;
890
		self::$marked = array();
891
		self::$expanded = array();
892
		self::$treeOpened = array();
893
	}
894
895
	/**
896
	 * Reset global Hierarchy caches:
897
	 * - Marked
898
	 * - Expanded
899
	 * - TreeOpened
900
	 */
901
	public static function reset() {
902
		self::$marked = array();
903
		self::$expanded = array();
904
		self::$treeOpened = array();
905
	}
906
907
}
908