Passed
Push — master ( a1de92...1a465b )
by Robbie
03:52
created

src/Forms/ElementalAreaField.php (1 issue)

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()->icon,
167
                    'ElementTitle' => $element->Title,
168
                    // @todo: Change this to block history permalink when that functionality becomes available.
169
                    'ElementEditLink' => $element->CMSEditLink()
170
                ]
171
            ]);
172
173
            return $elementGroup;
174
        };
175
    }
176
177
    /**
178
     * Provides a readonly representation of the GridField (superclass) Uses a reducer
179
     * {@see ElementalAreaField::getReadOnlyBlockReducer()} to fetch a read only representation of the listed class
180
     * {@see GridField::getModelClass()}
181
     *
182
     * @return CompositeField
183
     */
184
    public function performReadonlyTransformation()
185
    {
186
        /** @var CompositeField $readOnlyField */
187
        $readOnlyField = $this->castedCopy(CompositeField::class);
188
        $blockReducer = $this->getReadOnlyBlockReducer();
189
        $readOnlyField->setChildren(
190
            FieldList::create(array_map($blockReducer, $this->getArea()->Elements()->toArray()))
191
        );
192
193
        $readOnlyField = $readOnlyField->performReadonlyTransformation();
194
195
        // Ensure field names are unique between elements on parent form but only after transformations have been
196
        // performed
197
        /** @var FieldGroup $elementForm */
198
        foreach ($readOnlyField->getChildren() as $elementForm) {
199
            $parentName = $elementForm->getName();
200
            $elementForm->getChildren()->recursiveWalk(function (FormField $field) use ($parentName) {
201
                $field->setName($parentName . '_' . $field->getName());
202
            });
203
        }
204
205
        return $readOnlyField
206
            ->setReadOnly(true)
207
            ->setName($this->getName())
208
            ->addExtraClass('elemental-area--read-only');
209
    }
210
211
    public function setSubmittedValue($value, $data = null)
212
    {
213
        // Content comes through as a JSON encoded list through a hidden field.
214
        return $this->setValue(json_decode($value, true));
215
    }
216
217
    public function saveInto(DataObjectInterface $dataObject)
218
    {
219
        /** @var BlocksPage $dataObject */
220
        parent::saveInto($dataObject);
221
222
        $elementData = $this->Value();
223
        $idPrefixLength = strlen(sprintf(ElementalAreaController::FORM_NAME_TEMPLATE, ''));
224
225
        if (!$elementData) {
226
            return;
227
        }
228
229
        foreach ($elementData as $form => $data) {
230
            // Extract the ID
231
            $elementId = (int) substr($form, $idPrefixLength);
232
233
            /** @var BaseElement $element */
234
            $element = $this->getArea()->Elements()->byID($elementId);
235
236
            if (!$element) {
237
                // Ignore invalid elements
238
                continue;
239
            }
240
241
            $data = ElementalAreaController::removeNamespacesFromFields($data, $element->ID);
242
243
            $element->updateFromFormData($data);
244
            $element->write();
245
        }
246
    }
247
}
248