Issues (124)

src/Controllers/ElementalAreaController.php (5 issues)

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 = [
0 ignored issues
show
The private property $url_handlers is not used, and could be removed.
Loading history...
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());
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Core\Convert::json2array() has been deprecated: 4.4.0:5.0.0 Use json_decode() instead ( Ignorable by Annotation )

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

124
        $data = /** @scrutinizer ignore-deprecated */ Convert::json2array($request->getBody());

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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()) {
0 ignored issues
show
$element is of type DNADesign\Elemental\Models\BaseElement, thus it always evaluated to true.
Loading history...
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([
0 ignored issues
show
Deprecated Code introduced by
The function SilverStripe\Core\Convert::raw2json() has been deprecated: 4.4.0:5.0.0 Use json_encode() instead ( Ignorable by Annotation )

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

169
        $body = /** @scrutinizer ignore-deprecated */ Convert::raw2json([

This function has been deprecated. The supplier of the function has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the function will be removed and what other function to use instead.

Loading history...
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