Completed
Push — master ( 528f56...b74c98 )
by Will
02:50
created

ElementPageExtension::onAfterDuplicate()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 6
Ratio 35.29 %

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 6
loc 17
rs 9.2
cc 4
eloc 10
nc 3
nop 1
1
<?php
2
3
/**
4
 * @package elemental
5
 */
6
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...
7
{
8
9
    /**
10
     * @config
11
     *
12
     * @var string $elements_title Title of the element in the CMS.
13
     */
14
    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...
15
16
    /**
17
     * @config
18
     *
19
     * @var boolean $disable_element_publish_button Disable publish / unpublish buttons in GridFieldDetailForm.
20
     */
21
    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...
22
23
    /**
24
     * @config
25
     *
26
     * @var array $ignored_classes Classes to ignore adding elements too.
27
     */
28
    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...
29
30
    /**
31
     * @config
32
     *
33
     * @var boolean
34
     */
35
    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...
36
37
    /**
38
     * @config
39
     *
40
     * @var boolean
41
     */
42
    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...
43
44
    /**
45
     * @var array $db
46
     */
47
    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...
48
49
    /**
50
     * @var array $has_one
51
     */
52
    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...
53
        'ElementArea' => 'ElementalArea'
54
    );
55
56
    /**
57
     * Setup the CMS Fields
58
     *
59
     * @param FieldList
60
     */
61
    public function updateCMSFields(FieldList $fields)
62
    {
63
        if(!$this->supportsElemental()) {
64
            return;
65
        }
66
67
        // add an empty holder for content as some module explicitly use insert
68
        // after content.
69
        $fields->replaceField('Content', new LiteralField('Content', ''));
70
71
        $adder = new ElementalGridFieldAddNewMultiClass();
72
73
        $list = $this->getAvailableTypes();
74
        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...
75
            $adder->setClasses($list);
76
        }
77
78
        $area = $this->owner->ElementArea();
79
        if ($this->owner->exists() && (!$area->exists() || !$area->isInDB())) {
80
            $area->write();
81
82
            $this->owner->ElementAreaID = $area->ID;
83
            $this->owner->write();
84
        }
85
86
        $gridField = GridField::create('ElementArea',
87
            Config::inst()->get("ElementPageExtension", 'elements_title'),
88
            $area->Elements(),
89
            GridFieldConfig_RelationEditor::create()
90
                ->removeComponentsByType('GridFieldAddNewButton')
91
                ->removeComponentsByType('GridFieldDeleteAction')
92
                ->removeComponentsByType('GridFieldAddExistingAutocompleter')
93
                ->addComponent($autocomplete = new ElementalGridFieldAddExistingAutocompleter())
94
                ->addComponent(new ElementalGridFieldDeleteAction())
95
                ->addComponent($adder)
96
                ->addComponent(new GridFieldSortableRows('Sort'))
97
        );
98
99
        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...
100
            $autocomplete->setSearchList(BaseElement::get()->filter('ClassName', array_keys($list)));
101
        }
102
103
        $config = $gridField->getConfig();
104
        $paginator = $config->getComponentByType('GridFieldPaginator');
105
        $paginator->setItemsPerPage(100);
106
107
        if (!$this->owner->config()->disable_element_publish_button) {
108
            $config->removeComponentsByType('GridFieldDetailForm');
109
            $config->addComponent($obj = new VersionedDataObjectDetailsForm());
110
        }
111
112
        $fields->addFieldToTab('Root.Main', $gridField);
113
114
        return $fields;
115
    }
116
117
    /**
118
     * @return array
119
     */
120
    public function getAvailableTypes() {
121
        if (is_array($this->owner->config()->get('allowed_elements'))) {
122
            $list = $this->owner->config()->get('allowed_elements');
123
124 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...
125
                $sorted = array();
126
127
                foreach ($list as $class) {
128
                    $inst = singleton($class);
129
130
                    if ($inst->canCreate()) {
131
                        $sorted[$class] = singleton($class)->i18n_singular_name();
132
                    }
133
                }
134
135
                $list = $sorted;
136
                asort($list);
137
            }
138 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...
139
            $classes = ClassInfo::subclassesFor('BaseElement');
140
            $list = array();
141
            unset($classes['BaseElement']);
142
143
            $disallowedElements = (array) $this->owner->config()->get('disallowed_elements');
144
145
            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...
146
                $inst = singleton($class);
147
148
                if (!in_array($class, $disallowedElements) && $inst->canCreate()) {
149
                    $list[$class] = singleton($class)->i18n_singular_name();
150
                }
151
            }
152
153
            asort($list);
154
        }
155
156
        if (method_exists($this->owner, 'sortElementalOptions')) {
157
            $this->owner->sortElementalOptions($list);
158
        }
159
160
        return $list;
161
    }
162
163
    /**
164
     * Make sure there is always a WidgetArea sidebar for adding widgets
165
     *
166
     */
167
    public function onBeforeWrite()
168
    {
169
        if(!$this->supportsElemental()) {
170
            return;
171
        }
172
173
        // enable theme in case elements are being rendered with templates stored in theme folder
174
        $originalThemeEnabled = Config::inst()->get('SSViewer', 'theme_enabled');
175
        Config::inst()->update('SSViewer', 'theme_enabled', true);
176
177
178
        if ($this->owner->hasMethod('ElementArea') && Config::inst()->get(__CLASS__, 'copy_element_content_to_contentfield')) {
179
            $elements = $this->owner->ElementArea();
180
181
            if (!$elements->isInDB()) {
182
                $elements->write();
183
                $this->owner->ElementAreaID = $elements->ID;
184
            } else {
185
                // Copy widgets content to Content to enable search
186
                $searchableContent = array();
187
188
                Requirements::clear();
189
190
                foreach ($elements->Elements() as $element) {
191
                    if ($element->config()->exclude_from_content) {
192
                        continue;
193
                    }
194
195
                    $controller = $element->getController();
196
                    $controller->init();
197
198
                    array_push($searchableContent, $controller->WidgetHolder());
199
                }
200
                
201
                Requirements::restore();
202
203
                $this->owner->Content = trim(implode(' ', $searchableContent));
204
            }
205
        } else {
206
            if(Config::inst()->get(__CLASS__, 'clear_contentfield')) {
207
                $this->owner->Content = '';
208
            }
209
        }
210
211
212
        // set theme_enabled back to what it was
213
        Config::inst()->update('SSViewer', 'theme_enabled', $originalThemeEnabled);
214
215
        parent::onBeforeWrite();
216
    }
217
218
    /**
219
     * @return boolean
220
     */
221
    public function supportsElemental() {
222
        if (method_exists($this->owner, 'includeElemental')) {
223
            return $this->owner->includeElemental();
224
        }
225
226
        if (is_a($this->owner, 'RedirectorPage')) {
227
            return false;
228
        } else if ($ignored = Config::inst()->get('ElementPageExtension', 'ignored_classes')) {
229
            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...
230
                if (is_a($this->owner, $check)) {
231
                    return false;
232
                }
233
            }
234
        }
235
236
        return true;
237
    }
238
239
    /**
240
     * If the page is duplicated, copy the widgets across too.
241
     *
242
     * Gets called twice from either direction, due to bad DataObject and SiteTree code, hence the weird if statement
243
     *
244
     * @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...
245
     */
246
    public function onAfterDuplicate($duplicatePage)
247
    {
248
        if ($this->owner->ID != 0 && $this->owner->ID < $duplicatePage->ID) {
249
            $originalWidgetArea = $this->owner->getComponent('ElementArea');
250
            $duplicateWidgetArea = $originalWidgetArea->duplicate(false);
251
            $duplicateWidgetArea->write();
252
            $duplicatePage->ElementAreaID = $duplicateWidgetArea->ID;
253
            $duplicatePage->write();
254
255 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...
256
                $duplicateWidget = $originalWidget->duplicate(true);
257
258
                // manually set the ParentID of each widget, so we don't get versioning issues
259
                DB::query(sprintf("UPDATE Widget SET ParentID = %d WHERE ID = %d", $duplicateWidgetArea->ID, $duplicateWidget->ID));
260
            }
261
        }
262
    }
263
264
    /**
265
     * If the page is duplicated across subsites, copy the widgets across too.
266
     *
267
     * @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...
268
     */
269
    public function onAfterDuplicateToSubsite($originalPage)
270
    {
271
        $originalWidgetArea = $originalPage->getComponent('ElementArea');
272
        $duplicateWidgetArea = $originalWidgetArea->duplicate(false);
273
        $duplicateWidgetArea->write();
274
        $this->owner->ElementAreaID = $duplicateWidgetArea->ID;
275
        $this->owner->write();
276
277 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...
278
            $duplicateWidget = $originalWidget->duplicate(true);
279
280
            // manually set the ParentID of each widget, so we don't get versioning issues
281
            DB::query(sprintf("UPDATE Widget SET ParentID = %d WHERE ID = %d", $duplicateWidgetArea->ID, $duplicateWidget->ID));
282
        }
283
    }
284
285
    /**
286
     * Publish
287
     */
288
    public function onAfterPublish()
289
    {
290
        if ($id = $this->owner->ElementAreaID) {
291
            $widgets = Versioned::get_by_stage('BaseElement', 'Stage', "ParentID = '$id'");
292
            $staged = array();
293
294
            foreach ($widgets as $widget) {
295
                $staged[] = $widget->ID;
296
297
                $widget->publish('Stage', 'Live');
298
            }
299
300
            // remove any elements that are on live but not in draft.
301
            $widgets = Versioned::get_by_stage('BaseElement', 'Live', "ParentID = '$id'");
302
303
            foreach ($widgets as $widget) {
304
                if (!in_array($widget->ID, $staged)) {
305
                    $widget->deleteFromStage('Live');
306
                }
307
            }
308
        }
309
    }
310
311
    /**
312
     * Roll back all changes if the parent page has a rollback event
313
     *
314
     * Only do rollback if it's the 'cancel draft changes' rollback, not a specific version
315
     * rollback.
316
     *
317
     * @param string $version
318
     * @return null
319
     */
320
    public function onBeforeRollback($version)
321
    {
322
        if ($version !== 'Live') {
323
            // we don't yet have a smart way of rolling back to a specific version
324
            return;
325
        }
326
        if ($id = $this->owner->ElementAreaID) {
327
            $widgets = Versioned::get_by_stage('BaseElement', 'Live', "ParentID = '$id'");
328
            $staged = array();
329
330
            foreach ($widgets as $widget) {
331
                $staged[] = $widget->ID;
332
333
                $widget->invokeWithExtensions('onBeforeRollback', $widget);
334
335
                $widget->publish("Live", "Stage", false);
336
337
                $widget->invokeWithExtensions('onAfterRollback', $widget);
338
            }
339
        }
340
    }
341
}
342