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

code/extensions/ElementPageExtension.php (4 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
/**
4
 * @package elemental
5
 */
6
class ElementPageExtension extends DataExtension
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';
15
16
    /**
17
     * @config
18
     *
19
     * @var array $ignored_classes Classes to ignore adding elements too.
20
     */
21
    private static $ignored_classes = array();
22
23
    /**
24
     * @var array $db
25
     */
26
    private static $db = array();
27
28
    private static $has_one = array(
29
        'ElementArea' => 'ElementalArea'
30
    );
31
32
    /**
33
     * Setup the CMS Fields
34
     *
35
     * @param FieldList
36
     */
37
    public function updateCMSFields(FieldList $fields)
38
    {
39
        // redirector pages should not have elements
40
        if (is_a($this->owner, 'RedirectorPage')) {
41
            return;
42 View Code Duplication
        } else if ($ignored = Config::inst()->get('ElementPageExtension', 'ignored_classes')) {
0 ignored issues
show
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...
43
            foreach ($ignored as $check) {
0 ignored issues
show
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...
44
                if (is_a($this->owner, $check)) {
45
                    return;
46
                }
47
            }
48
        }
49
50
        // add an empty holder for content as some module explicitly use insert
51
        // after content.
52
        $fields->replaceField('Content', new LiteralField('Content', ''));
53
54
        $adder = new ElementalGridFieldAddNewMultiClass();
55
56
        $list = $this->getAvailableTypes();
57
        $adder->setClasses($list);
58
59
        $area = $this->owner->ElementArea();
60
61
        if (!$area->exists() || !$area->isInDB()) {
62
            $area->write();
63
64
            $this->owner->ElementAreaID = $area->ID;
65
            $this->owner->write();
66
        }
67
68
        $gridField = GridField::create('ElementArea',
69
            Config::inst()->get("ElementPageExtension", 'elements_title'),
70
            $this->owner->ElementArea()->Elements(),
71
            GridFieldConfig_RelationEditor::create()
72
                ->removeComponentsByType('GridFieldAddNewButton')
73
                ->removeComponentsByType('GridFieldAddExistingAutocompleter')
74
                ->removeComponentsByType('GridFieldDeleteAction')
75
                ->addComponent(new GridFieldDeleteAction(false))
76
                ->addComponent($adder)
77
                ->addComponent(new GridFieldSortableRows('Sort'))
78
        );
79
80
        $config = $gridField->getConfig();
81
        $paginator = $config->getComponentByType('GridFieldPaginator');
82
        $paginator->setItemsPerPage(100);
83
84
        $config->removeComponentsByType('GridFieldDetailForm');
85
        $config->addComponent(new VersionedDataObjectDetailsForm());
86
87
        $fields->addFieldToTab('Root.Main', $gridField, 'Metadata');
88
89
        return $fields;
90
    }
91
92
    public function getAvailableTypes() {
0 ignored issues
show
The return type could not be reliably inferred; please add a @return annotation.

Our type inference engine in quite powerful, but sometimes the code does not provide enough clues to go by. In these cases we request you to add a @return annotation as described here.

Loading history...
93
        if (is_array($this->owner->config()->get('allowed_elements'))) {
94
            $list = $this->owner->config()->get('allowed_elements');
95 View Code Duplication
        } else {
96
            $classes = ClassInfo::subclassesFor('BaseElement');
97
            $list = array();
98
            unset($classes['BaseElement']);
99
100
            foreach ($classes as $class) {
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 incase 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 View Code Duplication
        if ($ignored = Config::inst()->get('ElementPageExtension', 'ignored_classes')) {
0 ignored issues
show
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...
128
            foreach ($ignored as $check) {
129
                if (is_a($this->owner, $check)) {
130
                    return;
131
                }
132
            }
133
        }
134
135
        if ($this->owner->hasMethod('ElementArea')) {
136
            $elements = $this->owner->ElementArea();
137
138
            if (!$elements->isInDB()) {
139
                $elements->write();
140
                $this->owner->ElementAreaID = $elements->ID;
141
            } else {
142
                // Copy widgets content to Content to enable search
143
                $searchableContent = array();
144
145
                foreach ($elements->Items() as $element) {
146
                    if ($element->config()->exclude_from_content) {
147
                        continue;
148
                    }
149
150
                    $controller = $element->getController();
151
                    $controller->init();
152
153
                    array_push($searchableContent, $controller->WidgetHolder());
154
                }
155
156
                $this->owner->Content = trim(implode(' ', $searchableContent));
157
            }
158
        }
159
160
161
        // set theme_enabled back to what it was
162
        Config::inst()->update('SSViewer', 'theme_enabled', $originalThemeEnabled);
163
164
        parent::onBeforeWrite();
165
    }
166
167
    /**
168
     * If the page is duplicated, copy the widgets across too.
169
     * Gets called twice from either direction, due to bad DataObject
170
     * and SiteTree code, hence the weird if statement
171
     *
172
     * @return Page The duplicated page
173
     */
174
    public function onAfterDuplicate($duplicatePage)
175
    {
176
        if ($this->owner->ID != 0 && $this->owner->ID < $duplicatePage->ID) {
177
            $originalWidgetArea = $this->owner->getComponent('ElementArea');
178
            $duplicateWidgetArea = $originalWidgetArea->duplicate(false);
179
            $duplicateWidgetArea->write();
180
            $duplicatePage->ElementAreaID = $duplicateWidgetArea->ID;
181
            $duplicatePage->write();
182
183 View Code Duplication
            foreach ($originalWidgetArea->Items() as $originalWidget) {
184
                $duplicateWidget = $originalWidget->duplicate(true);
185
186
                // manually set the ParentID of each widget, so we don't get versioning issues
187
                DB::query(sprintf("UPDATE Widget SET ParentID = %d WHERE ID = %d", $duplicateWidgetArea->ID, $duplicateWidget->ID));
188
            }
189
        }
190
    }
191
192
    /**
193
     * If the page is duplicated across subsites, copy the widgets across too.
194
     *
195
     * @return Page The duplicated page
196
     */
197
    public function onAfterDuplicateToSubsite($originalPage)
198
    {
199
        $originalWidgetArea = $originalPage->getComponent('ElementArea');
200
        $duplicateWidgetArea = $originalWidgetArea->duplicate(false);
201
        $duplicateWidgetArea->write();
202
        $this->owner->ElementAreaID = $duplicateWidgetArea->ID;
203
        $this->owner->write();
204
205 View Code Duplication
        foreach ($originalWidgetArea->Items() as $originalWidget) {
206
            $duplicateWidget = $originalWidget->duplicate(true);
207
208
            // manually set the ParentID of each widget, so we don't get versioning issues
209
            DB::query(sprintf("UPDATE Widget SET ParentID = %d WHERE ID = %d", $duplicateWidgetArea->ID, $duplicateWidget->ID));
210
        }
211
    }
212
213
    public function onAfterPublish()
214
    {
215
        if ($id = $this->owner->ElementAreaID) {
216
            $widgets = Versioned::get_by_stage('BaseElement', 'Stage', "ParentID = '$id'");
217
            $staged = array();
218
219
            foreach ($widgets as $widget) {
220
                $staged[] = $widget->ID;
221
222
                $widget->publish('Stage', 'Live');
223
            }
224
225
            // remove any elements that are on live but not in draft.
226
            $widgets = Versioned::get_by_stage('BaseElement', 'Live', "ParentID = '$id'");
227
228
            foreach ($widgets as $widget) {
229
                if (!in_array($widget->ID, $staged)) {
230
                    $widget->deleteFromStage('Live');
231
                }
232
            }
233
        }
234
    }
235
236
    /**
237
     * Roll back all changes if the parent page has a rollback event
238
     *
239
     * Only do rollback if it's the 'cancel draft changes' rollback, not a specific version
240
     * rollback.
241
     *
242
     * @param string $version
243
     * @return null
244
     */
245
    public function onBeforeRollback($version)
246
    {
247
        if ($version !== 'Live') {
248
            // we don't yet have a smart way of rolling back to a specific version
249
            return;
250
        }
251
        if ($id = $this->owner->ElementAreaID) {
252
            $widgets = Versioned::get_by_stage('BaseElement', 'Live', "ParentID = '$id'");
253
            $staged = array();
254
255
            foreach ($widgets as $widget) {
256
                $staged[] = $widget->ID;
257
258
                $widget->invokeWithExtensions('onBeforeRollback', $widget);
259
260
                $widget->publish("Live", "Stage", false);
261
262
                $widget->invokeWithExtensions('onAfterRollback', $widget);
263
            }
264
        }
265
    }
266
}
267