Issues (124)

src/Forms/ElementalAreaField.php (1 issue)

Checks if the types of the passed arguments in a function/method call are compatible.

Bug Minor
1
<?php
2
3
namespace DNADesign\Elemental\Forms;
4
5
use BlocksPage;
6
use DNADesign\Elemental\Controllers\ElementalAreaController;
7
use DNADesign\Elemental\Models\BaseElement;
8
use DNADesign\Elemental\Models\ElementalArea;
9
use DNADesign\Elemental\Services\ElementTabProvider;
10
use SilverStripe\Control\Controller;
11
use SilverStripe\Core\Config\Config;
12
use SilverStripe\Core\Injector\Injector;
13
use SilverStripe\Forms\CompositeField;
14
use SilverStripe\Forms\FieldGroup;
15
use SilverStripe\Forms\FieldList;
16
use SilverStripe\Forms\FormField;
17
use SilverStripe\Forms\GridField\GridField;
18
use SilverStripe\Forms\TabSet;
19
use SilverStripe\ORM\DataObjectInterface;
20
use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass;
21
22
class ElementalAreaField extends GridField
23
{
24
    /**
25
     * @var ElementalArea $area
26
     */
27
    protected $area;
28
29
    /**
30
     * @var array $type
31
     */
32
    protected $types = [];
33
34
    /**
35
     * @var null
36
     */
37
    protected $inputType = null;
38
39
    protected $modelClassName = BaseElement::class;
40
41
    /**
42
     * @param string $name
43
     * @param ElementalArea $area
44
     * @param string[] $blockTypes
45
     */
46
    public function __construct($name, ElementalArea $area, array $blockTypes)
47
    {
48
        $this->setTypes($blockTypes);
49
50
        $config = new ElementalAreaConfig();
51
52
        if (!empty($blockTypes)) {
53
            /** @var GridFieldAddNewMultiClass $adder */
54
            $adder = Injector::inst()->create(GridFieldAddNewMultiClass::class);
55
            $adder->setClasses($blockTypes);
56
            $config->addComponent($adder);
57
        }
58
59
        // By default, no need for a title on the editor. If there is more than one area then use `setTitle` to describe
60
        parent::__construct($name, '', $area->Elements(), $config);
0 ignored issues
show
It seems like $area->Elements() can also be of type DNADesign\Elemental\Models\BaseElement[]; however, parameter $dataList of SilverStripe\Forms\GridF...ridField::__construct() does only seem to accept SilverStripe\ORM\SS_List|null, maybe add an additional type check? ( Ignorable by Annotation )

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

60
        parent::__construct($name, '', /** @scrutinizer ignore-type */ $area->Elements(), $config);
Loading history...
61
        $this->area = $area;
62
63
        $this->addExtraClass('element-editor__container no-change-track');
64
    }
65
66
    /**
67
     * @param array $types
68
     *
69
     * @return $this
70
     */
71
    public function setTypes($types)
72
    {
73
        $this->types = $types;
74
75
        return $this;
76
    }
77
78
    /**
79
     * @return array
80
     */
81
    public function getTypes()
82
    {
83
        $types = $this->types;
84
85
        $this->extend('updateGetTypes', $types);
86
87
        return $types;
88
    }
89
90
    /**
91
     * @return ElementalArea
92
     */
93
    public function getArea()
94
    {
95
        return $this->area;
96
    }
97
98
    /**
99
     * Overloaded to skip GridField implementation - this is copied from FormField.
100
     *
101
     * @param array $properties
102
     * @return \SilverStripe\ORM\FieldType\DBHTMLText|string
103
     */
104
    public function FieldHolder($properties = array())
105
    {
106
        $context = $this;
107
108
        if (count($properties)) {
109
            $context = $this->customise($properties);
110
        }
111
112
        return $context->renderWith($this->getFieldHolderTemplates());
113
    }
114
115
    public function getSchemaDataDefaults()
116
    {
117
        $schemaData = parent::getSchemaDataDefaults();
118
119
        $area = $this->getArea();
120
        $pageId = ($area && ($page = $area->getOwnerPage())) ? $page->ID : null;
121
        $schemaData['page-id'] = $pageId;
122
        $schemaData['elemental-area-id'] = $area ? (int) $area->ID : null;
123
124
        $allowedTypes = $this->getTypes();
125
        $schemaData['allowed-elements'] = array_keys($allowedTypes);
126
127
        return $schemaData;
128
    }
129
130
    /**
131
     * A getter method that seems redundant in that it is a function that returns a function,
132
     * however the returned closure is used in an array map function to return a complete FieldList
133
     * representing a read only view of the element passed in (to the closure).
134
     *
135
     * @return callable
136
     */
137
    protected function getReadOnlyBlockReducer()
138
    {
139
        return function (BaseElement $element) {
140
            $parentName = 'Element' . $element->ID;
141
            $elementFields = $element->getCMSFields();
142
143
            // Obtain highest impact fields for a summary (e.g. Title & Content)
144
            foreach ($elementFields as $field) {
145
                if (is_object($field) && $field instanceof TabSet) {
146
                    // Assign the fields of the first Tab in the TabSet - most regularly 'Root.Main'
147
                    $elementFields = $field->FieldList()->first()->FieldList();
148
                    break;
149
                }
150
            }
151
152
            // Set values (before names don't match anymore)
153
            $elementFields->setValues($element->getQueriedDatabaseFields());
154
155
            // Combine into an appropriately named group
156
            $elementGroup = FieldGroup::create($elementFields);
157
            $elementGroup->setForm($this->getForm());
158
            $elementGroup->setName($parentName);
159
            $elementGroup->addExtraClass('elemental-area__element--historic');
160
161
            // Also set the important data for the rendering Component
162
            $elementGroup->setSchemaData([
163
                'data' => [
164
                    'ElementID' => $element->ID,
165
                    'ElementType' => $element->getType(),
166
                    'ElementIcon' => $element->config()->get('icon'),
167
                    'ElementTitle' => $element->Title,
168
                    // @todo: Change this to block history permalink when that functionality becomes available.
169
                    'ElementEditLink' => Controller::join_links(
170
                        // Always get the edit link for the block directly, not the in-line edit form if supported
171
                        $element->CMSEditLink(true),
172
                        // @todo make this auto-permalinking work somehow
173
                        '#Root_History'
174
                    ),
175
                ],
176
            ]);
177
178
            return $elementGroup;
179
        };
180
    }
181
182
    /**
183
     * Provides a readonly representation of the GridField (superclass) Uses a reducer
184
     * {@see ElementalAreaField::getReadOnlyBlockReducer()} to fetch a read only representation of the listed class
185
     * {@see GridField::getModelClass()}
186
     *
187
     * @return CompositeField
188
     */
189
    public function performReadonlyTransformation()
190
    {
191
        /** @var CompositeField $readOnlyField */
192
        $readOnlyField = $this->castedCopy(CompositeField::class);
193
        $blockReducer = $this->getReadOnlyBlockReducer();
194
        $readOnlyField->setChildren(
195
            FieldList::create(array_map($blockReducer, $this->getArea()->Elements()->toArray()))
196
        );
197
198
        $readOnlyField = $readOnlyField->performReadonlyTransformation();
199
200
        // Ensure field names are unique between elements on parent form but only after transformations have been
201
        // performed
202
        /** @var FieldGroup $elementForm */
203
        foreach ($readOnlyField->getChildren() as $elementForm) {
204
            $parentName = $elementForm->getName();
205
            $elementForm->getChildren()->recursiveWalk(function (FormField $field) use ($parentName) {
206
                $field->setName($parentName . '_' . $field->getName());
207
            });
208
        }
209
210
        return $readOnlyField
211
            ->setReadOnly(true)
212
            ->setName($this->getName())
213
            ->addExtraClass('elemental-area--read-only');
214
    }
215
216
    public function setSubmittedValue($value, $data = null)
217
    {
218
        // Content comes through as a JSON encoded list through a hidden field.
219
        return $this->setValue(json_decode($value, true));
220
    }
221
222
    public function saveInto(DataObjectInterface $dataObject)
223
    {
224
        /** @var BlocksPage $dataObject */
225
        parent::saveInto($dataObject);
226
227
        $elementData = $this->Value();
228
        $idPrefixLength = strlen(sprintf(ElementalAreaController::FORM_NAME_TEMPLATE, ''));
229
230
        if (!$elementData) {
231
            return;
232
        }
233
234
        foreach ($elementData as $form => $data) {
235
            // Extract the ID
236
            $elementId = (int) substr($form, $idPrefixLength);
237
238
            /** @var BaseElement $element */
239
            $element = $this->getArea()->Elements()->byID($elementId);
240
241
            if (!$element) {
242
                // Ignore invalid elements
243
                continue;
244
            }
245
246
            $data = ElementalAreaController::removeNamespacesFromFields($data, $element->ID);
247
248
            $element->updateFromFormData($data);
249
            $element->write();
250
        }
251
    }
252
}
253