Passed
Pull Request — 4 (#8843)
by Daniel
08:11
created

Hierarchy   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 419
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 141
dl 0
loc 419
rs 6
c 0
b 0
f 0
wmc 55

17 Methods

Rating   Name   Duplication   Size   Complexity  
A get_extra_config() 0 4 1
A AllChildren() 0 3 1
A flushCache() 0 4 1
A numHistoricalChildren() 0 3 1
A getParent() 0 11 2
B liveChildren() 0 28 9
A Children() 0 15 2
A getBreadcrumbs() 0 10 2
B validate() 0 35 7
A AllHistoricalChildren() 0 16 3
A getAncestors() 0 13 3
A numChildren() 0 18 4
A loadDescendantIDListInto() 0 10 4
A getDescendantIDList() 0 5 1
A showingCMSTree() 0 8 4
A AllChildrenIncludingDeleted() 0 19 4
A stageChildren() 0 19 6

How to fix   Complexity   

Complex Class

Complex classes like Hierarchy often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Hierarchy, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\ORM\Hierarchy;
4
5
use SilverStripe\Admin\LeftAndMain;
0 ignored issues
show
Bug introduced by
The type SilverStripe\Admin\LeftAndMain was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
6
use SilverStripe\Control\Controller;
7
use SilverStripe\ORM\DataList;
8
use SilverStripe\ORM\SS_List;
9
use SilverStripe\ORM\ValidationResult;
10
use SilverStripe\ORM\ArrayList;
11
use SilverStripe\ORM\DataObject;
12
use SilverStripe\ORM\DataExtension;
13
use SilverStripe\Versioned\Versioned;
14
use Exception;
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
 * @property int $ParentID
21
 * @property DataObject|Hierarchy $owner
22
 * @method DataObject Parent()
23
 */
24
class Hierarchy extends DataExtension
25
{
26
    /**
27
     * The lower bounds for the amount of nodes to mark. If set, the logic will expand nodes until it reaches at least
28
     * this number, and then stops. Root nodes will always show regardless of this settting. Further nodes can be
29
     * lazy-loaded via ajax. This isn't a hard limit. Example: On a value of 10, with 20 root nodes, each having 30
30
     * children, the actual node count will be 50 (all root nodes plus first expanded child).
31
     *
32
     * @config
33
     * @var int
34
     */
35
    private static $node_threshold_total = 50;
0 ignored issues
show
introduced by
The private property $node_threshold_total is not used, and could be removed.
Loading history...
36
37
    /**
38
     * Limit on the maximum children a specific node can display. Serves as a hard limit to avoid exceeding available
39
     * server resources in generating the tree, and browser resources in rendering it. Nodes with children exceeding
40
     * this value typically won't display any children, although this is configurable through the $nodeCountCallback
41
     * parameter in {@link getChildrenAsUL()}. "Root" nodes will always show all children, regardless of this setting.
42
     *
43
     * @config
44
     * @var int
45
     */
46
    private static $node_threshold_leaf = 250;
0 ignored issues
show
introduced by
The private property $node_threshold_leaf is not used, and could be removed.
Loading history...
47
48
    /**
49
     * A list of classnames to exclude from display in both the CMS and front end
50
     * displays. ->Children() and ->AllChildren affected.
51
     * Especially useful for big sets of pages like listings
52
     * If you use this, and still need the classes to be editable
53
     * then add a model admin for the class
54
     * Note: Does not filter subclasses (non-inheriting)
55
     *
56
     * @var array
57
     * @config
58
     */
59
    private static $hide_from_hierarchy = array();
60
61
    /**
62
     * A list of classnames to exclude from display in the page tree views of the CMS,
63
     * unlike $hide_from_hierarchy above which effects both CMS and front end.
64
     * Especially useful for big sets of pages like listings
65
     * If you use this, and still need the classes to be editable
66
     * then add a model admin for the class
67
     * Note: Does not filter subclasses (non-inheriting)
68
     *
69
     * @var array
70
     * @config
71
     */
72
    private static $hide_from_cms_tree = array();
73
74
    /**
75
     * Prevent virtual page virtualising these fields
76
     *
77
     * @config
78
     * @var array
79
     */
80
    private static $non_virtual_fields = [
0 ignored issues
show
introduced by
The private property $non_virtual_fields is not used, and could be removed.
Loading history...
81
        '_cache_children',
82
        '_cache_numChildren',
83
    ];
84
85
    public static function get_extra_config($class, $extension, $args)
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

85
    public static function get_extra_config($class, $extension, /** @scrutinizer ignore-unused */ $args)

This check looks for 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 $extension is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

85
    public static function get_extra_config($class, /** @scrutinizer ignore-unused */ $extension, $args)

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

Loading history...
86
    {
87
        return array(
88
            'has_one' => array('Parent' => $class)
89
        );
90
    }
91
92
    /**
93
     * Validate the owner object - check for existence of infinite loops.
94
     *
95
     * @param ValidationResult $validationResult
96
     */
97
    public function validate(ValidationResult $validationResult)
98
    {
99
        // The object is new, won't be looping.
100
        /** @var DataObject|Hierarchy $owner */
101
        $owner = $this->owner;
102
        if (!$owner->ID) {
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. Did you maybe forget to declare it?
Loading history...
103
            return;
104
        }
105
        // The object has no parent, won't be looping.
106
        if (!$owner->ParentID) {
0 ignored issues
show
Bug Best Practice introduced by
The property ParentID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
107
            return;
108
        }
109
        // The parent has not changed, skip the check for performance reasons.
110
        if (!$owner->isChanged('ParentID')) {
0 ignored issues
show
Bug introduced by
The method isChanged() does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

110
        if (!$owner->/** @scrutinizer ignore-call */ isChanged('ParentID')) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
111
            return;
112
        }
113
114
        // Walk the hierarchy upwards until we reach the top, or until we reach the originating node again.
115
        $node = $owner;
116
        while ($node && $node->ParentID) {
117
            if ((int)$node->ParentID === (int)$owner->ID) {
118
                // Hierarchy is looping.
119
                $validationResult->addError(
120
                    _t(
121
                        __CLASS__ . '.InfiniteLoopNotAllowed',
122
                        'Infinite loop found within the "{type}" hierarchy. Please change the parent to resolve this',
123
                        'First argument is the class that makes up the hierarchy.',
124
                        array('type' => get_class($owner))
125
                    ),
126
                    'bad',
127
                    'INFINITE_LOOP'
128
                );
129
                break;
130
            }
131
            $node = $node->Parent();
0 ignored issues
show
Bug introduced by
The method Parent() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

131
            /** @scrutinizer ignore-call */ 
132
            $node = $node->Parent();
Loading history...
132
        }
133
    }
134
135
136
    /**
137
     * Get a list of this DataObject's and all it's descendants IDs.
138
     *
139
     * @return int[]
140
     */
141
    public function getDescendantIDList()
142
    {
143
        $idList = array();
144
        $this->loadDescendantIDListInto($idList);
145
        return $idList;
146
    }
147
148
    /**
149
     * Get a list of this DataObject's and all it's descendants ID, and put them in $idList.
150
     *
151
     * @param array $idList Array to put results in.
152
     * @param DataObject|Hierarchy $node
153
     */
154
    protected function loadDescendantIDListInto(&$idList, $node = null)
155
    {
156
        if (!$node) {
157
            $node = $this->owner;
158
        }
159
        $children = $node->AllChildren();
0 ignored issues
show
Bug introduced by
The method AllChildren() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

159
        /** @scrutinizer ignore-call */ 
160
        $children = $node->AllChildren();
Loading history...
160
        foreach ($children as $child) {
161
            if (!in_array($child->ID, $idList)) {
162
                $idList[] = $child->ID;
163
                $this->loadDescendantIDListInto($idList, $child);
164
            }
165
        }
166
    }
167
168
    /**
169
     * Get the children for this DataObject filtered by canView()
170
     *
171
     * @return SS_List
172
     */
173
    public function Children()
174
    {
175
        $children = $this->owner->_cache_children;
0 ignored issues
show
Bug Best Practice introduced by
The property _cache_children does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
176
        if ($children) {
177
            return $children;
178
        }
179
180
        $children = $this
181
            ->owner
182
            ->stageChildren(false)
0 ignored issues
show
Bug introduced by
The method stageChildren() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

182
            ->/** @scrutinizer ignore-call */ stageChildren(false)
Loading history...
183
            ->filterByCallback(function (DataObject $record) {
184
                return $record->canView();
185
            });
186
        $this->owner->_cache_children = $children;
0 ignored issues
show
Bug Best Practice introduced by
The property _cache_children does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property _cache_children does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
187
        return $children;
188
    }
189
190
    /**
191
     * Return all children, including those 'not in menus'.
192
     *
193
     * @return DataList
194
     */
195
    public function AllChildren()
196
    {
197
        return $this->owner->stageChildren(true);
198
    }
199
200
    /**
201
     * Return all children, including those that have been deleted but are still in live.
202
     * - Deleted children will be marked as "DeletedFromStage"
203
     * - Added children will be marked as "AddedToStage"
204
     * - Modified children will be marked as "ModifiedOnStage"
205
     * - Everything else has "SameOnStage" set, as an indicator that this information has been looked up.
206
     *
207
     * @return ArrayList
208
     */
209
    public function AllChildrenIncludingDeleted()
210
    {
211
        /** @var DataObject|Hierarchy|Versioned $owner */
212
        $owner = $this->owner;
213
        $stageChildren = $owner->stageChildren(true);
214
215
        // Add live site content that doesn't exist on the stage site, if required.
216
        if ($owner->hasExtension(Versioned::class) && $owner->hasStages()) {
0 ignored issues
show
Bug introduced by
The method hasStages() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

216
        if ($owner->hasExtension(Versioned::class) && $owner->/** @scrutinizer ignore-call */ hasStages()) {
Loading history...
Bug introduced by
The method hasExtension() does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

216
        if ($owner->/** @scrutinizer ignore-call */ hasExtension(Versioned::class) && $owner->hasStages()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
The method hasStages() does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

216
        if ($owner->hasExtension(Versioned::class) && $owner->/** @scrutinizer ignore-call */ hasStages()) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
217
            // Next, go through the live children.  Only some of these will be listed
218
            $liveChildren = $owner->liveChildren(true, true);
0 ignored issues
show
Bug introduced by
The method liveChildren() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

218
            /** @scrutinizer ignore-call */ 
219
            $liveChildren = $owner->liveChildren(true, true);
Loading history...
219
            if ($liveChildren) {
220
                $merged = new ArrayList();
221
                $merged->merge($stageChildren);
222
                $merged->merge($liveChildren);
223
                $stageChildren = $merged;
224
            }
225
        }
226
        $owner->extend("augmentAllChildrenIncludingDeleted", $stageChildren);
0 ignored issues
show
Bug introduced by
The method extend() does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

226
        $owner->/** @scrutinizer ignore-call */ 
227
                extend("augmentAllChildrenIncludingDeleted", $stageChildren);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
227
        return $stageChildren;
228
    }
229
230
    /**
231
     * Return all the children that this page had, including pages that were deleted from both stage & live.
232
     *
233
     * @return DataList
234
     * @throws Exception
235
     */
236
    public function AllHistoricalChildren()
237
    {
238
        /** @var DataObject|Versioned|Hierarchy $owner */
239
        $owner = $this->owner;
240
        if (!$owner->hasExtension(Versioned::class) || !$owner->hasStages()) {
241
            throw new Exception(
242
                'Hierarchy->AllHistoricalChildren() only works with Versioned extension applied with staging'
243
            );
244
        }
245
246
        $baseTable = $owner->baseTable();
0 ignored issues
show
Bug introduced by
The method baseTable() does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

246
        /** @scrutinizer ignore-call */ 
247
        $baseTable = $owner->baseTable();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
247
        $parentIDColumn = $owner->getSchema()->sqlColumnForField($owner, 'ParentID');
0 ignored issues
show
Bug introduced by
The method getSchema() does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

247
        $parentIDColumn = $owner->/** @scrutinizer ignore-call */ getSchema()->sqlColumnForField($owner, 'ParentID');

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug introduced by
It seems like $owner can also be of type SilverStripe\ORM\Hierarchy\Hierarchy; however, parameter $class of SilverStripe\ORM\DataObj...ma::sqlColumnForField() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

247
        $parentIDColumn = $owner->getSchema()->sqlColumnForField(/** @scrutinizer ignore-type */ $owner, 'ParentID');
Loading history...
248
        return Versioned::get_including_deleted(
249
            $owner->baseClass(),
0 ignored issues
show
Bug introduced by
The method baseClass() does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

249
            $owner->/** @scrutinizer ignore-call */ 
250
                    baseClass(),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
250
            [ $parentIDColumn => $owner->ID ],
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. Did you maybe forget to declare it?
Loading history...
Bug introduced by
array($parentIDColumn => $owner->ID) of type array<string,integer> is incompatible with the type string expected by parameter $filter of SilverStripe\Versioned\V...get_including_deleted(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

250
            /** @scrutinizer ignore-type */ [ $parentIDColumn => $owner->ID ],
Loading history...
251
            "\"{$baseTable}\".\"ID\" ASC"
252
        );
253
    }
254
255
    /**
256
     * Return the number of children that this page ever had, including pages that were deleted.
257
     *
258
     * @return int
259
     */
260
    public function numHistoricalChildren()
261
    {
262
        return $this->AllHistoricalChildren()->count();
263
    }
264
265
    /**
266
     * Return the number of direct children. By default, values are cached after the first invocation. Can be
267
     * augumented by {@link augmentNumChildrenCountQuery()}.
268
     *
269
     * @param bool $cache Whether to retrieve values from cache
270
     * @return int
271
     */
272
    public function numChildren($cache = true)
273
    {
274
        // Load if caching
275
        if ($cache) {
276
            $numChildren = $this->owner->_cache_numChildren;
0 ignored issues
show
Bug Best Practice introduced by
The property _cache_numChildren does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
277
            if (isset($numChildren)) {
278
                return $numChildren;
279
            }
280
        }
281
282
        // We call stageChildren(), because Children() has canView() filtering
283
        $numChildren = (int)$this->owner->stageChildren(true)->Count();
284
285
        // Save if caching
286
        if ($cache) {
287
            $this->owner->_cache_numChildren = $numChildren;
0 ignored issues
show
Bug Best Practice introduced by
The property _cache_numChildren does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property _cache_numChildren does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
288
        }
289
        return $numChildren;
290
    }
291
292
    /**
293
     * Checks if we're on a controller where we should filter. ie. Are we loading the SiteTree?
294
     *
295
     * @return bool
296
     */
297
    public function showingCMSTree()
298
    {
299
        if (!Controller::has_curr() || !class_exists(LeftAndMain::class)) {
300
            return false;
301
        }
302
        $controller = Controller::curr();
303
        return $controller instanceof LeftAndMain
304
            && in_array($controller->getAction(), array("treeview", "listview", "getsubtree"));
305
    }
306
307
    /**
308
     * Return children in the stage site.
309
     *
310
     * @param bool $showAll Include all of the elements, even those not shown in the menus. Only applicable when
311
     *                      extension is applied to {@link SiteTree}.
312
     * @return DataList
313
     */
314
    public function stageChildren($showAll = false)
315
    {
316
        $hideFromHierarchy = $this->owner->config()->hide_from_hierarchy;
0 ignored issues
show
Bug introduced by
The method config() does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. Did you maybe mean get_extra_config()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

316
        $hideFromHierarchy = $this->owner->/** @scrutinizer ignore-call */ config()->hide_from_hierarchy;

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
Bug Best Practice introduced by
The property hide_from_hierarchy does not exist on SilverStripe\Core\Config\Config_ForClass. Since you implemented __get, consider adding a @property annotation.
Loading history...
317
        $hideFromCMSTree = $this->owner->config()->hide_from_cms_tree;
0 ignored issues
show
Bug Best Practice introduced by
The property hide_from_cms_tree does not exist on SilverStripe\Core\Config\Config_ForClass. Since you implemented __get, consider adding a @property annotation.
Loading history...
318
        $baseClass = $this->owner->baseClass();
319
        $staged = DataObject::get($baseClass)
320
                ->filter('ParentID', (int)$this->owner->ID)
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. Did you maybe forget to declare it?
Loading history...
321
                ->exclude('ID', (int)$this->owner->ID);
322
        if ($hideFromHierarchy) {
323
            $staged = $staged->exclude('ClassName', $hideFromHierarchy);
324
        }
325
        if ($hideFromCMSTree && $this->showingCMSTree()) {
326
            $staged = $staged->exclude('ClassName', $hideFromCMSTree);
327
        }
328
        if (!$showAll && DataObject::getSchema()->fieldSpec($this->owner, 'ShowInMenus')) {
0 ignored issues
show
Bug introduced by
It seems like $this->owner can also be of type SilverStripe\ORM\Hierarchy\Hierarchy; however, parameter $classOrInstance of SilverStripe\ORM\DataObjectSchema::fieldSpec() does only seem to accept SilverStripe\ORM\DataObject|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

328
        if (!$showAll && DataObject::getSchema()->fieldSpec(/** @scrutinizer ignore-type */ $this->owner, 'ShowInMenus')) {
Loading history...
329
            $staged = $staged->filter('ShowInMenus', 1);
330
        }
331
        $this->owner->extend("augmentStageChildren", $staged, $showAll);
332
        return $staged;
333
    }
334
335
    /**
336
     * Return children in the live site, if it exists.
337
     *
338
     * @param bool $showAll              Include all of the elements, even those not shown in the menus. Only
339
     *                                   applicable when extension is applied to {@link SiteTree}.
340
     * @param bool $onlyDeletedFromStage Only return items that have been deleted from stage
341
     * @return DataList
342
     * @throws Exception
343
     */
344
    public function liveChildren($showAll = false, $onlyDeletedFromStage = false)
345
    {
346
        /** @var Versioned|DataObject|Hierarchy $owner */
347
        $owner = $this->owner;
348
        if (!$owner->hasExtension(Versioned::class) || !$owner->hasStages()) {
349
            throw new Exception('Hierarchy->liveChildren() only works with Versioned extension applied with staging');
350
        }
351
352
        $hideFromHierarchy = $owner->config()->hide_from_hierarchy;
0 ignored issues
show
Bug Best Practice introduced by
The property hide_from_hierarchy does not exist on SilverStripe\Core\Config\Config_ForClass. Since you implemented __get, consider adding a @property annotation.
Loading history...
353
        $hideFromCMSTree = $owner->config()->hide_from_cms_tree;
0 ignored issues
show
Bug Best Practice introduced by
The property hide_from_cms_tree does not exist on SilverStripe\Core\Config\Config_ForClass. Since you implemented __get, consider adding a @property annotation.
Loading history...
354
        $children = DataObject::get($owner->baseClass())
355
            ->filter('ParentID', (int)$owner->ID)
0 ignored issues
show
Bug Best Practice introduced by
The property ID does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. Did you maybe forget to declare it?
Loading history...
356
            ->exclude('ID', (int)$owner->ID)
357
            ->setDataQueryParam(array(
358
                'Versioned.mode' => $onlyDeletedFromStage ? 'stage_unique' : 'stage',
359
                'Versioned.stage' => 'Live'
360
            ));
361
        if ($hideFromHierarchy) {
362
            $children = $children->exclude('ClassName', $hideFromHierarchy);
363
        }
364
        if ($hideFromCMSTree && $this->showingCMSTree()) {
365
            $children = $children->exclude('ClassName', $hideFromCMSTree);
366
        }
367
        if (!$showAll && DataObject::getSchema()->fieldSpec($owner, 'ShowInMenus')) {
0 ignored issues
show
Bug introduced by
It seems like $owner can also be of type SilverStripe\ORM\Hierarchy\Hierarchy; however, parameter $classOrInstance of SilverStripe\ORM\DataObjectSchema::fieldSpec() does only seem to accept SilverStripe\ORM\DataObject|string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

367
        if (!$showAll && DataObject::getSchema()->fieldSpec(/** @scrutinizer ignore-type */ $owner, 'ShowInMenus')) {
Loading history...
368
            $children = $children->filter('ShowInMenus', 1);
369
        }
370
371
        return $children;
372
    }
373
374
    /**
375
     * Get this object's parent, optionally filtered by an SQL clause. If the clause doesn't match the parent, nothing
376
     * is returned.
377
     *
378
     * @param string $filter
379
     * @return DataObject
380
     */
381
    public function getParent($filter = null)
382
    {
383
        $parentID = $this->owner->ParentID;
0 ignored issues
show
Bug Best Practice introduced by
The property ParentID does not exist on SilverStripe\ORM\DataObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
384
        if (empty($parentID)) {
385
            return null;
386
        }
387
        $baseClass = $this->owner->baseClass();
388
        $idSQL = $this->owner->getSchema()->sqlColumnForField($baseClass, 'ID');
389
        return DataObject::get_one($baseClass, [
390
            [$idSQL => $parentID],
391
            $filter
392
        ]);
393
    }
394
395
    /**
396
     * Return all the parents of this class in a set ordered from the closest to furtherest parent.
397
     *
398
     * @param bool $includeSelf
399
     * @return ArrayList
400
     */
401
    public function getAncestors($includeSelf = false)
402
    {
403
        $ancestors = new ArrayList();
404
        $object = $this->owner;
405
406
        if ($includeSelf) {
407
            $ancestors->push($object);
408
        }
409
        while ($object = $object->getParent()) {
0 ignored issues
show
Bug introduced by
The method getParent() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

409
        while ($object = $object->/** @scrutinizer ignore-call */ getParent()) {
Loading history...
410
            $ancestors->push($object);
411
        }
412
413
        return $ancestors;
414
    }
415
416
    /**
417
     * Returns a human-readable, flattened representation of the path to the object, using its {@link Title} attribute.
418
     *
419
     * @param string $separator
420
     * @return string
421
     */
422
    public function getBreadcrumbs($separator = ' &raquo; ')
423
    {
424
        $crumbs = array();
425
        $ancestors = array_reverse($this->owner->getAncestors()->toArray());
0 ignored issues
show
Bug introduced by
The method getAncestors() does not exist on SilverStripe\ORM\DataObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

425
        $ancestors = array_reverse($this->owner->/** @scrutinizer ignore-call */ getAncestors()->toArray());
Loading history...
426
        /** @var DataObject $ancestor */
427
        foreach ($ancestors as $ancestor) {
428
            $crumbs[] = $ancestor->getTitle();
429
        }
430
        $crumbs[] = $this->owner->getTitle();
0 ignored issues
show
Bug introduced by
The method getTitle() does not exist on SilverStripe\ORM\Hierarchy\Hierarchy. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

430
        /** @scrutinizer ignore-call */ 
431
        $crumbs[] = $this->owner->getTitle();

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
431
        return implode($separator, $crumbs);
432
    }
433
434
    /**
435
     * Flush all Hierarchy caches:
436
     * - Children (instance)
437
     * - NumChildren (instance)
438
     */
439
    public function flushCache()
440
    {
441
        $this->owner->_cache_children = null;
0 ignored issues
show
Bug Best Practice introduced by
The property _cache_children does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property _cache_children does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
442
        $this->owner->_cache_numChildren = null;
0 ignored issues
show
Bug Best Practice introduced by
The property _cache_numChildren does not exist on SilverStripe\ORM\DataObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property _cache_numChildren does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
443
    }
444
}
445