Completed
Push — unit-tests-conditions ( 8b51f2...c3e9b3 )
by Romain
02:09
created

AjaxValidationController::getFieldValidation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
cc 3
eloc 13
nc 3
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\Validation\DataObject\ValidatorDataObject;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Extbase\Error\Result;
30
use TYPO3\CMS\Extbase\Mvc\Controller\ActionController;
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
    public function getView()
119
    {
120
        return $this->view;
121
    }
122
123
    /**
124
     * Main action that will render the validation result.
125
     */
126
    public function runAction()
127
    {
128
        $result = ($this->protectedRequestMode)
129
            ? $this->getProtectedRequestResult()
130
            : $this->getRequestResult();
131
132
        $this->view->setVariablesToRender(['result']);
133
        $this->view->assign('result', $result);
134
    }
135
136
    /**
137
     * @param bool $flag
138
     */
139
    public function setProtectedRequestMode($flag)
140
    {
141
        $this->protectedRequestMode = (bool)$flag;
142
    }
143
144
    /**
145
     * Will fetch the result and prevent any exception or external message to be
146
     * displayed.
147
     *
148
     * @return array
149
     */
150
    protected function getProtectedRequestResult()
151
    {
152
        // Default technical error result if the function can not be reached.
153
        $result = [
154
            'success' => false,
155
            'message' => [ContextService::get()->translate(self::DEFAULT_ERROR_MESSAGE_KEY)]
156
        ];
157
158
        // We prevent any external message to be displayed here.
159
        ob_start();
160
161
        try {
162
            $result = $this->getRequestResult();
163
        } catch (\Exception $exception) {
164
            $result['data'] = ['errorCode' => $exception->getCode()];
165
166
            if (ExtensionService::get()->isInDebugMode()) {
167
                $result['message'] = $this->getDebugMessageForException($exception);
168
            }
169
        }
170
171
        ob_end_clean();
172
173
        return $result;
174
    }
175
176
    /**
177
     * Will get the result of the validation for this Ajax request.
178
     *
179
     * If any error is found, an exception is thrown.
180
     *
181
     * @return array
182
     */
183
    protected function getRequestResult()
184
    {
185
        $this->initializeArguments();
186
187
        $this->formObject = $this->getFormObject();
188
        $this->checkConfigurationValidationResult();
189
        $validation = $this->getFieldValidation();
190
        $form = $this->buildObject();
191
        $fieldValue = ObjectAccess::getProperty($form, $this->fieldName);
192
        $validatorDataObject = new ValidatorDataObject($this->formObject, $form, $validation);
193
194
        /** @var ValidatorInterface $validator */
195
        $validator = GeneralUtility::makeInstance(
196
            $validation->getClassName(),
197
            $validation->getOptions(),
198
            $validatorDataObject
199
        );
200
201
        return $this->convertResultToJson($validator->validate($fieldValue));
202
    }
203
204
    /**
205
     * Initializes all arguments for the request, and returns an array
206
     * containing the missing arguments.
207
     */
208
    protected function initializeArguments()
209
    {
210
        $argumentsMissing = [];
211
212
        foreach (self::$requiredArguments as $argument) {
213
            $argumentValue = $this->getArgument($argument);
214
215
            if ($argumentValue) {
216
                $this->$argument = $argumentValue;
217
            } else {
218
                $argumentsMissing[] = $argument;
219
            }
220
        }
221
222
        if (false === empty($argumentsMissing)) {
223
            throw new MissingArgumentException(
224
                'One or more arguments are missing in the request: "' . implode('", "', $argumentsMissing) . '".',
225
                1487673983
226
            );
227
        }
228
    }
229
230
    /**
231
     * @return FormObject
232
     */
233
    protected function getFormObject()
234
    {
235
        /** @var FormObjectFactory $formObjectFactory */
236
        $formObjectFactory = Core::instantiate(FormObjectFactory::class);
237
238
        return $formObjectFactory->getInstanceFromClassName($this->formClassName, $this->formName);
239
    }
240
241
    /**
242
     * @throws InvalidConfigurationException
243
     */
244
    protected function checkConfigurationValidationResult()
245
    {
246
        $validationResult = $this->formObject->getConfigurationValidationResult();
247
248
        if (true === $validationResult->hasErrors()) {
249
            throw new InvalidConfigurationException(
250
                'The form configuration contains errors.',
251
                1487671395
252
            );
253
        }
254
    }
255
256
    /**
257
     * @return Validation
258
     * @throws EntryNotFoundException
259
     * @throws InvalidConfigurationException
260
     */
261
    protected function getFieldValidation()
262
    {
263
        $formConfiguration = $this->getFormConfiguration($this->formObject);
264
        $field = $formConfiguration->getField($this->fieldName);
265
266
        if (false === $field->hasValidation($this->validatorName)) {
267
            throw new EntryNotFoundException(
268
                'The field "' . $this->fieldName . '" does not have a rule "' . $this->validatorName . '".',
269
                1487672956
270
            );
271
        }
272
273
        $fieldValidationConfiguration = $field->getValidationByName($this->validatorName);
274
275
        if (false === $fieldValidationConfiguration->doesUseAjax()) {
276
            throw new InvalidConfigurationException(
277
                'The validation "' . $this->validatorName . '" of the field "' . $this->fieldName . '" is not configured to work with Ajax. Please add the option "useAjax".',
278
                1487673434
279
            );
280
        }
281
282
        return $fieldValidationConfiguration;
283
    }
284
285
    /**
286
     * @param FormObject $formObject
287
     * @return Form
288
     * @throws EntryNotFoundException
289
     */
290
    protected function getFormConfiguration(FormObject $formObject)
291
    {
292
        $formConfiguration = $formObject->getConfiguration();
293
294
        if (false === $formConfiguration->hasField($this->fieldName)) {
295
            throw new EntryNotFoundException(
296
                'The field "' . $this->fieldName . '" was not found in the form "' . $this->formName . '" with class "' . $this->formClassName . '".',
297
                1487671603
298
            );
299
        }
300
301
        return $formConfiguration;
302
    }
303
304
    /**
305
     * Will build and fill an object with a form sent value.
306
     *
307
     * @return FormInterface
308
     */
309
    protected function buildObject()
310
    {
311
        $values = $this->cleanValuesFromUrl($this->form);
312
        /** @var ReflectionService $reflectionService */
313
        $reflectionService = Core::instantiate(ReflectionService::class);
314
        /** @var FormInterface $object */
315
        $object = Core::instantiate($this->formClassName);
316
        $properties = $reflectionService->getClassPropertyNames($this->formClassName);
317
318
        foreach ($properties as $propertyName) {
319
            if (ObjectAccess::isPropertySettable($object, $propertyName)
320
                && isset($values[$propertyName])
321
            ) {
322
                ObjectAccess::setProperty($object, $propertyName, $values[$propertyName]);
323
            }
324
        }
325
326
        return $object;
327
    }
328
329
    /**
330
     * Will convert the result of the function called by this class in a JSON
331
     * string.
332
     *
333
     * @param Result $result
334
     * @return array
335
     */
336
    protected function convertResultToJson(Result $result)
337
    {
338
        $error = ($result->hasErrors())
339
            ? $result->getFirstError()->getMessage()
340
            : '';
341
342
        return [
343
            'success' => !$result->hasErrors(),
344
            'message' => $error
345
        ];
346
    }
347
348
    /**
349
     * @param \Exception $exception
350
     * @return string
351
     */
352
    protected function getDebugMessageForException(\Exception $exception)
353
    {
354
        return 'Debug mode – ' . $exception->getMessage();
355
    }
356
357
    /**
358
     * @param string $name
359
     * @return mixed
360
     */
361
    protected function getArgument($name)
362
    {
363
        return GeneralUtility::_GP($name);
364
    }
365
366
    /**
367
     * Will clean the string filled with form values sent with Ajax.
368
     *
369
     * @param array $values
370
     * @return array
371
     */
372
    protected function cleanValuesFromUrl($values)
373
    {
374
        // Cleaning the given form values.
375
        $values = reset($values);
376
        unset($values['__referrer']);
377
        unset($values['__trustedProperties']);
378
379
        return reset($values);
380
    }
381
}
382