Completed
Push — wip/steps ( 20ae80...116a8d )
by Romain
02:54
created

fieldActivationIsBeingChecked()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
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\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\Form\DataObject\FormValidatorDataObject;
28
use Romm\Formz\Validation\Form\Service\SubstepService;
29
use Romm\Formz\Validation\Validator\AbstractValidator;
30
use TYPO3\CMS\Core\Utility\GeneralUtility;
31
use TYPO3\CMS\Extbase\Error\Result;
32
use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
33
use TYPO3\CMS\Extbase\Validation\Validator\ValidatorInterface;
34
35
class FormValidatorExecutor
36
{
37
    /**
38
     * @var FormValidatorDataObject
39
     */
40
    protected $dataObject;
41
42
    /**
43
     * @var ConditionProcessor
44
     */
45
    private $conditionProcessor;
46
47
    /**
48
     * @var PhpConditionDataObject
49
     */
50
    protected $phpConditionDataObject;
51
52
    /**
53
     * @var SubstepService
54
     */
55
    protected $substepService;
56
57
    /**
58
     * @var array
59
     */
60
    protected $fieldsActivationChecked = [];
61
62
    /**
63
     * Current queue, used to prevent infinite loop.
64
     *
65
     * @var array
66
     */
67
    protected $fieldsActivationChecking = [];
68
69
    /**
70
     * @var array
71
     */
72
    protected $fieldsValidated = [];
73
74
    /**
75
     * Array of arbitral data which are handled by validators.
76
     *
77
     * @var array
78
     */
79
    protected $validationData = [];
80
81
    /**
82
     * @param FormValidatorDataObject $dataObject
83
     */
84
    public function __construct(FormValidatorDataObject $dataObject)
85
    {
86
        $this->dataObject = $dataObject;
87
88
        $this->conditionProcessor = $this->getConditionProcessor();
89
        $this->phpConditionDataObject = $this->getPhpConditionDataObject();
90
91
        $this->substepService = Core::instantiate(SubstepService::class, $this, $dataObject);
92
    }
93
94
    /**
95
     * @return FormValidatorExecutor
96
     */
97
    public function applyBehaviours()
98
    {
99
        /** @var BehavioursManager $behavioursManager */
100
        $behavioursManager = GeneralUtility::makeInstance(BehavioursManager::class);
101
        $behavioursManager->applyBehaviourOnFormInstance($this->getFormObject());
102
103
        return $this;
104
    }
105
106
    /**
107
     * This function will take care of deactivating the validation for fields
108
     * that do not match their activation condition.
109
     *
110
     * @return FormValidatorExecutor
111
     */
112
    public function checkFieldsActivation()
113
    {
114
        foreach ($this->getFormObject()->getDefinition()->getFields() as $field) {
115
            if (false === $this->getResult()->fieldIsDeactivated($field)) {
116
                $this->checkFieldActivation($field);
117
            }
118
        }
119
120
        return $this;
121
    }
122
123
    /**
124
     * @param Field $field
125
     */
126
    protected function checkFieldActivation(Field $field)
127
    {
128
        // Prevents loop checking.
129
        if ($this->fieldActivationIsBeingChecked($field)
130
            || $this->fieldActivationHasBeenChecked($field)
131
        ) {
132
            return;
133
        }
134
135
        $this->markFieldActivationCheckBegin($field);
136
137
        $this->checkFieldStepSupport($field);
138
139
        if (false === $this->getResult()->fieldIsOutOfScope($field)
140
            && true === $field->hasActivation()
141
            && false === $this->getFieldActivationProcessResult($field)
142
        ) {
143
            $this->getResult()->deactivateField($field);
144
        }
145
146
        if (false === $this->getResult()->fieldIsDeactivated($field)) {
147
            $this->checkFieldValidatorActivation($field);
148
        }
149
150
        $this->markFieldActivationAsChecked($field);
151
        $this->markFieldActivationCheckEnd($field);
152
    }
153
154
    /**
155
     * Check if the given field is supported by the current step of the form.
156
     *
157
     * @param Field $field
158
     */
159
    protected function checkFieldStepSupport(Field $field)
160
    {
161
        $validatedStep = $this->dataObject->getValidatedStep();
162
163
        if ($validatedStep
164
            && false === $validatedStep->supportsField($field)
165
        ) {
166
            $this->getResult()->markFieldOutOfScope($field);
167
        }
168
    }
169
170
    /**
171
     * @param Field $field
172
     */
173
    protected function checkFieldValidatorActivation(Field $field)
174
    {
175
        foreach ($field->getValidators() as $validator) {
176
            if (true === $validator->hasActivation()
177
                && false === $this->getValidatorActivationProcessResult($validator)
178
            ) {
179
                $this->getResult()->deactivateValidator($validator);
180
            }
181
        }
182
    }
183
184
    /**
185
     * @return FormValidatorExecutor
186
     */
187
    public function validateFields()
188
    {
189
        $validatedStep = $this->dataObject->getValidatedStep();
190
191
        if ($validatedStep && $validatedStep->hasSubsteps()) {
192
            $this->substepService->handleSubsteps();
193
        } else {
194
            foreach ($this->getFormObject()->getDefinition()->getFields() as $field) {
195
                $this->validateField($field);
196
            }
197
        }
198
199
        return $this;
200
    }
201
202
    /**
203
     * Will loop on each validator and apply it on the field. The validation
204
     * result is merged with the form result.
205
     *
206
     * @param Field $field
207
     */
208
    public function validateField(Field $field)
209
    {
210
        if (false === $this->fieldWasValidated($field)) {
211
            $this->checkFieldActivation($field);
212
213
            if (false === $this->getResult()->fieldIsOutOfScope($field)
214
                && false === $this->getResult()->fieldIsDeactivated($field)
215
            ) {
216
                $this->markFieldAsValidated($field);
217
218
                // Looping on the field's validators settings...
219
                foreach ($field->getValidators() as $validator) {
220
                    if ($this->getResult()->validatorIsDeactivated($validator)) {
221
                        continue;
222
                    }
223
224
                    $validatorResult = $this->processFieldValidator($field, $validator);
225
                    $this->getResult()->markFieldAsValidated($field);
226
227
                    // Breaking the loop if an error occurred: we stop the validation process for the current field.
228
                    if ($validatorResult->hasErrors()) {
229
                        break;
230
                    }
231
                }
232
233
                $this->callFieldValidationCallback($field);
234
            }
235
        }
236
    }
237
238
    /**
239
     * @param Field     $field
240
     * @param Validator $validator
241
     * @return Result
242
     */
243
    protected function processFieldValidator(Field $field, Validator $validator)
244
    {
245
        $form = $this->getFormObject()->getForm();
246
        $fieldName = $field->getName();
247
        $fieldValue = ObjectAccess::getProperty($form, $fieldName);
248
        $validatorDataObject = new ValidatorDataObject($this->getFormObject(), $validator);
249
250
        /** @var ValidatorInterface $validatorInstance */
251
        $validatorInstance = Core::instantiate(
252
            $validator->getClassName(),
253
            $validator->getOptions(),
254
            $validatorDataObject
255
        );
256
257
        $validatorResult = $validatorInstance->validate($fieldValue);
258
        $validatorResult = MessageService::get()->sanitizeValidatorResult($validatorResult, $validator->getName());
259
260
        if ($validatorInstance instanceof AbstractValidator
261
            && false === empty($validationData = $validatorInstance->getValidationData())
262
        ) {
263
            $this->validationData[$fieldName] = ($this->validationData[$fieldName]) ?: [];
264
            $this->validationData[$fieldName] = array_merge(
265
                $this->validationData[$fieldName],
266
                $validationData
267
            );
268
269
            $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...
270
        }
271
272
        $this->getResult()->forProperty($fieldName)->merge($validatorResult);
273
        unset($validatorDataObject);
274
275
        return $validatorResult;
276
    }
277
278
    /**
279
     * Loops on registered callbacks that should be called after the given field
280
     * validation.
281
     *
282
     * @param Field $field
283
     */
284
    protected function callFieldValidationCallback(Field $field)
285
    {
286
        $fieldValidationCallbacks = $this->dataObject->getFieldValidationCallbacks();
287
288
        foreach ($fieldValidationCallbacks as $callback) {
289
            if (is_callable($callback)) {
290
                call_user_func($callback, $field);
291
            }
292
        }
293
    }
294
295
    /**
296
     * @return FormObject
297
     */
298
    public function getFormObject()
299
    {
300
        return $this->dataObject->getFormObject();
301
    }
302
303
    /**
304
     * @return FormResult
305
     */
306
    public function getResult()
307
    {
308
        return $this->dataObject->getFormResult();
309
    }
310
311
    /**
312
     * @param Field $field
313
     * @return bool
314
     */
315
    protected function getFieldActivationProcessResult(Field $field)
316
    {
317
        return $this->conditionProcessor->getActivationConditionTreeForField($field)->getPhpResult($this->phpConditionDataObject);
318
    }
319
320
    /**
321
     * @param Validator $validator
322
     * @return bool
323
     */
324
    protected function getValidatorActivationProcessResult(Validator $validator)
325
    {
326
        return $this->conditionProcessor->getActivationConditionTreeForValidator($validator)->getPhpResult($this->phpConditionDataObject);
327
    }
328
329
    /**
330
     * @param Field $field
331
     * @return bool
332
     */
333
    protected function fieldActivationHasBeenChecked(Field $field)
334
    {
335
        return in_array($field->getName(), $this->fieldsActivationChecked);
336
    }
337
338
    /**
339
     * @param Field $field
340
     */
341
    protected function markFieldActivationAsChecked(Field $field)
342
    {
343
        $this->fieldsActivationChecked[] = $field->getName();
344
    }
345
346
    /**
347
     * @param Field $field
348
     * @return bool
349
     */
350
    protected function fieldActivationIsBeingChecked(Field $field)
351
    {
352
        return isset($this->fieldsActivationChecking[$field->getName()]);
353
    }
354
355
    /**
356
     * @param Field $field
357
     */
358
    protected function markFieldActivationCheckBegin(Field $field)
359
    {
360
        $this->fieldsActivationChecking[$field->getName()] = true;
361
    }
362
363
    /**
364
     * @param Field $field
365
     */
366
    protected function markFieldActivationCheckEnd(Field $field)
367
    {
368
        unset($this->fieldsActivationChecking[$field->getName()]);
369
    }
370
371
    /**
372
     * @param Field $field
373
     * @return bool
374
     */
375
    protected function fieldWasValidated(Field $field)
376
    {
377
        return in_array($field->getName(), $this->fieldsValidated);
378
    }
379
380
    /**
381
     * @param Field $field
382
     */
383
    protected function markFieldAsValidated(Field $field)
384
    {
385
        $this->fieldsValidated[] = $field->getName();
386
    }
387
388
    /**
389
     * @return ConditionProcessor
390
     */
391
    protected function getConditionProcessor()
392
    {
393
        return ConditionProcessorFactory::getInstance()->get($this->getFormObject());
394
    }
395
396
    /**
397
     * @return PhpConditionDataObject
398
     */
399
    protected function getPhpConditionDataObject()
400
    {
401
        return new PhpConditionDataObject($this->getFormObject()->getForm(), $this);
402
    }
403
}
404