Completed
Push — master ( 5b436c...430cc4 )
by Will
02:16
created

ElementPageExtension::onBeforeRollback()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 21
rs 9.0534
cc 4
eloc 11
nc 4
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 false;
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(new ElementalGridFieldAddExistingAutocompleter())
94
                ->addComponent(new ElementalGridFieldDeleteAction())
95
                ->addComponent($adder)
96
                ->addComponent(new GridFieldSortableRows('Sort'))
97
        );
98
99
        $config = $gridField->getConfig();
100
        $paginator = $config->getComponentByType('GridFieldPaginator');
101
        $paginator->setItemsPerPage(100);
102
103
        if (!$this->owner->config()->disable_element_publish_button) {
104
            $config->removeComponentsByType('GridFieldDetailForm');
105
            $config->addComponent($obj = new VersionedDataObjectDetailsForm());
106
        }
107
108
        $fields->addFieldToTab('Root.Main', $gridField);
109
110
        return $fields;
111
    }
112
113
    /**
114
     * @return array
115
     */
116
    public function getAvailableTypes() {
117
        if (is_array($this->owner->config()->get('allowed_elements'))) {
118
            $list = $this->owner->config()->get('allowed_elements');
119
120 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...
121
                $sorted = array();
122
123
                foreach ($list as $class) {
124
                    $inst = singleton($class);
125
126
                    if ($inst->canCreate()) {
127
                        $sorted[$class] = singleton($class)->i18n_singular_name();
128
                    }
129
                }
130
131
                $list = $sorted;
132
                asort($list);
133
            }
134 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...
135
            $classes = ClassInfo::subclassesFor('BaseElement');
136
            $list = array();
137
            unset($classes['BaseElement']);
138
139
            $disallowedElements = (array) $this->owner->config()->get('disallowed_elements');
140
141
            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...
142
                $inst = singleton($class);
143
144
                if (!in_array($class, $disallowedElements) && $inst->canCreate()) {
145
                    $list[$class] = singleton($class)->i18n_singular_name();
146
                }
147
            }
148
149
            asort($list);
150
        }
151
152
        if (method_exists($this->owner, 'sortElementalOptions')) {
153
            $this->owner->sortElementalOptions($list);
154
        }
155
156
        return $list;
157
    }
158
159
    /**
160
     * Make sure there is always a WidgetArea sidebar for adding widgets
161
     *
162
     */
163
    public function onBeforeWrite()
164
    {
165
        // enable theme in case elements are being rendered with templates stored in theme folder
166
        $originalThemeEnabled = Config::inst()->get('SSViewer', 'theme_enabled');
167
        Config::inst()->update('SSViewer', 'theme_enabled', true);
168
169
        if(!$this->supportsElemental()) {
170
            return;
171
        }
172
173
        if ($this->owner->hasMethod('ElementArea') && Config::inst()->get(__CLASS__, 'copy_element_content_to_contentfield')) {
174
            $elements = $this->owner->ElementArea();
175
176
            if (!$elements->isInDB()) {
177
                $elements->write();
178
                $this->owner->ElementAreaID = $elements->ID;
179
            } else {
180
                // Copy widgets content to Content to enable search
181
                $searchableContent = array();
182
183
                Requirements::clear();
184
                foreach ($elements->Elements() as $element) {
185
                    if ($element->config()->exclude_from_content) {
186
                        continue;
187
                    }
188
189
                    $controller = $element->getController();
190
191
                    foreach ($elements->Items() as $element) {
192
                        $controller->init();
193
194
                        array_push($searchableContent, $controller->WidgetHolder());
195
                    }
196
                }
197
                Requirements::restore();
198
199
                $this->owner->Content = trim(implode(' ', $searchableContent));
200
            }
201
        } else {
202
            if(Config::inst()->get(__CLASS__, 'clear_contentfield')) {
203
                $this->owner->Content = '';
204
            }
205
        }
206
207
208
        // set theme_enabled back to what it was
209
        Config::inst()->update('SSViewer', 'theme_enabled', $originalThemeEnabled);
210
211
        parent::onBeforeWrite();
212
    }
213
214
    /**
215
     * @return boolean
216
     */
217
    public function supportsElemental() {
218
        if (method_exists($this->owner, 'includeElemental')) {
219
            return $this->owner->includeElemental();
220
        }
221
222
        if (is_a($this->owner, 'RedirectorPage')) {
223
            return false;
224
        } else if ($ignored = Config::inst()->get('ElementPageExtension', 'ignored_classes')) {
225
            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...
226
                if (is_a($this->owner, $check)) {
227
                    return false;
228
                }
229
            }
230
        }
231
232
        return true;
233
    }
234
235
    /**
236
     * If the page is duplicated, copy the widgets across too.
237
     *
238
     * Gets called twice from either direction, due to bad DataObject and SiteTree code, hence the weird if statement
239
     *
240
     * @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...
241
     */
242
    public function onAfterDuplicate($duplicatePage)
243
    {
244
        if ($this->owner->ID != 0 && $this->owner->ID < $duplicatePage->ID) {
245
            $originalWidgetArea = $this->owner->getComponent('ElementArea');
246
            $duplicateWidgetArea = $originalWidgetArea->duplicate(false);
247
            $duplicateWidgetArea->write();
248
            $duplicatePage->ElementAreaID = $duplicateWidgetArea->ID;
249
            $duplicatePage->write();
250
251 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...
252
                $duplicateWidget = $originalWidget->duplicate(true);
253
254
                // manually set the ParentID of each widget, so we don't get versioning issues
255
                DB::query(sprintf("UPDATE Widget SET ParentID = %d WHERE ID = %d", $duplicateWidgetArea->ID, $duplicateWidget->ID));
256
            }
257
        }
258
    }
259
260
    /**
261
     * If the page is duplicated across subsites, copy the widgets across too.
262
     *
263
     * @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...
264
     */
265
    public function onAfterDuplicateToSubsite($originalPage)
266
    {
267
        $originalWidgetArea = $originalPage->getComponent('ElementArea');
268
        $duplicateWidgetArea = $originalWidgetArea->duplicate(false);
269
        $duplicateWidgetArea->write();
270
        $this->owner->ElementAreaID = $duplicateWidgetArea->ID;
271
        $this->owner->write();
272
273 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...
274
            $duplicateWidget = $originalWidget->duplicate(true);
275
276
            // manually set the ParentID of each widget, so we don't get versioning issues
277
            DB::query(sprintf("UPDATE Widget SET ParentID = %d WHERE ID = %d", $duplicateWidgetArea->ID, $duplicateWidget->ID));
278
        }
279
    }
280
281
    /**
282
     * Publish
283
     */
284
    public function onAfterPublish()
285
    {
286
        if ($id = $this->owner->ElementAreaID) {
287
            $widgets = Versioned::get_by_stage('BaseElement', 'Stage', "ParentID = '$id'");
288
            $staged = array();
289
290
            foreach ($widgets as $widget) {
291
                $staged[] = $widget->ID;
292
293
                $widget->publish('Stage', 'Live');
294
            }
295
296
            // remove any elements that are on live but not in draft.
297
            $widgets = Versioned::get_by_stage('BaseElement', 'Live', "ParentID = '$id'");
298
299
            foreach ($widgets as $widget) {
300
                if (!in_array($widget->ID, $staged)) {
301
                    $widget->deleteFromStage('Live');
302
                }
303
            }
304
        }
305
    }
306
307
    /**
308
     * Roll back all changes if the parent page has a rollback event
309
     *
310
     * Only do rollback if it's the 'cancel draft changes' rollback, not a specific version
311
     * rollback.
312
     *
313
     * @param string $version
314
     * @return null
315
     */
316
    public function onBeforeRollback($version)
317
    {
318
        if ($version !== 'Live') {
319
            // we don't yet have a smart way of rolling back to a specific version
320
            return;
321
        }
322
        if ($id = $this->owner->ElementAreaID) {
323
            $widgets = Versioned::get_by_stage('BaseElement', 'Live', "ParentID = '$id'");
324
            $staged = array();
325
326
            foreach ($widgets as $widget) {
327
                $staged[] = $widget->ID;
328
329
                $widget->invokeWithExtensions('onBeforeRollback', $widget);
330
331
                $widget->publish("Live", "Stage", false);
332
333
                $widget->invokeWithExtensions('onAfterRollback', $widget);
334
            }
335
        }
336
    }
337
}
338