Completed
Push — unit-tests-validation ( de96f6 )
by Romain
02:30
created

AjaxFieldValidation::initializeArguments()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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