Passed
Pull Request — master (#456)
by
unknown
02:22
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\Admin\LeftAndMain;
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 LeftAndMain
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
        return $form;
96
    }
97
98
    /**
99
     * Save an inline edit form for a block
100
     *
101
     * @param HTTPRequest $request
102
     * @return HTTPResponse|null JSON encoded string or null if an exception is thrown
103
     * @throws HTTPResponse_Exception
104
     */
105
    public function apiSaveForm(HTTPRequest $request)
106
    {
107
        // Validate required input data
108
        if (!isset($this->urlParams['ID'])) {
109
            $this->jsonError(400);
110
            return null;
111
        }
112
113
        $data = Convert::json2array($request->getBody());
114
        if (empty($data)) {
115
            $this->jsonError(400);
116
            return null;
117
        }
118
119
        // Inject request body as request vars
120
        foreach ($data as $key => $value) {
121
            $request->offsetSet($key, $value);
122
        }
123
124
        // Check security token
125
        if (!SecurityToken::inst()->checkRequest($request)) {
126
            $this->jsonError(400);
127
            return null;
128
        }
129
130
        /** @var BaseElement $element */
131
        $element = BaseElement::get()->byID($this->urlParams['ID']);
132
        // Ensure the element can be edited by the current user
133
        if (!$element || !$element->canEdit()) {
0 ignored issues
show
introduced by
$element is of type DNADesign\Elemental\Models\BaseElement, thus it always evaluated to true. If $element can have other possible types, add them to src/Controllers/ElementalAreaController.php:130
Loading history...
134
            $this->jsonError(403);
135
            return null;
136
        }
137
138
        // Remove the pseudo namespaces that were added by the form factory
139
        $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

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

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