Passed
Pull Request — 4 (#930)
by Steve
04:01
created

ElementalAreaField::saveInto()   B

Complexity

Conditions 10
Paths 28

Size

Total Lines 59
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 1 Features 0
Metric Value
cc 10
eloc 32
c 3
b 1
f 0
nc 28
nop 1
dl 0
loc 59
rs 7.6666

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace DNADesign\Elemental\Forms;
4
5
use BlocksPage;
0 ignored issues
show
Bug introduced by
The type BlocksPage was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
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 SilverStripe\ORM\ValidationException;
21
use SilverStripe\ORM\ValidationResult;
22
use Symbiote\GridFieldExtensions\GridFieldAddNewMultiClass;
23
24
class ElementalAreaField extends GridField
25
{
26
    /**
27
     * @var ElementalArea $area
28
     */
29
    protected $area;
30
31
    /**
32
     * @var array $type
33
     */
34
    protected $types = [];
35
36
    /**
37
     * @var null
38
     */
39
    protected $inputType = null;
40
41
    protected $modelClassName = BaseElement::class;
42
43
    /**
44
     * @param string $name
45
     * @param ElementalArea $area
46
     * @param string[] $blockTypes
47
     */
48
    public function __construct($name, ElementalArea $area, array $blockTypes)
49
    {
50
        $this->setTypes($blockTypes);
51
52
        $config = new ElementalAreaConfig();
53
54
        if (!empty($blockTypes)) {
55
            /** @var GridFieldAddNewMultiClass $adder */
56
            $adder = Injector::inst()->create(GridFieldAddNewMultiClass::class);
57
            $adder->setClasses($blockTypes);
58
            $config->addComponent($adder);
59
        }
60
61
        // By default, no need for a title on the editor. If there is more than one area then use `setTitle` to describe
62
        parent::__construct($name, '', $area->Elements(), $config);
0 ignored issues
show
Bug introduced by
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

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