Completed
Push — unit-test-services ( f70ec7...99caba )
by Romain
02:28
created

AjaxValidationController::getRequestResult()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
cc 1
eloc 16
nc 1
nop 0
1
<?php
2
/*
3
 * 2017 Romain CANON <[email protected]>
4
 *
5
 * This file is part of the TYPO3 FormZ project.
6
 * It is free software; you can redistribute it and/or modify it
7
 * under the terms of the GNU General Public License, either
8
 * version 3 of the License, or any later version.
9
 *
10
 * For the full copyright and license information, see:
11
 * http://www.gnu.org/licenses/gpl-3.0.html
12
 */
13
14
namespace Romm\Formz\Controller;
15
16
use Romm\Formz\Configuration\Form\Field\Validation\Validation;
17
use Romm\Formz\Configuration\Form\Form;
18
use Romm\Formz\Core\Core;
19
use Romm\Formz\Error\FormzMessageInterface;
20
use Romm\Formz\Exceptions\EntryNotFoundException;
21
use Romm\Formz\Exceptions\InvalidConfigurationException;
22
use Romm\Formz\Exceptions\MissingArgumentException;
23
use Romm\Formz\Form\FormInterface;
24
use Romm\Formz\Form\FormObject;
25
use Romm\Formz\Form\FormObjectFactory;
26
use Romm\Formz\Service\ContextService;
27
use Romm\Formz\Service\ExtensionService;
28
use Romm\Formz\Service\MessageService;
29
use Romm\Formz\Validation\DataObject\ValidatorDataObject;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Extbase\Error\Result;
32
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
33
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
34
use TYPO3\CMS\Extbase\Mvc\Web\Request;
35
use TYPO3\CMS\Extbase\Property\PropertyMapper;
36
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
37
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
38
39
class AjaxValidationController extends ActionController
40
{
41
    const ARGUMENT_FORM_CLASS_NAME = 'formClassName';
42
    const ARGUMENT_FORM_NAME = 'formName';
43
    const ARGUMENT_FORM = 'form';
44
    const ARGUMENT_FIELD_NAME = 'fieldName';
45
    const ARGUMENT_VALIDATOR_NAME = 'validatorName';
46
47
    const DEFAULT_ERROR_MESSAGE_KEY = 'default_error_message';
48
49
    /**
50
     * @var array
51
     */
52
    public static $requiredArguments = [
53
        self::ARGUMENT_FORM_CLASS_NAME,
54
        self::ARGUMENT_FORM_NAME,
55
        self::ARGUMENT_FORM,
56
        self::ARGUMENT_FIELD_NAME,
57
        self::ARGUMENT_VALIDATOR_NAME
58
    ];
59
60
    /**
61
     * @var JsonView
62
     */
63
    protected $view;
64
65
    /**
66
     * @var string
67
     */
68
    protected $defaultViewObjectName = JsonView::class;
69
70
    /**
71
     * @var Request
72
     */
73
    protected $request;
74
75
    /**
76
     * @var bool
77
     */
78
    protected $protectedRequestMode = true;
79
80
    /**
81
     * @var string
82
     */
83
    protected $formClassName;
84
85
    /**
86
     * @var string
87
     */
88
    protected $formName;
89
90
    /**
91
     * @var array
92
     */
93
    protected $form;
94
95
    /**
96
     * @var string
97
     */
98
    protected $fieldName;
99
100
    /**
101
     * @var string
102
     */
103
    protected $validatorName;
104
105
    /**
106
     * @var FormObject
107
     */
108
    protected $formObject;
109
110
    /**
111
     * The only accepted method for the request is `POST`.
112
     */
113
    public function initializeAction()
114
    {
115
        if ($this->request->getMethod() !== 'POST') {
116
            $this->throwStatus(400);
117
        }
118
    }
119
120
    public function getView()
121
    {
122
        return $this->view;
123
    }
124
125
    /**
126
     * Main action that will render the validation result.
127
     */
128
    public function runAction()
129
    {
130
        $result = ($this->protectedRequestMode)
131
            ? $this->getProtectedRequestResult()
132
            : $this->getRequestResult();
133
134
        $this->view->setVariablesToRender(['result']);
135
        $this->view->assign('result', $result);
136
    }
137
138
    /**
139
     * @param bool $flag
140
     */
141
    public function setProtectedRequestMode($flag)
142
    {
143
        $this->protectedRequestMode = (bool)$flag;
144
    }
145
146
    /**
147
     * Will fetch the result and prevent any exception or external message to be
148
     * displayed.
149
     *
150
     * @return array
151
     */
152
    protected function getProtectedRequestResult()
153
    {
154
        // Default technical error result if the function can not be reached.
155
        $result = [
156
            'success' => false,
157
            'messages' => [
158
                'errors' => ['default' => ContextService::get()->translate(self::DEFAULT_ERROR_MESSAGE_KEY)]
159
            ]
160
        ];
161
162
        // We prevent any external message to be displayed here.
163
        ob_start();
164
165
        try {
166
            $result = $this->getRequestResult();
167
        } catch (\Exception $exception) {
168
            $result['data'] = ['errorCode' => $exception->getCode()];
169
170
            if (ExtensionService::get()->isInDebugMode()) {
171
                $result['messages']['errors']['default'] = $this->getDebugMessageForException($exception);
172
            }
173
        }
174
175
        ob_end_clean();
176
177
        return $result;
178
    }
179
180
    /**
181
     * Will get the result of the validation for this Ajax request.
182
     *
183
     * If any error is found, an exception is thrown.
184
     *
185
     * @return array
186
     */
187
    protected function getRequestResult()
188
    {
189
        $this->initializeArguments();
190
191
        $this->formObject = $this->getFormObject();
192
        $this->checkConfigurationValidationResult();
193
        $validation = $this->getFieldValidation();
194
        $form = $this->buildFormObject();
195
        $this->formObject->setForm($form);
196
        $fieldValue = ObjectAccess::getProperty($form, $this->fieldName);
197
        $validatorDataObject = new ValidatorDataObject($this->formObject, $validation);
198
199
        /** @var ValidatorInterface $validator */
200
        $validator = GeneralUtility::makeInstance(
201
            $validation->getClassName(),
202
            $validation->getOptions(),
203
            $validatorDataObject
204
        );
205
206
        $result = $validator->validate($fieldValue);
207
        $result = MessageService::get()->sanitizeValidatorResult($result, $validation);
208
209
        return $this->convertResultToJson($result);
210
    }
211
212
    /**
213
     * Initializes all arguments for the request, and returns an array
214
     * containing the missing arguments.
215
     */
216
    protected function initializeArguments()
217
    {
218
        $argumentsMissing = [];
219
220
        foreach (self::$requiredArguments as $argument) {
221
            $argumentValue = $this->getArgument($argument);
222
223
            if ($argumentValue) {
224
                $this->$argument = $argumentValue;
225
            } else {
226
                $argumentsMissing[] = $argument;
227
            }
228
        }
229
230
        if (false === empty($argumentsMissing)) {
231
            throw MissingArgumentException::ajaxControllerMissingArguments($argumentsMissing);
232
        }
233
    }
234
235
    /**
236
     * @throws InvalidConfigurationException
237
     */
238
    protected function checkConfigurationValidationResult()
239
    {
240
        $validationResult = $this->formObject->getConfigurationValidationResult();
241
242
        if (true === $validationResult->hasErrors()) {
243
            throw InvalidConfigurationException::ajaxControllerInvalidFormConfiguration();
244
        }
245
    }
246
247
    /**
248
     * @return Validation
249
     * @throws EntryNotFoundException
250
     * @throws InvalidConfigurationException
251
     */
252
    protected function getFieldValidation()
253
    {
254
        $formConfiguration = $this->getFormConfiguration($this->formObject);
255
        $field = $formConfiguration->getField($this->fieldName);
256
257
        if (false === $field->hasValidation($this->validatorName)) {
258
            throw EntryNotFoundException::ajaxControllerValidationNotFoundForField($this->validatorName, $this->fieldName);
259
        }
260
261
        $fieldValidationConfiguration = $field->getValidationByName($this->validatorName);
262
263
        if (false === $fieldValidationConfiguration->doesUseAjax()) {
264
            throw InvalidConfigurationException::ajaxControllerAjaxValidationNotActivated($this->validatorName, $this->fieldName);
265
        }
266
267
        return $fieldValidationConfiguration;
268
    }
269
270
    /**
271
     * @param FormObject $formObject
272
     * @return Form
273
     * @throws EntryNotFoundException
274
     */
275
    protected function getFormConfiguration(FormObject $formObject)
276
    {
277
        $formConfiguration = $formObject->getConfiguration();
278
279
        if (false === $formConfiguration->hasField($this->fieldName)) {
280
            throw EntryNotFoundException::ajaxControllerFieldNotFound($this->fieldName, $formObject);
281
        }
282
283
        return $formConfiguration;
284
    }
285
286
    /**
287
     * Will build and fill an object with a form sent value.
288
     *
289
     * @return FormInterface
290
     */
291
    protected function buildFormObject()
292
    {
293
        $values = $this->cleanValuesFromUrl($this->form);
294
295
        return $this->getPropertyMapper()->convert($values, $this->formClassName);
296
    }
297
298
    /**
299
     * Will convert the result of the function called by this class in a JSON
300
     * string.
301
     *
302
     * @param Result $result
303
     * @return array
304
     */
305
    protected function convertResultToJson(Result $result)
306
    {
307
        $messages = [];
308
309
        if ($result->hasErrors()) {
310
            $messages['errors'] = $this->formatMessages($result->getErrors());
0 ignored issues
show
Documentation introduced by
$result->getErrors() is of type array<integer,object<TYP...S\Extbase\Error\Error>>, but the function expects a array<integer,object<Rom...FormzMessageInterface>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
311
        }
312
313
        if ($result->hasWarnings()) {
314
            $messages['warnings'] = $this->formatMessages($result->getWarnings());
0 ignored issues
show
Documentation introduced by
$result->getWarnings() is of type array<integer,object<TYP...Extbase\Error\Warning>>, but the function expects a array<integer,object<Rom...FormzMessageInterface>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
315
        }
316
317
        if ($result->hasNotices()) {
318
            $messages['notices'] = $this->formatMessages($result->getNotices());
0 ignored issues
show
Documentation introduced by
$result->getNotices() is of type array<integer,object<TYP...\Extbase\Error\Notice>>, but the function expects a array<integer,object<Rom...FormzMessageInterface>>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
319
        }
320
321
        return [
322
            'success' => !$result->hasErrors(),
323
            'messages' => $messages
324
        ];
325
    }
326
327
    /**
328
     * @param FormzMessageInterface[] $messages
329
     * @return array
330
     */
331
    protected function formatMessages(array $messages)
332
    {
333
        $sortedMessages = [];
334
335
        foreach ($messages as $message) {
336
            $sortedMessages[$message->getMessageKey()] = $message->getMessage();
0 ignored issues
show
Bug introduced by
The method getMessage() does not exist on Romm\Formz\Error\FormzMessageInterface. Did you maybe mean getMessageKey()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
337
        }
338
339
        return $sortedMessages;
340
    }
341
342
    /**
343
     * @param \Exception $exception
344
     * @return string
345
     */
346
    protected function getDebugMessageForException(\Exception $exception)
347
    {
348
        return 'Debug mode – ' . $exception->getMessage();
349
    }
350
351
    /**
352
     * @param string $name
353
     * @return mixed
354
     */
355
    protected function getArgument($name)
356
    {
357
        return GeneralUtility::_GP($name);
358
    }
359
360
    /**
361
     * Will clean the string filled with form values sent with Ajax.
362
     *
363
     * @param array $values
364
     * @return array
365
     */
366
    protected function cleanValuesFromUrl($values)
367
    {
368
        // Cleaning the given form values.
369
        $values = reset($values);
370
        unset($values['__referrer']);
371
        unset($values['__trustedProperties']);
372
373
        return isset($values[$this->formName])
374
            ? $values[$this->formName]
375
            : [];
376
    }
377
378
    /**
379
     * @return FormObject
380
     */
381
    protected function getFormObject()
382
    {
383
        /** @var FormObjectFactory $formObjectFactory */
384
        $formObjectFactory = Core::instantiate(FormObjectFactory::class);
385
386
        return $formObjectFactory->getInstanceFromClassName($this->formClassName, $this->formName);
387
    }
388
389
    /**
390
     * @return PropertyMapper
391
     */
392
    protected function getPropertyMapper()
393
    {
394
        /** @var PropertyMapper $propertyMapper */
395
        $propertyMapper = Core::instantiate(PropertyMapper::class);
396
397
        return $propertyMapper;
398
    }
399
}
400