Completed
Push — middleware-wip ( a29b16...4a8d7b )
by Romain
11:57
created

stepDefinitionIsValid()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.2
c 0
b 0
f 0
cc 4
eloc 8
nc 4
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\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
            array_unshift($stepDefinitionsToTest, $stepDefinition);
147
        }
148
149
        foreach ($stepDefinitionsToTest as $stepDefinition) {
150
            $step = $stepDefinition->getStep();
151
152
            /*
153
             * If the already submitted form values are not found, the step is
154
             * considered as invalid.
155
             */
156
            if (false === $this->persistence->hasStepFormValues($stepDefinition)) {
157
                $invalidStepDefinition = $stepDefinition;
158
                break;
159
            }
160
161
            $result = $this->validateStep($step);
162
163
            if ($result->hasErrors()) {
164
                $invalidStepDefinition = $stepDefinition;
165
                break;
166
            } else {
167
                $this->persistence->markStepAsValidated($stepDefinition);
168
                $this->persistence->addValidatedFields($result->getValidatedFields());
169
            }
170
        }
171
172
        $nextStepDefinition = $this->service->getNextStepDefinition($stepDefinition);
173
174
        if ($nextStepDefinition !== $currentStepDefinition) {
175
            $invalidStepDefinition = $stepDefinition;
176
        }
177
178
        return $invalidStepDefinition;
179
    }
180
181
    /**
182
     * @param array $stepFormValues
183
     * @return PropertyMappingConfiguration
184
     */
185
    protected function getPropertyMappingConfiguration(array $stepFormValues)
186
    {
187
        /** @var PropertyMappingConfiguration $propertyMappingConfiguration */
188
        $propertyMappingConfiguration = GeneralUtility::makeInstance(PropertyMappingConfiguration::class);
189
        $propertyMappingConfiguration->allowAllProperties();
190
        $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_CREATION_ALLOWED, true);
191
192
        foreach ($stepFormValues as $key => $value) {
193
            if (is_array($value)) {
194
                $propertyMappingConfiguration->forProperty($key)->allowAllProperties();
195
            }
196
        }
197
198
        return $propertyMappingConfiguration;
199
    }
200
201
    /**
202
     * Validates (again) the given step with the form data that were previously
203
     * submitted and fetched from the step persistence.
204
     *
205
     * @param Step $step
206
     * @return FormResult
207
     */
208
    protected function validateStep(Step $step)
209
    {
210
        /** @var PropertyMapper $propertyMapper */
211
        $propertyMapper = Core::instantiate(PropertyMapper::class);
212
213
        $stepFormValues = $this->persistence->getMergedFormValues();
214
        $propertyMappingConfiguration = $this->getPropertyMappingConfiguration($stepFormValues);
215
216
        $form = $propertyMapper->convert($stepFormValues, $this->formObject->getClassName(), $propertyMappingConfiguration);
217
218
        /** @var FormValidationMiddlewareOption $formValidationMiddlewareOptions */
219
        $formValidationMiddlewareOptions = $this->formObject
220
            ->getDefinition()
221
            ->getPresetMiddlewares()
222
            ->getFormValidationMiddleware()
223
            ->getOptions();
224
225
        /** @var AbstractFormValidator $validator */
226
        $validator = Core::instantiate(
227
            $formValidationMiddlewareOptions->getFormValidatorClassName(),
228
            [
229
                'name'  => $this->formObject->getName(),
230
                'dummy' => true
231
            ]
232
        );
233
234
        $validator->getDataObject()->setValidatedStep($step);
235
236
        return $validator->validate($form);
237
    }
238
}
239