Passed
Push — master ( eb27fd...f091a3 )
by
unknown
02:36
created

ElementalAreasExtension::updateCMSFields()   A

Complexity

Conditions 6
Paths 5

Size

Total Lines 35
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 16
nc 5
nop 1
dl 0
loc 35
rs 9.1111
c 0
b 0
f 0
1
<?php
2
3
namespace DNADesign\Elemental\Extensions;
4
5
use DNADesign\Elemental\Forms\ElementalAreaField;
6
use DNADesign\Elemental\Models\BaseElement;
7
use DNADesign\Elemental\Models\ElementalArea;
8
use SilverStripe\CMS\Model\RedirectorPage;
9
use SilverStripe\CMS\Model\SiteTree;
10
use SilverStripe\CMS\Model\VirtualPage;
11
use SilverStripe\Core\ClassInfo;
12
use SilverStripe\Core\Config\Config;
13
use SilverStripe\Forms\FieldList;
14
use SilverStripe\Forms\LiteralField;
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
 * private static $cascade_duplicates = array(
37
 *     'ElementalArea1',
38
 *     'ElementalArea2'
39
 * );
40
 *
41
 * @package elemental
42
 */
43
class ElementalAreasExtension extends DataExtension
44
{
45
    /**
46
     * @config
47
     *
48
     * @var array $ignored_classes Classes to ignore adding elements too.
49
     */
50
    private static $ignored_classes = [];
51
52
    /**
53
     * @config
54
     *
55
     * On saving the element area, should Elemental reset the main website
56
     * `$Content` field.
57
     *
58
     * @var boolean
59
     */
60
    private static $clear_contentfield = false;
61
62
    /**
63
     * Get the available element types for this page type,
64
     *
65
     * Uses allowed_elements, stop_element_inheritance, disallowed_elements in
66
     * order to get to correct list.
67
     *
68
     * @return array
69
     */
70
    public function getElementalTypes()
71
    {
72
        $config = $this->owner->config();
73
74
        if (is_array($config->get('allowed_elements'))) {
75
            if ($config->get('stop_element_inheritance')) {
76
                $availableClasses = $config->get('allowed_elements', Config::UNINHERITED);
77
            } else {
78
                $availableClasses = $config->get('allowed_elements');
79
            }
80
        } else {
81
            $availableClasses = ClassInfo::subclassesFor(BaseElement::class);
82
        }
83
84
        $disallowedElements = (array) $config->get('disallowed_elements');
85
        $list = array();
86
87
        foreach ($availableClasses as $availableClass) {
88
            /** @var BaseElement $inst */
89
            $inst = singleton($availableClass);
90
91
            if (!in_array($availableClass, $disallowedElements) && $inst->canCreate()) {
92
                if ($inst->hasMethod('canCreateElement') && !$inst->canCreateElement()) {
0 ignored issues
show
Bug introduced by Will Rossiter
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

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