Completed
Push — unit-tests-validation ( 4a3d84...420a0c )
by Romain
02:02
created

AjaxValidationController::initializeAction()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 6
rs 9.4285
c 1
b 0
f 0
cc 2
eloc 3
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 TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
17
use Romm\Formz\Configuration\Form\Field\Validation\Validation;
18
use Romm\Formz\Configuration\Form\Form;
19
use Romm\Formz\Core\Core;
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\Validation\DataObject\ValidatorDataObject;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
use TYPO3\CMS\Extbase\Error\Result;
31
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
32
use TYPO3\CMS\Extbase\Mvc\Web\Request;
33
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
34
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
35
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
36
37
class AjaxValidationController extends ActionController
38
{
39
    const ARGUMENT_FORM_CLASS_NAME = 'formClassName';
40
    const ARGUMENT_FORM_NAME = 'formName';
41
    const ARGUMENT_FORM = 'form';
42
    const ARGUMENT_FIELD_NAME = 'fieldName';
43
    const ARGUMENT_VALIDATOR_NAME = 'validatorName';
44
45
    const DEFAULT_ERROR_MESSAGE_KEY = 'default_error_message';
46
47
    /**
48
     * @var array
49
     */
50
    public static $requiredArguments = [
51
        self::ARGUMENT_FORM_CLASS_NAME,
52
        self::ARGUMENT_FORM_NAME,
53
        self::ARGUMENT_FORM,
54
        self::ARGUMENT_FIELD_NAME,
55
        self::ARGUMENT_VALIDATOR_NAME
56
    ];
57
58
    /**
59
     * @var JsonView
60
     */
61
    protected $view;
62
63
    /**
64
     * @var string
65
     */
66
    protected $defaultViewObjectName = JsonView::class;
67
68
    /**
69
     * @var Request
70
     */
71
    protected $request;
72
73
    /**
74
     * @var bool
75
     */
76
    protected $protectedRequestMode = true;
77
78
    /**
79
     * @var string
80
     */
81
    protected $formClassName;
82
83
    /**
84
     * @var string
85
     */
86
    protected $formName;
87
88
    /**
89
     * @var array
90
     */
91
    protected $form;
92
93
    /**
94
     * @var string
95
     */
96
    protected $fieldName;
97
98
    /**
99
     * @var string
100
     */
101
    protected $validatorName;
102
103
    /**
104
     * @var FormObject
105
     */
106
    protected $formObject;
107
108
    /**
109
     * The only accepted method for the request is `POST`.
110
     */
111
    public function initializeAction()
112
    {
113
        if ($this->request->getMethod() !== 'POST') {
114
            $this->throwStatus(400);
115
        }
116
    }
117
118
    /**
119
     * Main action that will render the validation result.
120
     */
121
    public function runAction()
122
    {
123
        $result = ($this->protectedRequestMode)
124
            ? $this->getProtectedRequestResult()
125
            : $this->getRequestResult();
126
127
        $this->view->setVariablesToRender(['result']);
128
        $this->view->assign('result', $result);
129
    }
130
131
    /**
132
     * @param bool $flag
133
     */
134
    public function setProtectedRequestMode($flag)
135
    {
136
        $this->protectedRequestMode = (bool)$flag;
137
    }
138
139
    /**
140
     * Will fetch the result and prevent any exception or external message to be
141
     * displayed.
142
     *
143
     * @return array
144
     */
145
    protected function getProtectedRequestResult()
146
    {
147
        // Default technical error result if the function can not be reached.
148
        $result = [
149
            'success' => false,
150
            'message' => [ContextService::get()->translate(self::DEFAULT_ERROR_MESSAGE_KEY)]
151
        ];
152
153
        // We prevent any external message to be displayed here.
154
        ob_start();
155
156
        try {
157
            $result = $this->getRequestResult();
158
        } catch (\Exception $exception) {
159
            $result['data'] = ['errorCode' => $exception->getCode()];
160
161
            if (ExtensionService::get()->isInDebugMode()) {
162
                $result['message'] = 'Debug mode – ' . $exception->getMessage();
163
            }
164
        }
165
166
        ob_end_clean();
167
168
        return $result;
169
    }
170
171
    /**
172
     * Will get the result of the validation for this Ajax request.
173
     *
174
     * If any error is found, an exception is thrown.
175
     *
176
     * @return array
177
     */
178
    protected function getRequestResult()
179
    {
180
        $this->initializeArguments();
181
182
        $this->formObject = $this->getFormObject();
183
        $this->checkConfigurationValidationResult();
184
        $validation = $this->getFieldValidation();
185
        $form = $this->buildObject();
186
        $fieldValue = ObjectAccess::getProperty($form, $this->fieldName);
187
        $validatorDataObject = new ValidatorDataObject($this->formObject, $form, $validation);
188
189
        /** @var ValidatorInterface $validator */
190
        $validator = GeneralUtility::makeInstance(
191
            $validation->getClassName(),
192
            $validation->getOptions(),
193
            $validatorDataObject
194
        );
195
196
        return $this->convertResultToJson($validator->validate($fieldValue));
197
    }
198
199
    /**
200
     * Initializes all arguments for the request, and returns an array
201
     * containing the missing arguments.
202
     */
203
    protected function initializeArguments()
204
    {
205
        $argumentsMissing = [];
206
207
        foreach (self::$requiredArguments as $argument) {
208
            $argumentValue = $this->getArgument($argument);
209
210
            if ($argumentValue) {
211
                $this->$argument = $argumentValue;
212
            } else {
213
                $argumentsMissing[] = $argument;
214
            }
215
        }
216
217
        if (false === empty($argumentsMissing)) {
218
            throw new MissingArgumentException(
219
                'One or more arguments are missing in the request: "' . implode('", "', $argumentsMissing) . '".',
220
                1487673983
221
            );
222
        }
223
    }
224
225
    /**
226
     * @return FormObject
227
     */
228
    protected function getFormObject()
229
    {
230
        /** @var FormObjectFactory $formObjectFactory */
231
        $formObjectFactory = Core::instantiate(FormObjectFactory::class);
232
233
        return $formObjectFactory->getInstanceFromClassName($this->formClassName, $this->formName);
234
    }
235
236
    /**
237
     * @throws InvalidConfigurationException
238
     */
239
    protected function checkConfigurationValidationResult()
240
    {
241
        $validationResult = $this->formObject->getConfigurationValidationResult();
242
243
        if (true === $validationResult->hasErrors()) {
244
            throw new InvalidConfigurationException(
245
                'The form configuration contains errors.',
246
                1487671395
247
            );
248
        }
249
    }
250
251
    /**
252
     * @return Validation
253
     * @throws EntryNotFoundException
254
     * @throws InvalidConfigurationException
255
     */
256
    protected function getFieldValidation()
257
    {
258
        $formConfiguration = $this->getFormConfiguration($this->formObject);
259
        $field = $formConfiguration->getField($this->fieldName);
260
261
        if (false === $field->hasValidation($this->validatorName)) {
262
            throw new EntryNotFoundException(
263
                'The field "' . $this->fieldName . '" does not have a rule "' . $this->validatorName . '".',
264
                1487672956
265
            );
266
        }
267
268
        $fieldValidationConfiguration = $field->getValidationByName($this->validatorName);
269
270
        if (false === $fieldValidationConfiguration->doesUseAjax()) {
271
            throw new InvalidConfigurationException(
272
                'The validation "' . $this->validatorName . '" of the field "' . $this->fieldName . '" is not configured to work with Ajax. Please add the option "useAjax".',
273
                1487673434
274
            );
275
        }
276
277
        return $fieldValidationConfiguration;
278
    }
279
280
    /**
281
     * @param FormObject $formObject
282
     * @return Form
283
     * @throws EntryNotFoundException
284
     */
285
    protected function getFormConfiguration(FormObject $formObject)
286
    {
287
        $formConfiguration = $formObject->getConfiguration();
288
289
        if (false === $formConfiguration->hasField($this->fieldName)) {
290
            throw new EntryNotFoundException(
291
                'The field "' . $this->fieldName . '" was not found in the form "' . $this->formName . '" with class "' . $this->formClassName . '".',
292
                1487671603
293
            );
294
        }
295
296
        return $formConfiguration;
297
    }
298
299
    /**
300
     * Will build and fill an object with a form sent value.
301
     *
302
     * @return FormInterface
303
     */
304
    protected function buildObject()
305
    {
306
        $values = $this->cleanValuesFromUrl($this->form);
307
        /** @var ReflectionService $reflectionService */
308
        $reflectionService = Core::instantiate(ReflectionService::class);
309
        /** @var FormInterface $object */
310
        $object = Core::instantiate($this->formClassName);
311
        $properties = $reflectionService->getClassPropertyNames($this->formClassName);
312
313
        foreach ($properties as $propertyName) {
314
            if (ObjectAccess::isPropertySettable($object, $propertyName)
315
                && isset($values[$propertyName])
316
            ) {
317
                ObjectAccess::setProperty($object, $propertyName, $values[$propertyName]);
318
            }
319
        }
320
321
        return $object;
322
    }
323
324
    /**
325
     * Will convert the result of the function called by this class in a JSON
326
     * string.
327
     *
328
     * @param Result $result
329
     * @return array
330
     */
331
    protected function convertResultToJson(Result $result)
332
    {
333
        $error = ($result->hasErrors())
334
            ? $result->getFirstError()->getMessage()
335
            : '';
336
337
        return [
338
            'success' => !$result->hasErrors(),
339
            'message' => $error
340
        ];
341
    }
342
343
    /**
344
     * @param string $name
345
     * @return mixed
346
     */
347
    protected function getArgument($name)
348
    {
349
        return GeneralUtility::_GP($name);
350
    }
351
352
    /**
353
     * Will clean the string filled with form values sent with Ajax.
354
     *
355
     * @param array $values
356
     * @return array
357
     */
358
    protected function cleanValuesFromUrl($values)
359
    {
360
        // Cleaning the given form values.
361
        $values = reset($values);
362
        unset($values['__referrer']);
363
        unset($values['__trustedProperties']);
364
365
        return reset($values);
366
    }
367
}
368