SiteTreeExtension::setTopPageForElementalArea()   A
last analyzed

Complexity

Conditions 6
Paths 6

Size

Total Lines 32
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 14
nc 6
nop 0
dl 0
loc 32
rs 9.2222
c 1
b 0
f 0
1
<?php
2
3
namespace DNADesign\Elemental\TopPage;
4
5
use DNADesign\Elemental\Extensions\ElementalPageExtension;
6
use DNADesign\Elemental\Models\ElementalArea;
7
use Page;
8
use SilverStripe\CMS\Model\SiteTreeExtension as BaseSiteTreeExtension;
9
use SilverStripe\ORM\DataObject;
10
use SilverStripe\ORM\ValidationException;
11
12
/**
13
 * Class SiteTreeExtension
14
 *
15
 * This extension must be present on pagetypes that need to support Elemental TopPage functionality.
16
 * It can be applied directly to Page, as it only takes effect in the presence of a ElementalArea.
17
 *
18
 * @property Page|$this $owner
19
 * @package DNADesign\Elemental\TopPage
20
 */
21
class SiteTreeExtension extends BaseSiteTreeExtension
22
{
23
    /**
24
     * List of pages currently undergoing duplication
25
     *
26
     * @var array
27
     */
28
    protected $duplicatedPages = [];
29
30
    /**
31
     * List of objects that need to udate their top page reference
32
     *
33
     * @var array
34
     */
35
    protected $duplicatedObjects = [];
36
37
    /**
38
     * Extension point in @see DataObject::onAfterWrite()
39
     *
40
     * @throws ValidationException
41
     */
42
    public function onAfterWrite(): void
43
    {
44
        $this->setTopPageForElementalArea();
45
        $this->processDuplicationFromOriginal();
46
    }
47
48
    /**
49
     * Extension point in @see DataObject::duplicate()
50
     *
51
     * @param Page $original
52
     */
53
    public function onBeforeDuplicate(Page $original): void
54
    {
55
        $this->initDuplication($original);
56
    }
57
58
    /**
59
     * Extension point in @see DataObject::duplicate()
60
     *
61
     * @param Page $original
62
     * @param bool $doWrite
63
     * @throws ValidationException
64
     */
65
    public function onAfterDuplicate(Page $original, $doWrite): void
66
    {
67
        $this->processDuplication($original, (bool) $doWrite);
68
    }
69
70
    /**
71
     * Generates a unique key for the page
72
     *
73
     * @return string|null
74
     */
75
    public function getDuplicationKey(): ?string
76
    {
77
        $owner = $this->owner;
78
79
        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

79
        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...
80
            return null;
81
        }
82
83
        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...
84
    }
85
86
    /**
87
     * Registers the given object to receive an updated TopPage reference after the duplication
88
     * operation completes, ensuring the new Page is written to the database beforehand.
89
     *
90
     * The registry uses a stack-like structure to allow accurate tracking of objects during
91
     * duplication operations that include nested pages.
92
     *
93
     * @param DataObject $object
94
     */
95
    public function addDuplicatedObject(DataObject $object): void
96
    {
97
        if (!$object->hasExtension(DataExtension::class)) {
98
            return;
99
        }
100
101
        $key = $this->getDuplicatedPageKey();
102
103
        if ($key === null) {
104
            return;
105
        }
106
107
        if (array_key_exists($key, $this->duplicatedObjects ?? [])) {
108
            array_unshift($this->duplicatedObjects[$key], $object);
109
110
            return;
111
        }
112
113
        $this->duplicatedObjects[$key] = [$object];
114
    }
115
116
    /**
117
     * Find currently duplicated page
118
     * note: this doesn't change any stored data
119
     *
120
     * @return string|null
121
     */
122
    protected function getDuplicatedPageKey(): ?string
123
    {
124
        $pages = $this->duplicatedPages;
125
126
        if (count($pages ?? []) === 0) {
127
            return null;
128
        }
129
130
        return array_shift($pages);
131
    }
132
133
    /**
134
     * @param Page|SiteTreeExtension $original
135
     */
136
    protected function initDuplication(Page $original): void
137
    {
138
        /** @var DataExtension $extension */
139
        $extension = singleton(DataExtension::class);
140
141
        if (!$extension->getTopPageUpdate()) {
142
            return;
143
        }
144
145
        $key = $original->getDuplicationKey();
146
147
        if ($key === null) {
148
            return;
149
        }
150
151
        if (in_array($key, $this->duplicatedPages ?? [])) {
152
            // this should never happen as it would indicate a duplication loop
153
            return;
154
        }
155
156
        array_unshift($this->duplicatedPages, $key);
157
    }
158
159
    /**
160
     * Update top page reference during duplication process
161
     *
162
     * @param Page $original
163
     * @param bool $written
164
     * @throws ValidationException
165
     */
166
    protected function processDuplication(Page $original, bool $written): void
167
    {
168
        /** @var DataExtension $extension */
169
        $extension = singleton(DataExtension::class);
170
171
        if (!$extension->getTopPageUpdate()) {
172
            return;
173
        }
174
175
        if ($written) {
176
            $this->writeDuplication($original);
177
178
            return;
179
        }
180
181
        // write may not be triggered as the page maybe have come up via relation
182
        // in this case we have to delay the processing until the page is written
183
        // store the origin reference on the object (in memory only) so we can pick it up later
184
        $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...
185
    }
186
187
    /**
188
     * Relevant only for duplicated object that were not written at the time of duplication
189
     *
190
     * @throws ValidationException
191
     */
192
    protected function processDuplicationFromOriginal(): void
193
    {
194
        /** @var DataExtension $extension */
195
        $extension = singleton(DataExtension::class);
196
197
        if (!$extension->getTopPageUpdate()) {
198
            return;
199
        }
200
201
        $owner = $this->owner;
202
203
        if (!isset($owner->duplicationOriginal)) {
204
            return;
205
        }
206
207
        $original = $owner->duplicationOriginal;
208
209
        if (!$original instanceof Page) {
210
            return;
211
        }
212
213
        unset($owner->duplicationOriginal);
214
        $this->writeDuplication($original);
215
    }
216
217
    /**
218
     * @param Page|SiteTreeExtension $original
219
     * @throws ValidationException
220
     */
221
    protected function writeDuplication(Page $original): void
222
    {
223
        $key = $original->getDuplicationKey();
224
        $currentKey = $this->getDuplicatedPageKey();
225
226
        if ($key !== $currentKey) {
227
            // should never happen but it indicates that the nesting hierarchy was incorrect
228
            return;
229
        }
230
231
        if (array_key_exists($key, $this->duplicatedObjects ?? [])) {
232
            $objects = $this->duplicatedObjects[$key];
233
234
            /** @var DataObject|DataExtension $object */
235
            foreach ($objects as $object) {
236
                // attach current page ID to the object
237
                $object->setTopPage($this->owner);
238
            }
239
        }
240
241
        // mark page as processed
242
        array_shift($this->duplicatedPages);
243
    }
244
245
    /**
246
     * Elemental area is created before related page is written so we have to set top page explicitly
247
     * after page is written and the relations are available
248
     *
249
     * @throws ValidationException
250
     */
251
    protected function setTopPageForElementalArea(): void
252
    {
253
        /** @var DataExtension $extension */
254
        $extension = singleton(DataExtension::class);
255
256
        if (!$extension->getTopPageUpdate()) {
257
            return;
258
        }
259
260
        /** @var Page|ElementalPageExtension $owner */
261
        $owner = $this->owner;
262
263
        if (!$owner->hasExtension(ElementalPageExtension::class)) {
264
            return;
265
        }
266
267
        if (!$owner->ElementalAreaID) {
268
            return;
269
        }
270
271
        /** @var ElementalArea|DataExtension $area */
272
        $area = $owner->ElementalArea();
273
274
        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

274
        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...
275
            return;
276
        }
277
278
        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

278
        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...
279
            return;
280
        }
281
282
        $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

282
        $area->/** @scrutinizer ignore-call */ 
283
               setTopPage($owner);
Loading history...
283
    }
284
}
285