Passed
Pull Request — master (#456)
by
unknown
02:24
created

ElementalAreaController::formAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 1
dl 0
loc 11
rs 10
c 0
b 0
f 0
1
<?php
2
3
namespace DNADesign\Elemental\Controllers;
4
5
use DNADesign\Elemental\Forms\EditFormFactory;
6
use DNADesign\Elemental\Models\BaseElement;
7
use Exception;
8
use Psr\Log\LoggerInterface;
9
use SilverStripe\CMS\Controllers\CMSMain;
10
use SilverStripe\Control\HTTPRequest;
11
use SilverStripe\Control\HTTPResponse;
12
use SilverStripe\Control\HTTPResponse_Exception;
13
use SilverStripe\Core\Convert;
14
use SilverStripe\Core\Injector\Injector;
15
use SilverStripe\Forms\Form;
16
use SilverStripe\Security\SecurityToken;
17
18
/**
19
 * Controller for "ElementalArea" - handles loading and saving of in-line edit forms in an elemental area in admin
20
 */
21
class ElementalAreaController extends CMSMain
22
{
23
    const FORM_NAME_TEMPLATE = 'ElementForm_%s';
24
25
    private static $url_segment = 'elemental-area';
0 ignored issues
show
introduced by
The private property $url_segment is not used, and could be removed.
Loading history...
26
27
    private static $ignore_menuitem = true;
0 ignored issues
show
introduced by
The private property $ignore_menuitem is not used, and could be removed.
Loading history...
28
29
    private static $url_handlers = [
0 ignored issues
show
introduced by
The private property $url_handlers is not used, and could be removed.
Loading history...
30
        // API access points with structured data
31
        'POST api/saveForm/$ID' => 'apiSaveForm',
32
        'POST $FormName/field/$FieldName' => 'formAction',
33
    ];
34
35
    private static $allowed_actions = [
0 ignored issues
show
introduced by
The private property $allowed_actions is not used, and could be removed.
Loading history...
36
        'elementForm',
37
        'schema',
38
        'apiSaveForm',
39
        'formAction',
40
    ];
41
42
    public function getClientConfig()
43
    {
44
        $clientConfig = parent::getClientConfig();
45
        $clientConfig['form']['elementForm'] = [
46
            'schemaUrl' => $this->Link('schema/elementForm'),
47
            'saveUrl' => $this->Link('api/saveForm'),
48
            'saveMethod' => 'post',
49
            'payloadFormat' => 'json',
50
            'formNameTemplate' => sprintf(static::FORM_NAME_TEMPLATE, '{id}'),
51
        ];
52
        return $clientConfig;
53
    }
54
55
    /**
56
     * @param HTTPRequest|null $request
57
     * @return Form
58
     * @throws HTTPResponse_Exception
59
     */
60
    public function elementForm(HTTPRequest $request = null)
61
    {
62
        // Get ID either from posted back value, or url parameter
63
        if (!$request) {
64
            $this->jsonError(400);
65
            return null;
66
        }
67
        $id = $request->param('ID');
68
        if (!$id) {
69
            $this->jsonError(400);
70
            return null;
71
        }
72
        return $this->getElementForm($id) ?: $this->jsonError(404);
0 ignored issues
show
Bug introduced by
$id of type string is incompatible with the type integer expected by parameter $elementID of DNADesign\Elemental\Cont...oller::getElementForm(). ( Ignorable by Annotation )

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

72
        return $this->getElementForm(/** @scrutinizer ignore-type */ $id) ?: $this->jsonError(404);
Loading history...
73
    }
74
75
    /**
76
     * @param int $elementID
77
     * @return Form|null Returns null if no element exists for the given ID
78
     */
79
    public function getElementForm($elementID)
80
    {
81
        $scaffolder = Injector::inst()->get(EditFormFactory::class);
82
        $element = BaseElement::get()->byID($elementID);
83
84
        if (!$element) {
0 ignored issues
show
introduced by
$element is of type SilverStripe\ORM\DataObject, thus it always evaluated to true.
Loading history...
85
            return null;
86
        }
87
88
        /** @var Form $form */
89
        $form = $scaffolder->getForm(
90
            $this,
91
            sprintf(static::FORM_NAME_TEMPLATE, $elementID),
92
            ['Record' => $element]
93
        );
94
95
        if (!$element->canEdit()) {
96
            $form->makeReadonly();
97
        }
98
99
        return $form;
100
    }
101
102
    /**
103
     * Save an inline edit form for a block
104
     *
105
     * @param HTTPRequest $request
106
     * @return HTTPResponse|null JSON encoded string or null if an exception is thrown
107
     * @throws HTTPResponse_Exception
108
     */
109
    public function apiSaveForm(HTTPRequest $request)
110
    {
111
        // Validate required input data
112
        if (!isset($this->urlParams['ID'])) {
113
            $this->jsonError(400);
114
            return null;
115
        }
116
117
        $data = Convert::json2array($request->getBody());
118
        if (empty($data)) {
119
            $this->jsonError(400);
120
            return null;
121
        }
122
123
        // Inject request body as request vars
124
        foreach ($data as $key => $value) {
125
            $request->offsetSet($key, $value);
126
        }
127
128
        // Check security token
129
        if (!SecurityToken::inst()->checkRequest($request)) {
130
            $this->jsonError(400);
131
            return null;
132
        }
133
134
        /** @var BaseElement $element */
135
        $element = BaseElement::get()->byID($this->urlParams['ID']);
136
        // Ensure the element can be edited by the current user
137
        if (!$element || !$element->canEdit()) {
0 ignored issues
show
introduced by
$element is of type DNADesign\Elemental\Models\BaseElement, thus it always evaluated to true.
Loading history...
138
            $this->jsonError(403);
139
            return null;
140
        }
141
142
        // Remove the pseudo namespaces that were added by the form factory
143
        $data = $this->removeNamespacesFromFields($data, $element->ID);
0 ignored issues
show
Bug introduced by
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

143
        $data = $this->removeNamespacesFromFields(/** @scrutinizer ignore-type */ $data, $element->ID);
Loading history...
144
145
        try {
146
            $updated = false;
147
            $element->update($data);
148
            // Check if anything will actually be changed before writing
149
            if ($element->isChanged()) {
150
                $element->write();
151
                // Track changes so we can return to the client
152
                $updated = true;
153
            }
154
        } catch (Exception $ex) {
155
            Injector::inst()->get(LoggerInterface::class)->debug($ex->getMessage());
156
157
            $this->jsonError(500);
158
            return null;
159
        }
160
161
        $body = Convert::raw2json([
162
            'status' => 'success',
163
            'updated' => $updated,
164
        ]);
165
        return HTTPResponse::create($body)->addHeader('Content-Type', 'application/json');
166
    }
167
168
    /**
169
     * Provides action control for form fields that are request handlers when they're used in an in-line edit form.
170
     *
171
     * Eg. UploadField
172
     *
173
     * @param HTTPRequest $request
174
     * @return array|HTTPResponse|\SilverStripe\Control\RequestHandler|string
175
     */
176
    public function formAction(HTTPRequest $request)
177
    {
178
        $formName = $request->param('FormName');
179
180
        // Get the element ID from the form name
181
        $id = substr($formName, strlen(sprintf(self::FORM_NAME_TEMPLATE, '')));
182
        $form = $this->getElementForm($id);
0 ignored issues
show
Bug introduced by
$id of type string is incompatible with the type integer expected by parameter $elementID of DNADesign\Elemental\Cont...oller::getElementForm(). ( Ignorable by Annotation )

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

182
        $form = $this->getElementForm(/** @scrutinizer ignore-type */ $id);
Loading history...
183
184
        $field = $form->getRequestHandler()->handleField($request);
185
186
        return $field->handleRequest($request);
187
    }
188
189
    /**
190
     * Remove the pseudo namespaces that were added to form fields by the form factory
191
     *
192
     * @param array $data
193
     * @param int $elementID
194
     * @return array
195
     */
196
    protected function removeNamespacesFromFields(array $data, $elementID)
197
    {
198
        $output = [];
199
        $template = sprintf(EditFormFactory::FIELD_NAMESPACE_TEMPLATE, $elementID, '');
200
        foreach ($data as $key => $value) {
201
            // Only look at fields that match the namespace template
202
            if (substr($key, 0, strlen($template)) !== $template) {
203
                continue;
204
            }
205
206
            $fieldName = substr($key, strlen($template));
207
            $output[$fieldName] = $value;
208
        }
209
        return $output;
210
    }
211
}
212