Completed
Push — master ( 59c799...149ad0 )
by Will
02:15
created

ElementPageExtension::supportsElemental()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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