Completed
Push — unit-tests-validation ( adb597...9c3cea )
by Romain
02:24
created

AjaxFieldValidation::getValidatorClassName()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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