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

src/Controllers/ElementalAreaController.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\Controllers;
4
5
use DNADesign\Elemental\Forms\EditFormFactory;
6
use DNADesign\Elemental\Models\BaseElement;
7
use DNADesign\Elemental\Services\ElementTypeRegistry;
8
use Exception;
9
use Psr\Log\LoggerInterface;
10
use SilverStripe\CMS\Controllers\CMSMain;
11
use SilverStripe\Control\HTTPRequest;
12
use SilverStripe\Control\HTTPResponse;
13
use SilverStripe\Control\HTTPResponse_Exception;
14
use SilverStripe\Core\Convert;
15
use SilverStripe\Core\Injector\Injector;
16
use SilverStripe\Forms\Form;
17
use SilverStripe\Security\SecurityToken;
18
19
/**
20
 * Controller for "ElementalArea" - handles loading and saving of in-line edit forms in an elemental area in admin
21
 */
22
class ElementalAreaController extends CMSMain
23
{
24
    const FORM_NAME_TEMPLATE = 'ElementForm_%s';
25
26
    private static $url_segment = 'elemental-area';
27
28
    private static $ignore_menuitem = true;
29
30
    private static $url_handlers = [
31
        // API access points with structured data
32
        'POST api/saveForm/$ID' => 'apiSaveForm',
33
        '$FormName/field/$FieldName' => 'formAction',
34
    ];
35
36
    private static $allowed_actions = [
37
        'elementForm',
38
        'schema',
39
        'apiSaveForm',
40
        'formAction',
41
    ];
42
43
    public function getClientConfig()
44
    {
45
        $clientConfig = parent::getClientConfig();
46
        $clientConfig['form']['elementForm'] = [
47
            'schemaUrl' => $this->Link('schema/elementForm'),
48
            'saveUrl' => $this->Link('api/saveForm'),
49
            'saveMethod' => 'post',
50
            'payloadFormat' => 'json',
51
            'formNameTemplate' => sprintf(static::FORM_NAME_TEMPLATE, '{id}'),
52
        ];
53
54
        // Configuration that is available per element type
55
        $clientConfig['elementTypes'] = ElementTypeRegistry::generate()->getDefinitions();
56
57
        return $clientConfig;
58
    }
59
60
    /**
61
     * @param HTTPRequest|null $request
62
     * @return Form
63
     * @throws HTTPResponse_Exception
64
     */
65
    public function elementForm(HTTPRequest $request = null)
66
    {
67
        // Get ID either from posted back value, or url parameter
68
        if (!$request) {
69
            $this->jsonError(400);
70
            return null;
71
        }
72
        $id = $request->param('ID');
73
        if (!$id) {
74
            $this->jsonError(400);
75
            return null;
76
        }
77
        return $this->getElementForm($id) ?: $this->jsonError(404);
78
    }
79
80
    /**
81
     * @param int $elementID
82
     * @return Form|null Returns null if no element exists for the given ID
83
     */
84
    public function getElementForm($elementID)
85
    {
86
        $scaffolder = Injector::inst()->get(EditFormFactory::class);
87
        $element = BaseElement::get()->byID($elementID);
88
89
        if (!$element) {
90
            return null;
91
        }
92
93
        /** @var Form $form */
94
        $form = $scaffolder->getForm(
95
            $this,
96
            sprintf(static::FORM_NAME_TEMPLATE, $elementID),
97
            ['Record' => $element]
98
        );
99
100
        if (!$element->canEdit()) {
101
            $form->makeReadonly();
102
        }
103
104
        $form->addExtraClass('element-editor-editform__form');
105
106
        return $form;
107
    }
108
109
    /**
110
     * Save an inline edit form for a block
111
     *
112
     * @param HTTPRequest $request
113
     * @return HTTPResponse|null JSON encoded string or null if an exception is thrown
114
     * @throws HTTPResponse_Exception
115
     */
116
    public function apiSaveForm(HTTPRequest $request)
117
    {
118
        // Validate required input data
119
        if (!isset($this->urlParams['ID'])) {
120
            $this->jsonError(400);
121
            return null;
122
        }
123
124
        $data = Convert::json2array($request->getBody());
125
        if (empty($data)) {
126
            $this->jsonError(400);
127
            return null;
128
        }
129
130
        // Inject request body as request vars
131
        foreach ($data as $key => $value) {
132
            $request->offsetSet($key, $value);
133
        }
134
135
        // Check security token
136
        if (!SecurityToken::inst()->checkRequest($request)) {
137
            $this->jsonError(400);
138
            return null;
139
        }
140
141
        /** @var BaseElement $element */
142
        $element = BaseElement::get()->byID($this->urlParams['ID']);
143
        // Ensure the element can be edited by the current user
144
        if (!$element || !$element->canEdit()) {
145
            $this->jsonError(403);
146
            return null;
147
        }
148
149
        // Remove the pseudo namespaces that were added by the form factory
150
        $data = $this->removeNamespacesFromFields($data, $element->ID);
0 ignored issues
show
It seems like $data can also be of type boolean; however, parameter $data of DNADesign\Elemental\Cont...eNamespacesFromFields() does only seem to accept array, 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

150
        $data = $this->removeNamespacesFromFields(/** @scrutinizer ignore-type */ $data, $element->ID);
Loading history...
151
152
        try {
153
            $updated = false;
154
155
            $element->updateFromFormData($data);
156
            // Check if anything will actually be changed before writing
157
            if ($element->isChanged()) {
158
                $element->write();
159
                // Track changes so we can return to the client
160
                $updated = true;
161
            }
162
        } catch (Exception $ex) {
163
            Injector::inst()->get(LoggerInterface::class)->debug($ex->getMessage());
164
165
            $this->jsonError(500);
166
            return null;
167
        }
168
169
        $body = Convert::raw2json([
170
            'status' => 'success',
171
            'updated' => $updated,
172
        ]);
173
        return HTTPResponse::create($body)->addHeader('Content-Type', 'application/json');
174
    }
175
176
    /**
177
     * Provides action control for form fields that are request handlers when they're used in an in-line edit form.
178
     *
179
     * Eg. UploadField
180
     *
181
     * @param HTTPRequest $request
182
     * @return array|HTTPResponse|\SilverStripe\Control\RequestHandler|string
183
     */
184
    public function formAction(HTTPRequest $request)
185
    {
186
        $formName = $request->param('FormName');
187
188
        // Get the element ID from the form name
189
        $id = substr($formName, strlen(sprintf(self::FORM_NAME_TEMPLATE, '')));
190
        $form = $this->getElementForm($id);
191
192
        $field = $form->getRequestHandler()->handleField($request);
193
194
        return $field->handleRequest($request);
195
    }
196
197
    /**
198
     * Remove the pseudo namespaces that were added to form fields by the form factory
199
     *
200
     * @param array $data
201
     * @param int $elementID
202
     * @return array
203
     */
204
    public static function removeNamespacesFromFields(array $data, $elementID)
205
    {
206
        $output = [];
207
        $template = sprintf(EditFormFactory::FIELD_NAMESPACE_TEMPLATE, $elementID, '');
208
        foreach ($data as $key => $value) {
209
            // Only look at fields that match the namespace template
210
            if (substr($key, 0, strlen($template)) !== $template) {
211
                continue;
212
            }
213
214
            $fieldName = substr($key, strlen($template));
215
            $output[$fieldName] = $value;
216
        }
217
        return $output;
218
    }
219
}
220