Passed
Pull Request — master (#221)
by Ingo
03:53
created

ElementalAreasExtension::supportsElemental()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 21
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 21
rs 7.1428
c 0
b 0
f 0
cc 8
eloc 11
nc 9
nop 0
1
<?php
2
3
namespace DNADesign\Elemental\Extensions;
4
5
use DNADesign\Elemental\Models\BaseElement;
6
use DNADesign\Elemental\Models\ElementalArea;
7
use DNADesign\Elemental\ElementalEditor;
8
use SilverStripe\CMS\Model\SiteTree;
9
use SilverStripe\Core\ClassInfo;
10
use SilverStripe\Core\Config\Config;
11
use SilverStripe\Forms\FieldList;
12
use SilverStripe\Forms\LiteralField;
13
use SilverStripe\CMS\Model\RedirectorPage;
14
use SilverStripe\CMS\Model\VirtualPage;
15
use SilverStripe\ORM\DataExtension;
16
17
/**
18
 * This extension handles most of the relationships between pages and element
19
 * area, it doesn't add an ElementArea to the page however. Because of this,
20
 * developers can add multiple {@link ElementArea} areas to to a page.
21
 *
22
 * If you want multiple ElementalAreas add them as has_ones, add this extensions
23
 * and MAKE SURE you don't forget to add ElementAreas to $owns, otherwise they
24
 * will never publish
25
 *
26
 * private static $has_one = array(
27
 *     'ElementalArea1' => ElementalArea::class,
28
 *     'ElementalArea2' => ElementalArea::class
29
 * );
30
 *
31
 * private static $owns = array(
32
 *     'ElementalArea1',
33
 *     'ElementalArea2'
34
 * );
35
 *
36
 * @package elemental
37
 */
38
class ElementalAreasExtension extends DataExtension
39
{
40
    /**
41
     * @config
42
     *
43
     * @var array $ignored_classes Classes to ignore adding elements too.
44
     */
45
    private static $ignored_classes = [];
46
47
    /**
48
     * @config
49
     *
50
     * On saving the element area, should Elemental reset the main website
51
     * `$Content` field.
52
     *
53
     * @var boolean
54
     */
55
    private static $clear_contentfield = false;
56
57
    /**
58
     * Get the available element types for this page type,
59
     *
60
     * Uses allowed_elements, stop_element_inheritance, disallowed_elements in
61
     * order to get to correct list.
62
     *
63
     * @return array
64
     */
65
    public function getElementalTypes()
66
    {
67
        $config = $this->owner->config();
68
69
        if (is_array($config->get('allowed_elements'))) {
70
            if ($config->get('stop_element_inheritance')) {
71
                $availableClasses = $config->get('allowed_elements', Config::UNINHERITED);
72
            } else {
73
                $availableClasses = $config->get('allowed_elements');
74
            }
75
        } else {
76
            $availableClasses = ClassInfo::subclassesFor(BaseElement::class);
77
        }
78
79
        $disallowedElements = (array) $config->get('disallowed_elements');
80
        $list = array();
81
82
        foreach ($availableClasses as $availableClass) {
83
            /** @var BaseElement $inst */
84
            $inst = singleton($availableClass);
85
86
            if (!in_array($availableClass, $disallowedElements) && $inst->canCreate()) {
87
                if ($inst->hasMethod('canCreateElement') && !$inst->canCreateElement()) {
0 ignored issues
show
Bug introduced by
The method canCreateElement() does not exist on DNADesign\Elemental\Models\BaseElement. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

87
                if ($inst->hasMethod('canCreateElement') && !$inst->/** @scrutinizer ignore-call */ canCreateElement()) {
Loading history...
88
                    continue;
89
                }
90
91
                $list[$availableClass] = $inst->getType();
92
            }
93
        }
94
95
        if ($config->get('sort_types_alphabetically') !== false) {
96
            asort($list);
97
        }
98
99
        if (isset($list[BaseElement::class])) {
100
            unset($list[BaseElement::class]);
101
        }
102
103
        $this->owner->invokeWithExtensions('updateAvailableTypesForClass', $class, $list);
104
105
        return $list;
106
    }
107
108
    /**
109
     * Returns an array of the relation names to ElementAreas. Ignores any
110
     * has_one fields named `Parent` as that would indicate that this is child
111
     * of an existing area
112
     *
113
     * @return array
114
     */
115
    public function getElementalRelations()
116
    {
117
        $hasOnes = $this->owner->hasOne();
118
119
        if (!$hasOnes) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $hasOnes 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...
120
            return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type array.
Loading history...
121
        }
122
123
        $elementalAreaRelations = [];
124
125
        foreach ($hasOnes as $hasOneName => $hasOneClass) {
126
            if ($hasOneName === 'Parent' || $hasOneName === 'ParentID') {
127
                continue;
128
            }
129
130
            if ($hasOneClass == ElementalArea::class || is_subclass_of($hasOneClass, ElementalArea::class)) {
131
                $elementalAreaRelations[] = $hasOneName;
132
            }
133
        }
134
135
        return $elementalAreaRelations;
136
    }
137
138
    /**
139
     * Setup the CMS Fields
140
     *
141
     * @param FieldList
142
     */
143
    public function updateCMSFields(FieldList $fields)
144
    {
145
        if (!$this->supportsElemental()) {
146
            return;
147
        }
148
149
        // add an empty holder for content as some module explicitly use insert
150
        // after content.
151
        $fields->replaceField('Content', new LiteralField('Content', ''));
152
        $elementalAreaRelations = $this->owner->getElementalRelations();
153
154
        foreach ($elementalAreaRelations as $eaRelationship) {
155
            $key = $eaRelationship . 'ID';
156
157
            // remove the scaffold dropdown
158
            $fields->removeByName($key);
159
160
            // remove the field, but don't add anything.
161
            if (!$this->owner->isInDb()) {
162
                continue;
163
            }
164
165
            $area = $this->owner->$eaRelationship();
166
167
            // if area isn't in the database then force a write so the blocks have a parent ID.
168
            if (!$area->isInDb()) {
169
                $area->write();
170
171
                $this->owner->{$key} = $area->ID;
172
                $this->owner->write();
173
            }
174
175
            $editor = ElementalEditor::create($eaRelationship, $area);
176
            $editor->setTypes($this->getElementalTypes());
177
178
            if ($this->owner instanceof SiteTree && $fields->findOrMakeTab('Root.Main')->fieldByName('Metadata')) {
179
                $fields->addFieldToTab('Root.Main', $editor->getField(), 'Metadata');
180
            } else {
181
                $fields->addFieldToTab('Root.Main', $editor->getField());
182
            }
183
        }
184
185
        return $fields;
186
    }
187
188
    /**
189
     * Make sure there is always an ElementalArea for adding Elements
190
     *
191
     */
192
    public function onBeforeWrite()
193
    {
194
        parent::onBeforeWrite();
195
196
        if (!$this->supportsElemental()) {
197
            return;
198
        }
199
200
        $elementalAreaRelations = $this->owner->getElementalRelations();
201
202
        foreach ($elementalAreaRelations as $eaRelationship) {
203
            $areaID = $eaRelationship . 'ID';
204
205
            if (!$this->owner->$areaID) {
206
                $area = ElementalArea::create();
207
                $area->OwnerClassName = $this->owner->ClassName;
208
                $area->write();
209
                $this->owner->$areaID = $area->ID;
210
            } elseif ($area = ElementalArea::get()->filter('ID', $this->owner->$areaID)->first()) {
211
                $area->write();
212
            }
213
        }
214
215
        if (Config::inst()->get(self::class, 'clear_contentfield')) {
216
            $this->owner->Content = '';
217
        }
218
    }
219
220
    /**
221
     * @return boolean
222
     */
223
    public function supportsElemental()
224
    {
225
        if ($this->owner->hasMethod('includeElemental')) {
226
            $res = $this->owner->includeElemental();
227
228
            if ($res !== null) {
229
                return $res;
230
            }
231
        }
232
233
        if (is_a($this->owner, RedirectorPage::class) || is_a($this->owner, VirtualPage::class)) {
234
            return false;
235
        } elseif ($ignored = Config::inst()->get(ElementalPageExtension::class, 'ignored_classes')) {
236
            foreach ($ignored as $check) {
237
                if (is_a($this->owner, $check)) {
238
                    return false;
239
                }
240
            }
241
        }
242
243
        return true;
244
    }
245
}
246