Passed
Pull Request — master (#774)
by
unknown
05:46
created

SiteTreeExtension::addDuplicatedObject()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 9
c 1
b 0
f 0
nc 4
nop 1
dl 0
loc 19
rs 9.9666
1
<?php
2
3
namespace DNADesign\Elemental\TopPage;
4
5
use DNADesign\Elemental\Extensions\ElementalPageExtension;
6
use DNADesign\Elemental\Models\ElementalArea;
7
use Page;
0 ignored issues
show
Bug introduced by
The type Page 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...
8
use SilverStripe\CMS\Model\SiteTreeExtension as BaseSiteTreeExtension;
9
use SilverStripe\ORM\DataObject;
10
11
/**
12
 * Class SiteTreeExtension
13
 *
14
 * This extension is mandatory for any pages that need to support top page functionality
15
 * it is safe to apply this extension directly to Page as the functionality detects presence of elemental area
16
 * alternatively, this extension can be applied to top level block page or all block pages individually
17
 * in case no common parent exists
18
 *
19
 * @property Page|$this $owner
20
 * @package DNADesign\Elemental\TopPage
21
 */
22
class SiteTreeExtension extends BaseSiteTreeExtension
23
{
24
    /**
25
     * List of pages currently undergoing duplication
26
     *
27
     * @var array
28
     */
29
    protected $duplicatedPages = [];
30
31
    /**
32
     * List of objects that need to udate their top page reference
33
     *
34
     * @var array
35
     */
36
    protected $duplicatedObjects = [];
37
38
    /**
39
     * Exension point in @see DataObject::onAfterWrite()
40
     */
41
    public function onAfterWrite(): void
42
    {
43
        $this->setTopPageForElementalArea();
44
        $this->processDuplicationFromOriginal();
45
    }
46
47
    /**
48
     * Exension point in @see DataObject::duplicate()
49
     *
50
     * @param Page $original
51
     */
52
    public function onBeforeDuplicate(Page $original): void
53
    {
54
        $this->initDuplication($original);
55
    }
56
57
    /**
58
     * Exension point in @see DataObject::duplicate()
59
     *
60
     * @param Page $original
61
     * @param bool $doWrite
62
     */
63
    public function onAfterDuplicate(Page $original, $doWrite): void
64
    {
65
        $this->processDuplication($original, (bool) $doWrite);
66
    }
67
68
    public function getDuplicationKey(): ?string
69
    {
70
        $owner = $this->owner;
71
72
        if (!$owner->isInDB()) {
0 ignored issues
show
Bug introduced by
The method isInDB() does not exist on DNADesign\Elemental\TopPage\SiteTreeExtension. ( Ignorable by Annotation )

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

72
        if (!$owner->/** @scrutinizer ignore-call */ isInDB()) {

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...
73
            return null;
74
        }
75
76
        return sprintf('%s-%d', $owner->ClassName, $owner->ID);
0 ignored issues
show
Bug Best Practice introduced by
The property ClassName does not exist on DNADesign\Elemental\TopPage\SiteTreeExtension. Did you maybe forget to declare it?
Loading history...
Bug Best Practice introduced by
The property ID does not exist on DNADesign\Elemental\TopPage\SiteTreeExtension. Did you maybe forget to declare it?
Loading history...
77
    }
78
79
    /**
80
     * This is a very roundabout way on how to update top page reference for data objects
81
     * the main reason why we're doing it like this is the fact that duplication process goes top-down
82
     * when it comes to model creation, but it goes bottom-up when it comes to writes
83
     * as a consequence the relations are not available when model is written
84
     *
85
     * Instead of updating the page reference during model write, we will simply push the object into a list
86
     * and update the reference later when page is available
87
     *
88
     * note that when going top-down on the relationship tree the models are not written yet so we can't use
89
     * page IDs as identifiers
90
     * instead we use IDs of the original objects that we are cloning from
91
     *
92
     * Additionally, the duplication process has to support nested pages which makes things more complicated
93
     * this is handled by using a stack like data structure to keep track on multiple pages
94
     * each with it's own duplication list
95
     *
96
     * @param DataObject $object
97
     */
98
    public function addDuplicatedObject(DataObject $object): void
99
    {
100
        if (!$object->hasExtension(DataExtension::class)) {
101
            return;
102
        }
103
104
        $key = $this->getDuplicatedPageKey();
105
106
        if ($key === null) {
107
            return;
108
        }
109
110
        if (array_key_exists($key, $this->duplicatedObjects)) {
111
            array_unshift($this->duplicatedObjects[$key], $object);
112
113
            return;
114
        }
115
116
        $this->duplicatedObjects[$key] = [$object];
117
    }
118
119
    /**
120
     * Find currently duplicated page
121
     * note: this doesn't change any stored data
122
     *
123
     * @return string|null
124
     */
125
    protected function getDuplicatedPageKey(): ?string
126
    {
127
        $pages = $this->duplicatedPages;
128
129
        if (count($pages) === 0) {
130
            return null;
131
        }
132
133
        return array_shift($pages);
134
    }
135
136
    /**
137
     * @param Page|SiteTreeExtension $original
138
     */
139
    protected function initDuplication(Page $original): void
140
    {
141
        $key = $original->getDuplicationKey();
142
143
        if ($key === null) {
144
            return;
145
        }
146
147
        if (in_array($key, $this->duplicatedPages)) {
148
            // this should never happen as it would indicate a duplication loop
149
            return;
150
        }
151
152
        array_unshift($this->duplicatedPages, $key);
153
    }
154
155
    protected function processDuplication(Page $original, bool $written): void
156
    {
157
        if ($written) {
158
            $this->writeDuplication($original);
159
160
            return;
161
        }
162
163
164
        // write may not be triggered as the page maybe have come up via relation
165
        // in this case we have to delay the processing until the page is written
166
        // store the origin reference on the object (in memory only) so we can pick it up later
167
        $this->owner->duplicationOriginal = $original;
0 ignored issues
show
Bug Best Practice introduced by
The property duplicationOriginal does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
168
    }
169
170
    /**
171
     * Relevant only for duplicated object that were not written at the time of duplication
172
     */
173
    protected function processDuplicationFromOriginal(): void
174
    {
175
        $owner = $this->owner;
176
177
        if (!isset($owner->duplicationOriginal)) {
178
            return;
179
        }
180
181
        $original = $owner->duplicationOriginal;
182
183
        if (!$original instanceof Page) {
184
            return;
185
        }
186
187
        unset($owner->duplicationOriginal);
188
        $this->writeDuplication($original);
189
    }
190
191
    /**
192
     * @param Page|SiteTreeExtension $original
193
     */
194
    protected function writeDuplication(Page $original): void
195
    {
196
        $key = $original->getDuplicationKey();
197
        $currentKey = $this->getDuplicatedPageKey();
198
199
        if ($key !== $currentKey) {
200
            // should never happen but it indicates that the nesting hierarchy was incorrect
201
            return;
202
        }
203
204
        if (array_key_exists($key, $this->duplicatedObjects)) {
205
            $objects = $this->duplicatedObjects[$key];
206
207
            /** @var DataObject|DataExtension $object */
208
            foreach ($objects as $object) {
209
                // attach current page ID to the object
210
                $object->setTopPage($this->owner);
211
            }
212
        }
213
214
        // mark page as processed
215
        array_shift($this->duplicatedPages);
216
    }
217
218
    /**
219
     * Elemental area is created before related page is written so we have to set top page explicitly
220
     * after page is written and the relations are available
221
     */
222
    protected function setTopPageForElementalArea(): void
223
    {
224
        /** @var Page|ElementalPageExtension $owner */
225
        $owner = $this->owner;
226
227
        if (!$owner->hasExtension(ElementalPageExtension::class)) {
228
            return;
229
        }
230
231
        if (!$owner->ElementalAreaID) {
232
            return;
233
        }
234
235
        /** @var ElementalArea|DataExtension $area */
236
        $area = $owner->ElementalArea();
237
238
        if (!$area->exists()) {
0 ignored issues
show
Bug introduced by
The method exists() does not exist on DNADesign\Elemental\TopPage\DataExtension. ( Ignorable by Annotation )

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

238
        if (!$area->/** @scrutinizer ignore-call */ exists()) {

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...
239
            return;
240
        }
241
242
        if (!$area->hasExtension(DataExtension::class)) {
0 ignored issues
show
Bug introduced by
The method hasExtension() does not exist on DNADesign\Elemental\TopPage\DataExtension. ( Ignorable by Annotation )

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

242
        if (!$area->/** @scrutinizer ignore-call */ hasExtension(DataExtension::class)) {

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...
243
            return;
244
        }
245
246
        $area->setTopPage($owner);
0 ignored issues
show
Bug introduced by
The method setTopPage() does not exist on DNADesign\Elemental\Models\ElementalArea. 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

246
        $area->/** @scrutinizer ignore-call */ 
247
               setTopPage($owner);
Loading history...
247
    }
248
}
249