Completed
Push — middleware-wip-tmp2 ( a3478c...ef0a86 )
by Romain
05:09
created

StepMiddlewareService::getFormStepPersistence()   B

Complexity

Conditions 6
Paths 9

Size

Total Lines 24
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 24
rs 8.5125
c 0
b 0
f 0
cc 6
eloc 14
nc 9
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\Middleware\Item\Step\Service;
15
16
use Romm\Formz\Configuration\Form\Step\Step\Step;
17
use Romm\Formz\Configuration\Form\Step\Step\StepDefinition;
18
use Romm\Formz\Core\Core;
19
use Romm\Formz\Error\FormResult;
20
use Romm\Formz\Form\FormObject\FormObject;
21
use Romm\Formz\Middleware\Item\FormValidation\FormValidationMiddlewareOption;
22
use Romm\Formz\Middleware\Request\Redirect;
23
use Romm\Formz\Service\Traits\SelfInstantiateTrait;
24
use Romm\Formz\Validation\Validator\Form\AbstractFormValidator;
25
use TYPO3\CMS\Core\SingletonInterface;
26
use TYPO3\CMS\Core\Utility\GeneralUtility;
27
use TYPO3\CMS\Extbase\Property\PropertyMapper;
28
29
/**
30
 * This service allows extended form steps manipulation.
31
 */
32
class StepMiddlewareService implements SingletonInterface
33
{
34
    use SelfInstantiateTrait;
35
36
    const METADATA_STEP_PERSISTENCE_KEY = 'core.formStepPersistence';
37
38
    /**
39
     * @var FormObject
40
     */
41
    protected $formObject;
42
43
    /**
44
     * Step persistence is saved in the form metadata.
45
     *
46
     * It allows having essential information about the form steps whenever it
47
     * is needed: submitted form values, as well as steps that were already
48
     * validated.
49
     *
50
     * @var FormStepPersistence
51
     */
52
    protected $formStepPersistence;
53
54
    /**
55
     * @param FormObject $formObject
56
     */
57
    public function reset(FormObject $formObject)
58
    {
59
        $this->formObject = $formObject;
60
        unset($this->formStepPersistence);
61
    }
62
63
    /**
64
     * Redirects the current request to the given step.
65
     *
66
     * @param Step     $step
67
     * @param Redirect $redirect
68
     */
69
    public function redirectToStep(Step $step, Redirect $redirect)
70
    {
71
        $redirect->toPage($step->getPageUid())
72
            ->toExtension($step->getExtension())
73
            ->toController($step->getController())
74
            ->toAction($step->getAction())
75
            ->withArguments([
76
                'fz-hash' => [
77
                    $this->formObject->getName() => $this->formObject->getFormHash()
78
                ]
79
            ])
80
            ->dispatch();
81
    }
82
83
    /**
84
     * Marks the given step as validated: no errors were found during validation
85
     * with the given values array.
86
     *
87
     * @param Step  $step
88
     * @param array $formValues
89
     */
90
    public function markStepAsValidated(Step $step, array $formValues)
91
    {
92
        $this->getFormStepPersistence()->markStepAsValidated($step);
93
        $this->getFormStepPersistence()->addStepFormValues($step, $formValues);
94
    }
95
96
    /**
97
     * Checks that the previous step has already been validated, meaning the
98
     * user has the right to be in the current step.
99
     *
100
     * @param Step $step
101
     * @return bool
102
     */
103
    public function stepIsValid(Step $step)
104
    {
105
        $stepDefinition = $this->getStepDefinition($step);
106
107
        if (false === $stepDefinition->hasPreviousDefinition()) {
108
            return true;
109
        }
110
111
        $lastStep = $stepDefinition->getPreviousDefinition()->getStep();
112
113
        return $this->getFormStepPersistence()->stepWasValidated($lastStep);
114
    }
115
116
    /**
117
     * Searches for the first invalid step among previous steps from the given
118
     * step.
119
     *
120
     * All previous steps are listed, then for each one we check if submitted
121
     * form values has been saved in the step persistence, in which case the
122
     * step validation is launched again with the current form configuration.
123
     *
124
     * @param Step $step
125
     * @return Step|null
126
     */
127
    public function getFirstInvalidStep(Step $step)
128
    {
129
        /*
130
         * The first step is always valid.
131
         */
132
        $firstStep = $this->getFirstStepDefinition()->getStep();
133
134
        if ($step === $firstStep) {
135
            return null;
136
        }
137
138
        /*
139
         * If there is no form instance, and the request is not in the first
140
         * step, obviously the user should not be there.
141
         */
142
        if (false === $this->formObject->hasForm()) {
143
            return $firstStep;
144
        }
145
146
        /** @var StepDefinition[] $stepDefinitionsToTest */
147
        $stepDefinitionsToTest = [];
148
        $invalidStep = null;
149
        $stepDefinition = $this->getStepDefinition($step);
150
151
        while ($stepDefinition->hasPreviousDefinition()) {
152
            $stepDefinition = $stepDefinition->getPreviousDefinition();
153
            array_unshift($stepDefinitionsToTest, $stepDefinition);
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->getFormStepPersistence()->hasStepFormValues($step)) {
164
                $invalidStep = $step;
165
                break;
166
            }
167
168
            $result = $this->validateStep($step);
169
170
            if ($result->hasErrors()) {
171
                $invalidStep = $step;
172
                break;
173
            } else {
174
                $this->getFormStepPersistence()->markStepAsValidated($step);
175
            }
176
        }
177
178
        return $invalidStep;
179
    }
180
181
    /**
182
     * Fetches the step persistence object for the current form, which may have
183
     * been stored in the form metadata.
184
     *
185
     * If the form object hash did change since the persistence object was saved
186
     * it is "refreshed" with the new hash (some data are also deleted as they
187
     * are no longer considered as valid).
188
     *
189
     * @return FormStepPersistence
190
     */
191
    protected function getFormStepPersistence()
192
    {
193
        if (null === $this->formStepPersistence) {
194
            $objectHash = $this->formObject->getObjectHash();
195
            $metadata = $this->formObject->getFormMetadata()->getMetadata();
196
197
            if ($metadata->has(self::METADATA_STEP_PERSISTENCE_KEY)) {
198
                $this->formStepPersistence = $metadata->get(self::METADATA_STEP_PERSISTENCE_KEY);
199
200
                if (false === $this->formStepPersistence instanceof FormStepPersistence) {
201
                    unset($this->formStepPersistence);
202
                } elseif ($objectHash !== $this->formStepPersistence->getObjectHash()) {
203
                    $this->formStepPersistence->refreshObjectHash($objectHash);
204
                }
205
            }
206
207
            if (null === $this->formStepPersistence) {
208
                $this->formStepPersistence = GeneralUtility::makeInstance(FormStepPersistence::class, $objectHash);
209
                $metadata->set(self::METADATA_STEP_PERSISTENCE_KEY, $this->formStepPersistence);
210
            }
211
        }
212
213
        return $this->formStepPersistence;
214
    }
215
216
    /**
217
     * @param Step $step
218
     * @return StepDefinition|null
219
     */
220
    public function getStepDefinition(Step $step)
221
    {
222
        $stepDefinition = $this->getFirstStepDefinition();
223
224
        while ($stepDefinition->hasNextSteps()) {
225
            if ($stepDefinition->getStep() === $step) {
226
                return $stepDefinition;
227
            }
228
229
            $stepDefinition = $stepDefinition->getNextSteps()->getDefaultStep();
230
        }
231
232
        return null;
233
    }
234
235
    /**
236
     * Validates (again) the given step with the form data that were previously
237
     * submitted and fetched from the step persistence.
238
     *
239
     * @param Step $step
240
     * @return FormResult
241
     */
242
    protected function validateStep(Step $step)
243
    {
244
        /** @var FormValidationMiddlewareOption $formValidationMiddlewareOptions */
245
        $formValidationMiddlewareOptions = $this->formObject
246
            ->getConfiguration()
247
            ->getPresetMiddlewares()
248
            ->getFormValidationMiddleware()
249
            ->getOptions();
250
251
        /** @var AbstractFormValidator $validator */
252
        $validator = Core::instantiate(
253
            $formValidationMiddlewareOptions->getFormValidatorClassName(),
254
            [
255
                'name'  => $this->formObject->getName(),
256
                'dummy' => true
257
            ]
258
        );
259
260
        $validator->getDataObject()->setValidatedStep($step);
261
262
        /** @var PropertyMapper $propertyMapper */
263
        $propertyMapper = Core::instantiate(PropertyMapper::class);
264
        $form = $propertyMapper->convert($this->getFormStepPersistence()->getStepFormValues($step), $this->formObject->getClassName());
265
266
        return $validator->validate($form);
267
    }
268
269
    /**
270
     * @return StepDefinition
271
     */
272
    protected function getFirstStepDefinition()
273
    {
274
        return $this->formObject->getConfiguration()->getSteps()->getFirstStepDefinition();
275
    }
276
}
277