Completed
Push — unit-tests-validation ( c31383...82d287 )
by Romain
17:06
created

AjaxFieldValidation::getFieldValidationConfiguration()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 23
rs 9.0856
cc 3
eloc 13
nc 3
nop 1
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\Validation;
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\InvalidArgumentValueException;
21
use Romm\Formz\Exceptions\InvalidConfigurationException;
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\SingletonInterface;
29
use TYPO3\CMS\Core\Utility\GeneralUtility;
30
use TYPO3\CMS\Extbase\Error\Result;
31
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
32
use TYPO3\CMS\Extbase\Reflection\ReflectionService;
33
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
34
35
/**
36
 * This class is used for automatic Ajax calls for fields which have the setting
37
 * `useAjax` enabled.
38
 */
39
class AjaxFieldValidation implements SingletonInterface
40
{
41
    /**
42
     * @var string
43
     */
44
    protected $formClassName;
45
46
    /**
47
     * @var string
48
     */
49
    protected $formName;
50
51
    /**
52
     * @todo remove
53
     *
54
     * @var string
55
     */
56
    protected $passObjectInstance;
57
58
    /**
59
     * @var string
60
     */
61
    protected $fieldValue;
62
63
    /**
64
     * @var string
65
     */
66
    protected $fieldName;
67
68
    /**
69
     * @var string
70
     */
71
    protected $validatorName;
72
73
    /**
74
     * Main function called.
75
     */
76
    public function run()
77
    {
78
        // Default technical error result if the function can not be reached.
79
        $result = [
80
            'success' => false,
81
            'message' => [ContextService::get()->translate('default_error_message')]
82
        ];
83
84
        // We prevent any external message to be displayed here.
85
        ob_start();
86
87
        try {
88
            $result = $this->getRequestResult();
89
        } catch (\Exception $e) {
90
            $result['data'] = ['errorCode' => $e->getCode()];
91
92
            if (ExtensionService::get()->isInDebugMode()) {
93
                $result['message'] = 'Debug mode – ' . $e->getMessage();
94
            }
95
        }
96
97
        ob_end_clean();
98
99
        return json_encode($result);
100
    }
101
102
    /**
103
     * @param FormObject $formObject
104
     * @throws InvalidConfigurationException
105
     */
106
    protected function checkConfigurationValidationResult(FormObject $formObject)
107
    {
108
        $validationResult = $formObject->getConfigurationValidationResult();
109
110
        if (true === $validationResult->hasErrors()) {
111
            throw new InvalidConfigurationException(
112
                'The form configuration contains errors.',
113
                1487671395
114
            );
115
        }
116
    }
117
118
    /**
119
     * Will get the result of the validation for this Ajax request.
120
     *
121
     * If any error is found, an exception is thrown.
122
     *
123
     * @return array
124
     * @throws EntryNotFoundException
125
     * @throws InvalidArgumentValueException
126
     * @throws InvalidConfigurationException
127
     */
128
    protected function getRequestResult()
129
    {
130
        $this->initializeArguments();
131
132
        /** @var FormObjectFactory $formObjectFactory */
133
        $formObjectFactory = Core::instantiate(FormObjectFactory::class);
134
        $formObject = $formObjectFactory->getInstanceFromClassName($this->formClassName, $this->formName);
135
136
        $this->checkConfigurationValidationResult($formObject);
137
        $validation = $this->getFieldValidation($formObject);
138
        $validatorClassName = $this->getValidatorClassName($validation);
139
140
        $form = $this->buildObject();
141
        $this->fieldValue = ObjectAccess::getProperty($form, $this->fieldName);
142
143
        $validatorDataObject = new ValidatorDataObject($form, $validation);
144
145
        /** @var ValidatorInterface $validator */
146
        $validator = GeneralUtility::makeInstance(
147
            $validatorClassName,
148
            $validation->getOptions(),
149
            $validatorDataObject
150
        );
151
152
        return $this->convertResultToJson($validator->validate($this->fieldValue));
153
    }
154
155
    /**
156
     * Initializes all arguments for the request, and returns an array
157
     * containing the missing arguments.
158
     */
159
    protected function initializeArguments()
160
    {
161
        $arguments = ['formClassName', 'formName', 'passObjectInstance', 'fieldValue', 'fieldName', 'validatorName'];
162
        $argumentsMissing = [];
163
164
        foreach ($arguments as $argument) {
165
            $argumentValue = GeneralUtility::_GP($argument);
166
167
            if ($argumentValue) {
168
                $this->$argument = $argumentValue;
169
            } else {
170
                $argumentsMissing[] = $argument;
171
            }
172
        }
173
174
        if (false === empty($argumentsMissing)) {
175
            throw new InvalidArgumentValueException(
176
                'One or more arguments are missing in the request: "' . implode('", "', $argumentsMissing) . '".',
177
                1487673983
178
            );
179
        }
180
    }
181
182
    /**
183
     * @param FormObject $formObject
184
     * @return Form
185
     * @throws EntryNotFoundException
186
     */
187
    protected function getFormConfiguration(FormObject $formObject)
188
    {
189
        $formConfiguration = $formObject->getConfiguration();
190
191
        if (false === $formConfiguration->hasField($this->fieldName)) {
192
            throw new EntryNotFoundException(
193
                'The field "' . $this->fieldName . '" was not found in the form "' . $this->formName . '" with class "' . $this->formClassName . '".',
194
                1487671603
195
            );
196
        }
197
198
        return $formConfiguration;
199
    }
200
201
    /**
202
     * @param FormObject $formObject
203
     * @return Validation
204
     * @throws EntryNotFoundException
205
     * @throws InvalidConfigurationException
206
     */
207
    protected function getFieldValidation(FormObject $formObject)
208
    {
209
        $formConfiguration = $this->getFormConfiguration($formObject);
210
        $field = $formConfiguration->getField($this->fieldName);
211
212
        if (false === $field->hasValidation($this->validatorName)) {
213
            throw new EntryNotFoundException(
214
                'The field "' . $this->fieldName . '" does not have a rule "' . $this->validatorName . '".',
215
                1487672956
216
            );
217
        }
218
219
        $fieldValidationConfiguration = $field->getValidationByName($this->validatorName);
220
221
        if (false === $fieldValidationConfiguration->doesUseAjax()) {
222
            throw new InvalidConfigurationException(
223
                'The validation "' . $this->validatorName . '" of the field "' . $this->fieldName . '" is not configured to work with Ajax. Please add the option "useAjax".',
224
                1487673434
225
            );
226
        }
227
228
        return $fieldValidationConfiguration;
229
    }
230
231
    /**
232
     * @param Validation $fieldValidationConfiguration
233
     * @return string
234
     * @throws InvalidConfigurationException
235
     */
236
    protected function getValidatorClassName(Validation $fieldValidationConfiguration)
237
    {
238
        $validatorClassName = $fieldValidationConfiguration->getClassName();
239
240
        if (false === in_array(ValidatorInterface::class, class_implements($validatorClassName))) {
241
            throw new InvalidConfigurationException(
242
                'The class name "' . $validatorClassName . '" of the validation "' . $this->validatorName . '" of the field "' . $this->fieldName . '" must implement the interface "' . ValidatorInterface::class . '".',
243
                1487673690
244
            );
245
        }
246
247
        return $validatorClassName;
248
    }
249
250
    /**
251
     * Will convert the result of the function called by this class in a JSON
252
     * string.
253
     *
254
     * @param Result $result
255
     * @return array
256
     */
257
    protected function convertResultToJson(Result $result)
258
    {
259
        $error = ($result->hasErrors())
260
            ? $result->getFirstError()->getMessage()
261
            : '';
262
263
        return [
264
            'success' => !$result->hasErrors(),
265
            'message' => $error
266
        ];
267
    }
268
269
    /**
270
     * Will build and fill an object with a form sent value.
271
     *
272
     * @return FormInterface
273
     */
274
    protected function buildObject()
275
    {
276
        $values = $this->cleanValuesFromUrl($this->fieldValue);
0 ignored issues
show
Documentation introduced by
$this->fieldValue is of type string, but the function expects a array.

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...
277
        /** @var ReflectionService $reflectionService */
278
        $reflectionService = Core::instantiate(ReflectionService::class);
279
        /** @var FormInterface $object */
280
        $object = Core::instantiate($this->formClassName);
281
        $properties = $reflectionService->getClassPropertyNames($this->formClassName);
282
283
        foreach ($properties as $propertyName) {
284
            if (ObjectAccess::isPropertySettable($object, $propertyName)
285
                && isset($values[$propertyName])
286
            ) {
287
                ObjectAccess::setProperty($object, $propertyName, $values[$propertyName]);
288
            }
289
        }
290
291
        return $object;
292
    }
293
294
    /**
295
     * Will clean the string filled with form values sent with Ajax.
296
     *
297
     * @param array $values
298
     * @return array
299
     */
300
    protected function cleanValuesFromUrl($values)
301
    {
302
        // Cleaning the given form values.
303
        $values = reset($values);
304
        unset($values['__referrer']);
305
        unset($values['__trustedProperties']);
306
307
        return reset($values);
308
    }
309
}
310