Issues (216)

src/Forms/ElementalAreaField.php (2 issues)

1
<?php
2
3
namespace DNADesign\Elemental\Forms;
4
5
use DNADesign\Elemental\Controllers\ElementalAreaController;
6
use DNADesign\Elemental\Models\BaseElement;
7
use DNADesign\Elemental\Models\ElementalArea;
8
use SilverStripe\Control\Controller;
9
use SilverStripe\Core\Injector\Injector;
10
use SilverStripe\Forms\CompositeField;
11
use SilverStripe\Forms\FieldGroup;
12
use SilverStripe\Forms\FieldList;
13
use SilverStripe\Forms\FormField;
14
use SilverStripe\Forms\GridField\GridField;
15
use SilverStripe\Forms\TabSet;
16
use SilverStripe\ORM\DataObjectInterface;
17
use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass;
18
19
class ElementalAreaField extends GridField
20
{
21
    /**
22
     * @var ElementalArea $area
23
     */
24
    protected $area;
25
26
    /**
27
     * @var array $type
28
     */
29
    protected $types = [];
30
31
    /**
32
     * @var null
33
     */
34
    protected $inputType = null;
35
36
    protected $modelClassName = BaseElement::class;
37
38
    /**
39
     * @param string $name
40
     * @param ElementalArea $area
41
     * @param string[] $blockTypes
42
     */
43
    public function __construct($name, ElementalArea $area, array $blockTypes)
44
    {
45
        $this->setTypes($blockTypes);
46
47
        $config = new ElementalAreaConfig();
48
49
        if (!empty($blockTypes)) {
50
            /** @var GridFieldAddNewMultiClass $adder */
51
            $adder = Injector::inst()->create(GridFieldAddNewMultiClass::class);
52
            $adder->setClasses($blockTypes);
53
            $config->addComponent($adder);
54
        }
55
56
        // By default, no need for a title on the editor. If there is more than one area then use `setTitle` to describe
57
        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

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