Completed
Pull Request — master (#49)
by
unknown
02:58
created

ElementPageExtension::onAfterDuplicate()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 6
Ratio 35.29 %

Importance

Changes 0
Metric Value
dl 6
loc 17
rs 9.2
c 0
b 0
f 0
cc 4
eloc 10
nc 3
nop 1
1
<?php
2
3
use \Heyday\VersionedDataObjects\VersionedDataObjectDetailsForm;
4
5
/**
6
 * @package elemental
7
 */
8
class ElementPageExtension extends DataExtension
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
9
{
10
11
    /**
12
     * @config
13
     *
14
     * @var string $elements_title Title of the element in the CMS.
15
     */
16
    private static $elements_title = 'Content Blocks';
0 ignored issues
show
Unused Code introduced by
The property $elements_title is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
17
18
    /**
19
     * @config
20
     *
21
     * @var boolean $disable_element_publish_button Disable publish / unpublish buttons in GridFieldDetailForm.
22
     */
23
    private static $disable_element_publish_button = false;
0 ignored issues
show
Unused Code introduced by
The property $disable_element_publish_button is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
24
25
    /**
26
     * @config
27
     *
28
     * @var array $ignored_classes Classes to ignore adding elements too.
29
     */
30
    private static $ignored_classes = array();
0 ignored issues
show
Unused Code introduced by
The property $ignored_classes is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
31
32
    /**
33
     * @config
34
     *
35
     * @var boolean
36
     */
37
    private static $copy_element_content_to_contentfield = true;
0 ignored issues
show
Unused Code introduced by
The property $copy_element_content_to_contentfield is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
38
39
    /**
40
     * @config
41
     *
42
     * @var boolean
43
     */
44
    private static $clear_contentfield = false;
0 ignored issues
show
Unused Code introduced by
The property $clear_contentfield is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
45
46
    /**
47
     * @var array $db
48
     */
49
    private static $db = array();
0 ignored issues
show
Unused Code introduced by
The property $db is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
50
51
    /**
52
     * @var array $has_one
53
     */
54
    private static $has_one = array(
0 ignored issues
show
Unused Code introduced by
The property $has_one is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
55
        'ElementArea' => 'ElementalArea'
56
    );
57
58
    /**
59
     * Setup the CMS Fields
60
     *
61
     * @param FieldList
62
     */
63
    public function updateCMSFields(FieldList $fields)
64
    {
65
        if(!$this->supportsElemental()) {
66
            return;
67
        }
68
69
        // add an empty holder for content as some module explicitly use insert
70
        // after content.
71
        $fields->replaceField('Content', new LiteralField('Content', ''));
72
73
        $adder = new ElementalGridFieldAddNewMultiClass();
74
75
        $list = $this->getAvailableTypes();
76
        if($list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $list 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...
77
            $adder->setClasses($list);
78
        }
79
80
        $area = $this->owner->ElementArea();
81
        if ($this->owner->exists() && (!$area->exists() || !$area->isInDB())) {
82
            $area->write();
83
84
            $this->owner->ElementAreaID = $area->ID;
85
            $this->owner->write();
86
        }
87
88
        $gridField = GridField::create('ElementArea',
89
            Config::inst()->get("ElementPageExtension", 'elements_title'),
90
            $area->AllElements(),
91
            GridFieldConfig_RelationEditor::create()
92
                ->removeComponentsByType('GridFieldAddNewButton')
93
                ->removeComponentsByType('GridFieldSortableHeader')
94
                ->removeComponentsByType('GridFieldDeleteAction')
95
                ->removeComponentsByType('GridFieldAddExistingAutocompleter')
96
                ->addComponent($autocomplete = new ElementalGridFieldAddExistingAutocompleter())
97
                ->addComponent(new ElementalGridFieldDeleteAction())
98
                ->addComponent(new GridFieldTitleHeader())
99
                ->addComponent($adder)
100
                ->addComponent(new GridFieldSortableRows('Sort'))
101
        );
102
103
        if($list) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $list 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...
104
            $autocomplete->setSearchList(BaseElement::get()->filter('ClassName', array_keys($list)));
105
        }
106
107
        $config = $gridField->getConfig();
108
        $paginator = $config->getComponentByType('GridFieldPaginator');
109
        $paginator->setItemsPerPage(100);
110
111
        if (!$this->owner->config()->disable_element_publish_button) {
112
            $config->removeComponentsByType('GridFieldDetailForm');
113
            $config->addComponent($obj = new VersionedDataObjectDetailsForm());
114
        }
115
116
        $fields->addFieldToTab('Root.Main', $gridField);
117
118
        return $fields;
119
    }
120
121
    /**
122
     * @return array
123
     */
124
    public function getAvailableTypes() {
125
        if (is_array($this->owner->config()->get('allowed_elements'))) {
126
            $list = $this->owner->config()->get('allowed_elements');
127
128 View Code Duplication
            if($this->owner->config()->get('sort_types_alphabetically') !== false) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
129
                $sorted = array();
130
131
                foreach ($list as $class) {
132
                    $inst = singleton($class);
133
134
                    if ($inst->canCreate()) {
135
                        $sorted[$class] = singleton($class)->i18n_singular_name();
136
                    }
137
                }
138
139
                $list = $sorted;
140
                asort($list);
141
            }
142 View Code Duplication
        } else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
143
            $classes = ClassInfo::subclassesFor('BaseElement');
144
            $list = array();
145
            unset($classes['BaseElement']);
146
147
            $disallowedElements = (array) $this->owner->config()->get('disallowed_elements');
148
149
            if (!in_array('ElementVirtualLinked', $disallowedElements)) {
150
                array_push($disallowedElements, 'ElementVirtualLinked');
151
            }
152
153
            foreach ($classes as $class) {
0 ignored issues
show
Bug introduced by
The expression $classes of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
154
                $inst = singleton($class);
155
156
                if (!in_array($class, $disallowedElements) && $inst->canCreate()) {
157
                    $list[$class] = singleton($class)->i18n_singular_name();
158
                }
159
            }
160
161
            asort($list);
162
        }
163
164
        if (method_exists($this->owner, 'sortElementalOptions')) {
165
            $this->owner->sortElementalOptions($list);
166
        }
167
168
        return $list;
169
    }
170
171
    /**
172
     * Make sure there is always a WidgetArea sidebar for adding widgets
173
     *
174
     */
175
    public function onBeforeWrite()
176
    {
177
        if(!$this->supportsElemental()) {
178
            return;
179
        }
180
181
        // enable theme in case elements are being rendered with templates stored in theme folder
182
        $originalThemeEnabled = Config::inst()->get('SSViewer', 'theme_enabled');
183
        Config::inst()->update('SSViewer', 'theme_enabled', true);
184
185
186
        if ($this->owner->hasMethod('ElementArea') && Config::inst()->get(__CLASS__, 'copy_element_content_to_contentfield')) {
187
            $elements = $this->owner->ElementArea();
188
189
            if (!$elements->isInDB()) {
190
                $elements->write();
191
                $this->owner->ElementAreaID = $elements->ID;
192
            } else {
193
                // Copy widgets content to Content to enable search
194
                $searchableContent = array();
195
196
                Requirements::clear();
197
198
                foreach ($elements->Elements() as $element) {
199
                    if ($element->config()->exclude_from_content) {
200
                        continue;
201
                    }
202
203
                    $controller = $element->getController();
204
                    $controller->init();
205
206
                    array_push($searchableContent, $controller->WidgetHolder());
207
                }
208
209
                Requirements::restore();
210
211
                $this->owner->Content = trim(implode(' ', $searchableContent));
212
            }
213
        } else {
214
            if(Config::inst()->get(__CLASS__, 'clear_contentfield')) {
215
                $this->owner->Content = '';
216
            }
217
        }
218
219
220
        // set theme_enabled back to what it was
221
        Config::inst()->update('SSViewer', 'theme_enabled', $originalThemeEnabled);
222
223
        parent::onBeforeWrite();
224
    }
225
226
    /**
227
     * @return boolean
228
     */
229
    public function supportsElemental() {
230
        if ($this->owner->hasMethod('includeElemental')) {
231
            $res = $this->owner->includeElemental();
232
            if ($res !== null) {
233
                return $res;
234
            }
235
        }
236
237
        if (is_a($this->owner, 'RedirectorPage')) {
238
            return false;
239
        } else if ($ignored = Config::inst()->get('ElementPageExtension', 'ignored_classes')) {
240
            foreach ($ignored as $check) {
0 ignored issues
show
Bug introduced by
The expression $ignored of type array|integer|double|string|boolean is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
241
                if (is_a($this->owner, $check)) {
242
                    return false;
243
                }
244
            }
245
        }
246
247
        return true;
248
    }
249
250
    /**
251
     * If the page is duplicated, copy the widgets across too.
252
     *
253
     * Gets called twice from either direction, due to bad DataObject and SiteTree code, hence the weird if statement
254
     *
255
     * @return Page The duplicated page
0 ignored issues
show
Documentation introduced by
Should the return type not be Page|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
256
     */
257
    public function onAfterDuplicate($duplicatePage)
258
    {
259
        if ($this->owner->ID != 0 && $this->owner->ID < $duplicatePage->ID) {
260
            $originalWidgetArea = $this->owner->getComponent('ElementArea');
261
            $duplicateWidgetArea = $originalWidgetArea->duplicate(false);
262
            $duplicateWidgetArea->write();
263
            $duplicatePage->ElementAreaID = $duplicateWidgetArea->ID;
264
            $duplicatePage->write();
265
266 View Code Duplication
            foreach ($originalWidgetArea->Items() as $originalWidget) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
267
                $duplicateWidget = $originalWidget->duplicate(true);
268
269
                // manually set the ParentID of each widget, so we don't get versioning issues
270
                DB::query(sprintf("UPDATE Widget SET ParentID = %d WHERE ID = %d", $duplicateWidgetArea->ID, $duplicateWidget->ID));
271
            }
272
        }
273
    }
274
275
    /**
276
     * If the page is duplicated across subsites, copy the widgets across too.
277
     *
278
     * @return Page The duplicated page
0 ignored issues
show
Documentation introduced by
Should the return type not be Page|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
279
     */
280
    public function onAfterDuplicateToSubsite($originalPage)
281
    {
282
        $originalWidgetArea = $originalPage->getComponent('ElementArea');
283
        $duplicateWidgetArea = $originalWidgetArea->duplicate(false);
284
        $duplicateWidgetArea->write();
285
        $this->owner->ElementAreaID = $duplicateWidgetArea->ID;
286
        $this->owner->write();
287
288 View Code Duplication
        foreach ($originalWidgetArea->Items() as $originalWidget) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
289
            $duplicateWidget = $originalWidget->duplicate(true);
290
291
            // manually set the ParentID of each widget, so we don't get versioning issues
292
            DB::query(sprintf("UPDATE Widget SET ParentID = %d WHERE ID = %d", $duplicateWidgetArea->ID, $duplicateWidget->ID));
293
        }
294
    }
295
296
    /**
297
     * Publish
298
     */
299
    public function onAfterPublish()
300
    {
301
        if ($id = $this->owner->ElementAreaID) {
302
            $widgets = Versioned::get_by_stage('BaseElement', 'Stage', "ParentID = '$id'");
303
            $staged = array();
304
305
            foreach ($widgets as $widget) {
306
                $staged[] = $widget->ID;
307
308
                $widget->publish('Stage', 'Live');
309
            }
310
311
            // remove any elements that are on live but not in draft.
312
            $widgets = Versioned::get_by_stage('BaseElement', 'Live', "ParentID = '$id'");
313
314
            foreach ($widgets as $widget) {
315
                if (!in_array($widget->ID, $staged)) {
316
                    $widget->deleteFromStage('Live');
317
                }
318
            }
319
        }
320
    }
321
322
    /**
323
     * Roll back all changes if the parent page has a rollback event
324
     *
325
     * Only do rollback if it's the 'cancel draft changes' rollback, not a specific version
326
     * rollback.
327
     *
328
     * @param string $version
329
     * @return null
330
     */
331
    public function onBeforeRollback($version)
332
    {
333
        if ($version !== 'Live') {
334
            // we don't yet have a smart way of rolling back to a specific version
335
            return;
336
        }
337
        if ($id = $this->owner->ElementAreaID) {
338
            $widgets = Versioned::get_by_stage('BaseElement', 'Live', "ParentID = '$id'");
339
            $staged = array();
340
341
            foreach ($widgets as $widget) {
342
                $staged[] = $widget->ID;
343
344
                $widget->invokeWithExtensions('onBeforeRollback', $widget);
345
346
                $widget->publish("Live", "Stage", false);
347
348
                $widget->invokeWithExtensions('onAfterRollback', $widget);
349
            }
350
        }
351
    }
352
}
353