Completed
Push — feature/middleware ( 45b992...e11f5b )
by Romain
02:19
created

FormValidatorExecutor::checkFieldsActivation()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 10
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 5
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\Form;
15
16
use Romm\Formz\Behaviours\BehavioursManager;
17
use Romm\Formz\Condition\Processor\ConditionProcessor;
18
use Romm\Formz\Condition\Processor\ConditionProcessorFactory;
19
use Romm\Formz\Condition\Processor\DataObject\PhpConditionDataObject;
20
use Romm\Formz\Core\Core;
21
use Romm\Formz\Error\FormResult;
22
use Romm\Formz\Form\Definition\Field\Field;
23
use Romm\Formz\Form\Definition\Field\Validation\Validator;
24
use Romm\Formz\Form\FormObject\FormObject;
25
use Romm\Formz\Service\MessageService;
26
use Romm\Formz\Validation\DataObject\ValidatorDataObject;
27
use Romm\Formz\Validation\Validator\AbstractValidator;
28
use Romm\Formz\Validation\Validator\Form\DataObject\FormValidatorDataObject;
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\Validation\Validator\ValidatorInterface;
33
34
class FormValidatorExecutor
35
{
36
    /**
37
     * @var FormValidatorDataObject
38
     */
39
    protected $dataObject;
40
41
    /**
42
     * @var FormObject
43
     */
44
    protected $formObject;
45
46
    /**
47
     * @var FormResult
48
     */
49
    protected $result;
50
51
    /**
52
     * @var ConditionProcessor
53
     */
54
    private $conditionProcessor;
55
56
    /**
57
     * @var array
58
     */
59
    protected $fieldsActivationChecked = [];
60
61
    /**
62
     * Current queue, used to prevent infinite loop.
63
     *
64
     * @var array
65
     */
66
    protected $fieldsActivationChecking = [];
67
68
    /**
69
     * @var array
70
     */
71
    protected $fieldsValidated = [];
72
73
    /**
74
     * Array of arbitrary data which are handled by validators.
75
     *
76
     * @var array
77
     */
78
    protected $validationData = [];
79
80
    /**
81
     * @var PhpConditionDataObject
82
     */
83
    protected $phpConditionDataObject;
84
85
    /**
86
     * @param FormObject              $formObject
87
     * @param FormValidatorDataObject $dataObject
88
     */
89
    public function __construct(FormObject $formObject, FormValidatorDataObject $dataObject)
90
    {
91
        $this->dataObject = $dataObject;
92
        $this->formObject = $formObject;
93
        $this->result = $this->dataObject->getFormResult();
94
        $this->conditionProcessor = $this->getConditionProcessor();
95
        $this->phpConditionDataObject = $this->getPhpConditionDataObject();
96
    }
97
98
    /**
99
     * @return FormValidatorExecutor
100
     */
101
    public function applyBehaviours()
102
    {
103
        /** @var BehavioursManager $behavioursManager */
104
        $behavioursManager = GeneralUtility::makeInstance(BehavioursManager::class);
105
        $behavioursManager->applyBehaviourOnFormInstance($this->formObject);
106
107
        return $this;
108
    }
109
110
    /**
111
     * This function will take care of deactivating the validation for fields
112
     * that do not match their activation condition.
113
     *
114
     * @return FormValidatorExecutor
115
     */
116
    public function checkFieldsActivation()
117
    {
118
        foreach ($this->formObject->getDefinition()->getFields() as $field) {
119
            if (false === $this->result->fieldIsDeactivated($field)) {
120
                $this->checkFieldActivation($field);
121
            }
122
        }
123
124
        return $this;
125
    }
126
127
    /**
128
     * @param Field $field
129
     */
130
    protected function checkFieldActivation(Field $field)
131
    {
132
        // Prevents loop checking.
133
        if ($this->fieldActivationIsBeingChecked($field)
134
            || $this->fieldActivationHasBeenChecked($field)
135
        ) {
136
            return;
137
        }
138
139
        $this->markFieldActivationCheckBegin($field);
140
141
        if (true === $field->hasActivation()
142
            && false === $this->getFieldActivationProcessResult($field)
143
        ) {
144
            $this->result->deactivateField($field);
145
        }
146
147
        if (false === $this->result->fieldIsDeactivated($field)) {
148
            $this->checkFieldValidatorActivation($field);
149
        }
150
151
        $this->markFieldActivationAsChecked($field);
152
        $this->markFieldActivationCheckEnd($field);
153
    }
154
155
    /**
156
     * @param Field $field
157
     */
158
    protected function checkFieldValidatorActivation(Field $field)
159
    {
160
        foreach ($field->getValidators() as $validator) {
161
            if (true === $validator->hasActivation()
162
                && false === $this->getValidatorActivationProcessResult($validator)
163
            ) {
164
                $this->result->deactivateValidator($validator);
165
            }
166
        }
167
    }
168
169
    /**
170
     * @return FormValidatorExecutor
171
     */
172
    public function validateFields()
173
    {
174
        foreach ($this->formObject->getDefinition()->getFields() as $field) {
175
            $this->launchFieldValidation($field);
176
        }
177
178
        return $this;
179
    }
180
181
    /**
182
     * @param Field $field
183
     */
184
    protected function launchFieldValidation(Field $field)
185
    {
186
        if (false === $this->fieldWasValidated($field)) {
187
            $this->validateField($field);
188
189
            if ($this->fieldWasValidated($field)) {
190
                $this->callFieldValidationCallback($field);
191
            }
192
        }
193
    }
194
195
    /**
196
     * Will loop on each validator and apply it on the field. The validation
197
     * result is merged with the form result.
198
     *
199
     * @param Field $field
200
     */
201
    public function validateField(Field $field)
202
    {
203
        if (false === $this->fieldWasValidated($field)) {
204
            $this->checkFieldActivation($field);
205
206
            if (false === $this->result->fieldIsDeactivated($field)) {
207
                $this->markFieldAsValidated($field);
208
209
                // Looping on the field's validators settings...
210
                foreach ($field->getValidators() as $validator) {
211
                    if ($this->result->validatorIsDeactivated($validator)) {
212
                        continue;
213
                    }
214
215
                    $validatorResult = $this->processFieldValidator($field, $validator);
216
217
                    // Breaking the loop if an error occurred: we stop the validation process for the current field.
218
                    if ($validatorResult->hasErrors()) {
219
                        break;
220
                    }
221
                }
222
            }
223
        }
224
    }
225
226
    /**
227
     * @param Field     $field
228
     * @param Validator $validator
229
     * @return Result
230
     */
231
    protected function processFieldValidator(Field $field, Validator $validator)
232
    {
233
        $form = $this->formObject->getForm();
234
        $fieldName = $field->getName();
235
        $fieldValue = ObjectAccess::getProperty($form, $fieldName);
236
        $validatorDataObject = new ValidatorDataObject($this->formObject, $validator);
237
238
        /** @var ValidatorInterface $validatorInstance */
239
        $validatorInstance = Core::instantiate(
240
            $validator->getClassName(),
241
            $validator->getOptions(),
242
            $validatorDataObject
243
        );
244
245
        $validatorResult = $validatorInstance->validate($fieldValue);
246
        $validatorResult = MessageService::get()->sanitizeValidatorResult($validatorResult, $validator->getName());
0 ignored issues
show
Bug introduced by
It seems like sanitizeValidatorResult() must be provided by classes using this trait. How about adding it as abstract method to this trait?

This check looks for methods that are used by a trait but not required by it.

To illustrate, let’s look at the following code example

trait Idable {
    public function equalIds(Idable $other) {
        return $this->getId() === $other->getId();
    }
}

The trait Idable provides a method equalsId that in turn relies on the method getId(). If this method does not exist on a class mixing in this trait, the method will fail.

Adding the getId() as an abstract method to the trait will make sure it is available.

Loading history...
247
248
        if ($validatorInstance instanceof AbstractValidator
249
            && false === empty($validationData = $validatorInstance->getValidationData())
250
        ) {
251
            $this->validationData[$fieldName] = ($this->validationData[$fieldName]) ?: [];
252
            $this->validationData[$fieldName] = array_merge(
253
                $this->validationData[$fieldName],
254
                $validationData
255
            );
256
257
            $form->setValidationData($this->validationData);
0 ignored issues
show
Deprecated Code introduced by
The method Romm\Formz\Form\FormInterface::setValidationData() has been deprecated with message: This method is deprecated and will be deleted in FormZ v2.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
258
        }
259
260
        $this->result->forProperty($fieldName)->merge($validatorResult);
261
        unset($validatorDataObject);
262
263
        return $validatorResult;
264
    }
265
266
    /**
267
     * Loops on registered callbacks that should be called after the given field
268
     * validation.
269
     *
270
     * @param Field $field
271
     */
272
    protected function callFieldValidationCallback(Field $field)
273
    {
274
        $fieldValidationCallbacks = $this->dataObject->getFieldValidationCallbacks();
275
276
        foreach ($fieldValidationCallbacks as $callback) {
277
            if (is_callable($callback)) {
278
                call_user_func($callback, $field);
279
            }
280
        }
281
    }
282
283
    /**
284
     * @return FormResult
285
     */
286
    public function getResult()
287
    {
288
        return $this->result;
289
    }
290
291
    /**
292
     * @param Field $field
293
     * @return bool
294
     */
295
    protected function getFieldActivationProcessResult(Field $field)
296
    {
297
        return $this->conditionProcessor->getActivationConditionTreeForField($field)->getPhpResult($this->phpConditionDataObject);
298
    }
299
300
    /**
301
     * @param Validator $validator
302
     * @return bool
303
     */
304
    protected function getValidatorActivationProcessResult(Validator $validator)
305
    {
306
        return $this->conditionProcessor->getActivationConditionTreeForValidator($validator)->getPhpResult($this->phpConditionDataObject);
307
    }
308
309
    /**
310
     * @param Field $field
311
     * @return bool
312
     */
313
    protected function fieldActivationHasBeenChecked(Field $field)
314
    {
315
        return in_array($field->getName(), $this->fieldsActivationChecked);
316
    }
317
318
    /**
319
     * @param Field $field
320
     */
321
    protected function markFieldActivationAsChecked(Field $field)
322
    {
323
        $this->fieldsActivationChecked[] = $field->getName();
324
    }
325
326
    /**
327
     * @param Field $field
328
     * @return bool
329
     */
330
    protected function fieldActivationIsBeingChecked(Field $field)
331
    {
332
        return isset($this->fieldsActivationChecking[$field->getName()]);
333
    }
334
335
    /**
336
     * @param Field $field
337
     */
338
    protected function markFieldActivationCheckBegin(Field $field)
339
    {
340
        $this->fieldsActivationChecking[$field->getName()] = true;
341
    }
342
343
    /**
344
     * @param Field $field
345
     */
346
    protected function markFieldActivationCheckEnd(Field $field)
347
    {
348
        unset($this->fieldsActivationChecking[$field->getName()]);
349
    }
350
351
    /**
352
     * @param Field $field
353
     * @return bool
354
     */
355
    protected function fieldWasValidated(Field $field)
356
    {
357
        return in_array($field->getName(), $this->fieldsValidated);
358
    }
359
360
    /**
361
     * @param Field $field
362
     */
363
    protected function markFieldAsValidated(Field $field)
364
    {
365
        $this->fieldsValidated[] = $field->getName();
366
    }
367
368
    /**
369
     * @return ConditionProcessor
370
     */
371
    protected function getConditionProcessor()
372
    {
373
        return ConditionProcessorFactory::getInstance()->get($this->formObject);
0 ignored issues
show
Unused Code introduced by
The call to ExtendedSelfInstantiateTrait::get() has too many arguments starting with $this->formObject.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
374
    }
375
376
    /**
377
     * @return PhpConditionDataObject
378
     */
379
    protected function getPhpConditionDataObject()
380
    {
381
        return new PhpConditionDataObject($this->formObject->getForm(), $this);
382
    }
383
}
384