Passed
Push — master ( 97e8d4...784af9 )
by Raissa
05:32 queued 03:00
created

ElementalAreaController::getClientConfig()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 0
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 Guy Marriott
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 Guy Marriott
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 Robbie Averill
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 Will Rossiter
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 Guy Marriott
$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 Guy Marriott
$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 Robbie Averill
$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 Robbie Averill
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
148
            $element->updateFromFormData($data);
149
            // Check if anything will actually be changed before writing
150
            if ($element->isChanged()) {
151
                $element->write();
152
                // Track changes so we can return to the client
153
                $updated = true;
154
            }
155
        } catch (Exception $ex) {
156
            Injector::inst()->get(LoggerInterface::class)->debug($ex->getMessage());
157
158
            $this->jsonError(500);
159
            return null;
160
        }
161
162
        $body = Convert::raw2json([
163
            'status' => 'success',
164
            'updated' => $updated,
165
        ]);
166
        return HTTPResponse::create($body)->addHeader('Content-Type', 'application/json');
167
    }
168
169
    /**
170
     * Provides action control for form fields that are request handlers when they're used in an in-line edit form.
171
     *
172
     * Eg. UploadField
173
     *
174
     * @param HTTPRequest $request
175
     * @return array|HTTPResponse|\SilverStripe\Control\RequestHandler|string
176
     */
177
    public function formAction(HTTPRequest $request)
178
    {
179
        $formName = $request->param('FormName');
180
181
        // Get the element ID from the form name
182
        $id = substr($formName, strlen(sprintf(self::FORM_NAME_TEMPLATE, '')));
183
        $form = $this->getElementForm($id);
0 ignored issues
show
Bug introduced by Guy Marriott
$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

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