Completed
Push — wip/steps ( 00e2d0...2b61f8 )
by Romain
14:33
created

StepMiddlewareValidationService   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 217
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 14

Importance

Changes 0
Metric Value
wmc 23
lcom 1
cbo 14
dl 0
loc 217
rs 10
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 1
A markStepAsValidated() 0 12 3
A addValidatedFields() 0 4 1
A stepDefinitionIsValid() 0 17 4
C getFirstInvalidStep() 0 67 10
A getPropertyMappingConfiguration() 0 15 3
B validateStep() 0 30 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\Middleware\Item\Step\Service;
15
16
use Romm\Formz\Core\Core;
17
use Romm\Formz\Error\FormResult;
18
use Romm\Formz\Form\Definition\Step\Step\Step;
19
use Romm\Formz\Form\Definition\Step\Step\StepDefinition;
20
use Romm\Formz\Form\FormObject\FormObject;
21
use Romm\Formz\Form\FormObject\Service\Step\FormStepPersistence;
22
use Romm\Formz\Middleware\Item\FormValidation\FormValidationMiddlewareOption;
23
use Romm\Formz\Validation\Validator\Form\AbstractFormValidator;
24
use TYPO3\CMS\Core\Utility\GeneralUtility;
25
use TYPO3\CMS\Extbase\Property\PropertyMapper;
26
use TYPO3\CMS\Extbase\Property\PropertyMappingConfiguration;
27
use TYPO3\CMS\Extbase\Property\TypeConverter\PersistentObjectConverter;
28
29
class StepMiddlewareValidationService
30
{
31
    /**
32
     * @var FormObject
33
     */
34
    protected $formObject;
35
36
    /**
37
     * @var StepMiddlewareService
38
     */
39
    protected $service;
40
41
    /**
42
     * @var FormStepPersistence
43
     */
44
    protected $persistence;
45
46
    /**
47
     * @param StepMiddlewareService $service
48
     */
49
    public function __construct(StepMiddlewareService $service)
50
    {
51
        $this->service = $service;
52
        $this->formObject = $service->getFormObject();
53
        $this->persistence = $service->getStepPersistence();
54
    }
55
56
    /**
57
     * Marks the given step as validated: no errors were found during validation
58
     * with the given values array.
59
     *
60
     * @param StepDefinition $stepDefinition
61
     * @param array          $formValues
62
     */
63
    public function markStepAsValidated(StepDefinition $stepDefinition, array $formValues)
64
    {
65
        $this->persistence->markStepAsValidated($stepDefinition);
66
67
        if ($this->persistence->hasStepFormValues($stepDefinition)
68
            && serialize($formValues) !== serialize($this->persistence->getStepFormValues($stepDefinition))
69
        ) {
70
            $this->persistence->resetValidationData();
71
        }
72
73
        $this->persistence->addStepFormValues($stepDefinition, $formValues);
74
    }
75
76
    /**
77
     * @param array $validatedFields
78
     */
79
    public function addValidatedFields(array $validatedFields)
80
    {
81
        $this->persistence->addValidatedFields($validatedFields);
82
    }
83
84
    /**
85
     * Checks that the previous step has already been validated, meaning the
86
     * user has the right to stand in the given step.
87
     *
88
     * @param StepDefinition $stepDefinition
89
     * @return bool
90
     */
91
    public function stepDefinitionIsValid(StepDefinition $stepDefinition)
92
    {
93
        if (false === $stepDefinition->hasPreviousDefinition()) {
94
            /*
95
             * No previous step definition found: the user stands on the first
96
             * step, it always has the right to stand there.
97
             */
98
            return true;
99
        }
100
101
        $previousStep = $stepDefinition->getPreviousDefinition()->getStep();
102
        $stepLevel = $stepDefinition->getStepLevel();
103
104
        return $this->persistence->stepWasValidated($previousStep)
105
            && true === $this->persistence->hasStepIdentifierAtLevel($stepLevel)
106
            && $stepDefinition->getStep()->getIdentifier() === $this->persistence->getStepIdentifierAtLevel($stepLevel);
107
    }
108
109
    /**
110
     * Searches for the first invalid step among previous steps from the given
111
     * step.
112
     *
113
     * All previous steps are listed, then for each one we check if submitted
114
     * form values has been saved in the step persistence, in which case the
115
     * step validation is launched again with the current form configuration.
116
     *
117
     * @param Step $step
118
     * @return StepDefinition|null
119
     */
120
    public function getFirstInvalidStep(Step $step)
121
    {
122
        $firstStep = $this->service->getFirstStepDefinition();
123
124
        if ($step === $firstStep->getStep()) {
125
            /*
126
             * The first step is always valid.
127
             */
128
            return null;
129
        }
130
131
        /*
132
         * If there is no form instance, and the request is not in the first
133
         * step, obviously the user should not be there.
134
         */
135
        if (false === $this->formObject->hasForm()) {
136
            return $firstStep;
137
        }
138
139
        /** @var StepDefinition[] $stepDefinitionsToTest */
140
        $stepDefinitionsToTest = [];
141
        $invalidStepDefinition = null;
142
        $currentStepDefinition = $stepDefinition = $this->service->getStepDefinition($step);
143
144
        while ($stepDefinition->hasPreviousDefinition()) {
145
            $stepDefinition = $stepDefinition->getPreviousDefinition();
146
147
            if ($stepDefinition->hasActivation()) {
148
                if (true === $this->service->getStepDefinitionConditionResult($stepDefinition)) {
149
                    array_unshift($stepDefinitionsToTest, $stepDefinition);
150
                }
151
            } else {
152
                array_unshift($stepDefinitionsToTest, $stepDefinition);
153
            }
154
        }
155
156
        foreach ($stepDefinitionsToTest as $stepDefinition) {
157
            $step = $stepDefinition->getStep();
158
159
            /*
160
             * If the already submitted form values are not found, the step is
161
             * considered as invalid.
162
             */
163
            if (false === $this->persistence->hasStepFormValues($stepDefinition)) {
164
                $invalidStepDefinition = $stepDefinition;
165
                break;
166
            }
167
168
            $result = $this->validateStep($step);
169
170
            if ($result->hasErrors()) {
171
                $invalidStepDefinition = $stepDefinition;
172
                break;
173
            } else {
174
                $this->persistence->markStepAsValidated($stepDefinition);
175
                $this->persistence->addValidatedFields($result->getValidatedFields());
176
            }
177
        }
178
179
        $nextStepDefinition = $this->service->getNextStepDefinition($stepDefinition);
180
181
        if ($nextStepDefinition !== $currentStepDefinition) {
182
            $invalidStepDefinition = $stepDefinition;
183
        }
184
185
        return $invalidStepDefinition;
186
    }
187
188
    /**
189
     * @param array $stepFormValues
190
     * @return PropertyMappingConfiguration
191
     */
192
    protected function getPropertyMappingConfiguration(array $stepFormValues)
193
    {
194
        /** @var PropertyMappingConfiguration $propertyMappingConfiguration */
195
        $propertyMappingConfiguration = GeneralUtility::makeInstance(PropertyMappingConfiguration::class);
196
        $propertyMappingConfiguration->allowAllProperties();
197
        $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, true);
198
199
        foreach ($stepFormValues as $key => $value) {
200
            if (is_array($value)) {
201
                $propertyMappingConfiguration->forProperty($key)->allowAllProperties();
202
            }
203
        }
204
205
        return $propertyMappingConfiguration;
206
    }
207
208
    /**
209
     * Validates (again) the given step with the form data that were previously
210
     * submitted and fetched from the step persistence.
211
     *
212
     * @param Step $step
213
     * @return FormResult
214
     */
215
    protected function validateStep(Step $step)
216
    {
217
        /** @var PropertyMapper $propertyMapper */
218
        $propertyMapper = Core::instantiate(PropertyMapper::class);
219
220
        $stepFormValues = $this->persistence->getMergedFormValues();
221
        $propertyMappingConfiguration = $this->getPropertyMappingConfiguration($stepFormValues);
222
223
        $form = $propertyMapper->convert($stepFormValues, $this->formObject->getClassName(), $propertyMappingConfiguration);
224
225
        /** @var FormValidationMiddlewareOption $formValidationMiddlewareOptions */
226
        $formValidationMiddlewareOptions = $this->formObject
227
            ->getDefinition()
228
            ->getPresetMiddlewares()
229
            ->getFormValidationMiddleware()
230
            ->getOptions();
231
232
        /** @var AbstractFormValidator $validator */
233
        $validator = Core::instantiate(
234
            $formValidationMiddlewareOptions->getFormValidatorClassName(),
235
            [
236
                'name'  => $this->formObject->getName(),
237
                'dummy' => true
238
            ]
239
        );
240
241
        $validator->getDataObject()->setValidatedStep($step);
242
243
        return $validator->validate($form);
244
    }
245
}
246