Completed
Push — feature-message-types ( b0465a )
by Romain
03:13
created

AjaxValidationController::runAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 9
rs 9.6666
c 0
b 0
f 0
cc 2
eloc 6
nc 2
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\Exceptions\EntryNotFoundException;
20
use Romm\Formz\Exceptions\InvalidConfigurationException;
21
use Romm\Formz\Exceptions\MissingArgumentException;
22
use Romm\Formz\Form\FormInterface;
23
use Romm\Formz\Form\FormObject;
24
use Romm\Formz\Form\FormObjectFactory;
25
use Romm\Formz\Service\ContextService;
26
use Romm\Formz\Service\ExtensionService;
27
use Romm\Formz\Service\MessageService;
28
use Romm\Formz\Validation\DataObject\ValidatorDataObject;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
use TYPO3\CMS\Extbase\Error\Message;
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
        $fieldValue = ObjectAccess::getProperty($form, $this->fieldName);
196
        $validatorDataObject = new ValidatorDataObject($this->formObject, $form, $validation);
197
198
        /** @var ValidatorInterface $validator */
199
        $validator = GeneralUtility::makeInstance(
200
            $validation->getClassName(),
201
            $validation->getOptions(),
202
            $validatorDataObject
203
        );
204
205
        return $this->convertResultToJson($validator->validate($fieldValue));
206
    }
207
208
    /**
209
     * Initializes all arguments for the request, and returns an array
210
     * containing the missing arguments.
211
     */
212
    protected function initializeArguments()
213
    {
214
        $argumentsMissing = [];
215
216
        foreach (self::$requiredArguments as $argument) {
217
            $argumentValue = $this->getArgument($argument);
218
219
            if ($argumentValue) {
220
                $this->$argument = $argumentValue;
221
            } else {
222
                $argumentsMissing[] = $argument;
223
            }
224
        }
225
226
        if (false === empty($argumentsMissing)) {
227
            throw new MissingArgumentException(
228
                'One or more arguments are missing in the request: "' . implode('", "', $argumentsMissing) . '".',
229
                1487673983
230
            );
231
        }
232
    }
233
234
    /**
235
     * @return FormObject
236
     */
237
    protected function getFormObject()
238
    {
239
        /** @var FormObjectFactory $formObjectFactory */
240
        $formObjectFactory = Core::instantiate(FormObjectFactory::class);
241
242
        return $formObjectFactory->getInstanceFromClassName($this->formClassName, $this->formName);
243
    }
244
245
    /**
246
     * @throws InvalidConfigurationException
247
     */
248
    protected function checkConfigurationValidationResult()
249
    {
250
        $validationResult = $this->formObject->getConfigurationValidationResult();
251
252
        if (true === $validationResult->hasErrors()) {
253
            throw new InvalidConfigurationException(
254
                'The form configuration contains errors.',
255
                1487671395
256
            );
257
        }
258
    }
259
260
    /**
261
     * @return Validation
262
     * @throws EntryNotFoundException
263
     * @throws InvalidConfigurationException
264
     */
265
    protected function getFieldValidation()
266
    {
267
        $formConfiguration = $this->getFormConfiguration($this->formObject);
268
        $field = $formConfiguration->getField($this->fieldName);
269
270
        if (false === $field->hasValidation($this->validatorName)) {
271
            throw new EntryNotFoundException(
272
                'The field "' . $this->fieldName . '" does not have a rule "' . $this->validatorName . '".',
273
                1487672956
274
            );
275
        }
276
277
        $fieldValidationConfiguration = $field->getValidationByName($this->validatorName);
278
279
        if (false === $fieldValidationConfiguration->doesUseAjax()) {
280
            throw new InvalidConfigurationException(
281
                'The validation "' . $this->validatorName . '" of the field "' . $this->fieldName . '" is not configured to work with Ajax. Please add the option "useAjax".',
282
                1487673434
283
            );
284
        }
285
286
        return $fieldValidationConfiguration;
287
    }
288
289
    /**
290
     * @param FormObject $formObject
291
     * @return Form
292
     * @throws EntryNotFoundException
293
     */
294
    protected function getFormConfiguration(FormObject $formObject)
295
    {
296
        $formConfiguration = $formObject->getConfiguration();
297
298
        if (false === $formConfiguration->hasField($this->fieldName)) {
299
            throw new EntryNotFoundException(
300
                'The field "' . $this->fieldName . '" was not found in the form "' . $this->formName . '" with class "' . $this->formClassName . '".',
301
                1487671603
302
            );
303
        }
304
305
        return $formConfiguration;
306
    }
307
308
    /**
309
     * Will build and fill an object with a form sent value.
310
     *
311
     * @return FormInterface
312
     */
313
    protected function buildFormObject()
314
    {
315
        $values = $this->cleanValuesFromUrl($this->form);
316
317
        return $this->getPropertyMapper()->convert($values, $this->formClassName);
318
    }
319
320
    /**
321
     * Will convert the result of the function called by this class in a JSON
322
     * string.
323
     *
324
     * @param Result $result
325
     * @return array
326
     */
327
    protected function convertResultToJson(Result $result)
328
    {
329
        $messages = [];
330
331
        if ($result->hasErrors()) {
332
            $messages['errors'] = $this->formatMessages($result->getErrors());
333
        }
334
335
        if ($result->hasWarnings()) {
336
            $messages['warnings'] = $this->formatMessages($result->getWarnings());
337
        }
338
339
        if ($result->hasNotices()) {
340
            $messages['notices'] = $this->formatMessages($result->getNotices());
341
        }
342
343
        return [
344
            'success' => !$result->hasErrors(),
345
            'messages' => $messages
346
        ];
347
    }
348
349
    /**
350
     * @param Message[] $messages
351
     * @return array
352
     */
353
    protected function formatMessages(array $messages)
354
    {
355
        $sortedMessages = [];
356
357
        foreach ($messages as $message) {
358
            $key = MessageService::get()->getMessageKey($message);
359
            $sortedMessages[$key] = $message->getMessage();
360
        }
361
362
        return $sortedMessages;
363
    }
364
365
    /**
366
     * @param \Exception $exception
367
     * @return string
368
     */
369
    protected function getDebugMessageForException(\Exception $exception)
370
    {
371
        return 'Debug mode – ' . $exception->getMessage();
372
    }
373
374
    /**
375
     * @param string $name
376
     * @return mixed
377
     */
378
    protected function getArgument($name)
379
    {
380
        return GeneralUtility::_GP($name);
381
    }
382
383
    /**
384
     * Will clean the string filled with form values sent with Ajax.
385
     *
386
     * @param array $values
387
     * @return array
388
     */
389
    protected function cleanValuesFromUrl($values)
390
    {
391
        // Cleaning the given form values.
392
        $values = reset($values);
393
        unset($values['__referrer']);
394
        unset($values['__trustedProperties']);
395
396
        return reset($values);
397
    }
398
399
    /**
400
     * @return PropertyMapper
401
     */
402
    protected function getPropertyMapper()
403
    {
404
        /** @var PropertyMapper $propertyMapper */
405
        $propertyMapper = Core::instantiate(PropertyMapper::class);
406
407
        return $propertyMapper;
408
    }
409
}
410