Completed
Push — wip/steps ( 5d4c08...d2fcdb )
by Romain
02:10
created

FormViewHelperService::formatDataAttributes()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 15
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 8.8571
c 0
b 0
f 0
cc 6
eloc 10
nc 5
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\Service\ViewHelper\Form;
15
16
use DateTime;
17
use Romm\Formz\AssetHandler\Html\DataAttributesAssetHandler;
18
use Romm\Formz\Behaviours\BehavioursManager;
19
use Romm\Formz\Core\Core;
20
use Romm\Formz\Error\FormResult;
21
use Romm\Formz\Exceptions\DuplicateEntryException;
22
use Romm\Formz\Form\Definition\Step\Step\Step;
23
use Romm\Formz\Form\FormObject\FormObject;
24
use Romm\Formz\Validation\Validator\Form\AbstractFormValidator;
25
use Romm\Formz\Validation\Validator\Form\DefaultFormValidator;
26
use Traversable;
27
use TYPO3\CMS\Core\SingletonInterface;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
30
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
31
use TYPO3\CMS\Extbase\Error\Error;
32
use TYPO3\CMS\Extbase\Error\Result;
33
use TYPO3\CMS\Extbase\Mvc\Web\Request;
34
use TYPO3\CMS\Fluid\Core\ViewHelper\ViewHelperVariableContainer;
35
use TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper;
36
37
/**
38
 * This class contains methods that help view helpers to manipulate data and
39
 * know more things concerning the current form state.
40
 *
41
 * It is mainly configured inside the `FormViewHelper`, and used in other
42
 * view helpers.
43
 */
44
class FormViewHelperService implements SingletonInterface
45
{
46
    /**
47
     * @var bool
48
     */
49
    protected $formContext = false;
50
51
    /**
52
     * @var FormObject
53
     */
54
    protected $formObject;
55
56
    /**
57
     * @var Request
58
     */
59
    protected $request;
60
61
    /**
62
     * @var Result
63
     */
64
    protected $result;
65
66
    /**
67
     * Reset every state that can be used by this service.
68
     */
69
    public function resetState()
70
    {
71
        $this->formContext = false;
72
        $this->formObject = null;
73
        $this->request = null;
74
    }
75
76
    /**
77
     * Will activate the form context, changing the result returned by the
78
     * function `formContextExists()`.
79
     *
80
     * @return FormViewHelperService
81
     * @throws DuplicateEntryException
82
     */
83
    public function activateFormContext()
84
    {
85
        if (true === $this->formContext) {
86
            throw DuplicateEntryException::duplicatedFormContext();
87
        }
88
89
        $this->formContext = true;
90
        $this->result = new Result;
91
92
        return $this;
93
    }
94
95
    /**
96
     * Returns `true` if the `FormViewHelper` context exists.
97
     *
98
     * @return bool
99
     */
100
    public function formContextExists()
101
    {
102
        return $this->formContext;
103
    }
104
105
    /**
106
     * Will loop on the submitted form fields and apply behaviours if their
107
     * configuration contains.
108
     */
109
    public function applyBehavioursOnSubmittedForm()
110
    {
111
        if ($this->formObject->formWasSubmitted()) {
112
            $request = $this->request->getOriginalRequest();
113
            $formName = $this->formObject->getName();
114
115
            if ($request
116
                && $request->hasArgument($formName)
117
            ) {
118
                /** @var BehavioursManager $behavioursManager */
119
                $behavioursManager = GeneralUtility::makeInstance(BehavioursManager::class);
120
121
                /** @var array $originalForm */
122
                $originalForm = $request->getArgument($formName);
123
124
                $formProperties = $behavioursManager->applyBehaviourOnPropertiesArray(
125
                    $originalForm,
126
                    $this->formObject->getDefinition()
127
                );
128
129
                $request->setArgument($formName, $formProperties);
130
            }
131
        }
132
    }
133
134
    /**
135
     * Takes care of injecting data for the form.
136
     *
137
     * If the form was generated using a content object, information about it
138
     * are injected, to be retrieved later to be able for instance to fetch the
139
     * object settings (TypoScript, FlexForm, ...).
140
     */
141
    public function injectFormRequestData()
142
    {
143
        if (false === $this->formObject->hasForm()) {
144
            return;
145
        }
146
147
        $requestData = $this->formObject->getRequestData();
148
149
        $currentStepIdentifier = $this->getCurrentStep()
150
            ? $this->getCurrentStep()->getIdentifier()
151
            : null;
152
        $requestData->setCurrentStepIdentifier($currentStepIdentifier);
153
154
        /** @var ConfigurationManager $configurationManager */
155
        $configurationManager = Core::instantiate(ConfigurationManagerInterface::class);
156
157
        $contentObject = $configurationManager->getContentObject();
158
159
        if (null !== $contentObject) {
160
            $requestData->setContentObjectTable($contentObject->getCurrentTable());
161
            $requestData->setContentObjectUid($contentObject->data['uid']);
162
        }
163
    }
164
165
    /**
166
     * Fetches all data attributes that are bound to the form: fields values,
167
     * validation result and others.
168
     *
169
     * @param DataAttributesAssetHandler $dataAttributesAssetHandler
170
     * @return array
171
     */
172
    public function getDataAttributes(DataAttributesAssetHandler $dataAttributesAssetHandler)
173
    {
174
        $dataAttributes = [];
175
176
        if ($this->formObject->hasForm()) {
177
            /*
178
             * Getting the data attributes for the form values. It is needed to
179
             * have a validation result because a field can be deactivated (in
180
             * that case, the data attribute for this field is removed).
181
             */
182
            if (false === $this->formObject->formWasValidated()) {
183
                $formResult = $this->getFormValidationResult();
184
            } else {
185
                $formResult = $this->formObject->getFormResult();
186
            }
187
188
            $dataAttributes += $dataAttributesAssetHandler->getFieldsValuesDataAttributes($formResult);
189
        }
190
191
        if (true === $this->formObject->formWasSubmitted()) {
192
            $dataAttributes += $dataAttributesAssetHandler->getFieldSubmissionDoneDataAttribute();
193
        }
194
195
        if (true === $this->formObject->formWasValidated()) {
196
            $dataAttributes += $dataAttributesAssetHandler->getFieldsValidDataAttributes();
197
            $dataAttributes += $dataAttributesAssetHandler->getFieldsMessagesDataAttributes();
198
        }
199
200
        $dataAttributes = $this->formatDataAttributes($dataAttributes);
201
202
        return $dataAttributes;
203
    }
204
205
    /**
206
     * Checks the type of every data attribute and formats it if needed.
207
     *
208
     * @param array $dataAttributes
209
     * @return array
210
     */
211
    protected function formatDataAttributes(array $dataAttributes)
212
    {
213
        foreach ($dataAttributes as $key => $value) {
214
            if (is_array($value) || $value instanceof Traversable) {
215
                $dataAttributes[$key] = implode(',', $value);
216
            } elseif ($value instanceof DateTime) {
217
                $format = $GLOBALS['TYPO3_CONF_VARS']['SYS']['ddmmyy'] . ' ' . $GLOBALS['TYPO3_CONF_VARS']['SYS']['hhmm'];
218
                $dataAttributes[$key] = $value->format($format);
219
            } elseif (false === is_string($value)) {
220
                $dataAttributes[$key] = (string)$value;
221
            }
222
        }
223
224
        return $dataAttributes;
225
    }
226
227
    /**
228
     * Checks if the form uses steps, in which case the current step is needed
229
     * in order to display the form. If the step is not found, an exception is
230
     * thrown.
231
     */
232
    public function checkStepDefinition()
233
    {
234
        if ($this->formObject->getDefinition()->hasSteps()
235
            && null === $this->getCurrentStep()
236
        ) {
237
            throw new \Exception('todo'); // @todo
238
        }
239
    }
240
241
    /**
242
     * Will check all the fields that have been added below the form view
243
     * helper: each field that is found in the form definition and is *not*
244
     * supported by the current step will add an error and cancel the form
245
     * rendering.
246
     *
247
     * @param ViewHelperVariableContainer $variableContainer
248
     */
249
    public function checkStepFields(ViewHelperVariableContainer $variableContainer)
250
    {
251
        $currentStep = $this->getCurrentStep();
252
253
        if (null === $currentStep) {
254
            return;
255
        }
256
257
        $unsupportedFieldsList = [];
258
        $formDefinition = $this->formObject->getDefinition();
259
        $fieldNames = $this->getCurrentFormFieldNames($variableContainer);
260
261
        foreach ($fieldNames as $fieldName) {
262
            if (false === $formDefinition->hasField($fieldName)) {
263
                continue;
264
            }
265
266
            $field = $formDefinition->getField($fieldName);
267
268
            if (false === $currentStep->supportsField($field)) {
269
                $unsupportedFieldsList[] = $fieldName;
270
            }
271
        }
272
273
        if ($unsupportedFieldsList) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $unsupportedFieldsList of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
274
            $error = new Error(
275
                'The following fields are not supported by the step "%s": "%s". Please add these fields to the supported fields list of the step in order to render it in your template.',
276
                1494430935,
277
                [$currentStep->getIdentifier(), implode('", "', $unsupportedFieldsList)]
278
            );
279
            $this->result->addError($error);
280
        }
281
    }
282
283
    /**
284
     * Returns the list of fields that have been added below the form view
285
     * helper.
286
     *
287
     * @param ViewHelperVariableContainer $variableContainer
288
     * @return array
289
     */
290
    public function getCurrentFormFieldNames(ViewHelperVariableContainer $variableContainer)
291
    {
292
        $formFieldNames = $variableContainer->get(FormViewHelper::class, 'formFieldNames');
293
        $cleanFormFieldNames = [];
294
295
        foreach ($formFieldNames as $fieldName) {
296
            $explode = explode('[', $fieldName);
297
298
            if (count($explode) >= 3) {
299
                $formName = rtrim($explode[1], ']');
300
                $fieldName = rtrim($explode[2], ']');
301
302
                if ($formName === $this->formObject->getName()
303
                    && $fieldName !== '__identity'
304
                ) {
305
                    $cleanFormFieldNames[$fieldName] = $fieldName;
306
                }
307
            }
308
        }
309
310
        return $cleanFormFieldNames;
311
    }
312
313
    /**
314
     * @return Step|null
315
     */
316
    public function getCurrentStep()
317
    {
318
        return $this->formObject->fetchCurrentStep($this->request)->getCurrentStep();
319
    }
320
321
    /**
322
     * @return FormObject
323
     */
324
    public function getFormObject()
325
    {
326
        return $this->formObject;
327
    }
328
329
    /**
330
     * @param FormObject $formObject
331
     */
332
    public function setFormObject(FormObject $formObject)
333
    {
334
        $this->formObject = $formObject;
335
    }
336
337
    /**
338
     * @param Request $request
339
     */
340
    public function setRequest(Request $request)
341
    {
342
        $this->request = $request;
343
    }
344
345
    /**
346
     * @return Result
347
     */
348
    public function getResult()
349
    {
350
        return $this->result;
351
    }
352
353
    /**
354
     * @return FormResult
355
     */
356
    protected function getFormValidationResult()
357
    {
358
        $formValidator = $this->getFormValidator($this->formObject->getName());
359
360
        return $formValidator->validate($this->formObject->getForm());
361
    }
362
363
    /**
364
     * @param string $formName
365
     * @return AbstractFormValidator
366
     */
367
    protected function getFormValidator($formName)
368
    {
369
        /** @var AbstractFormValidator $validator */
370
        $validator = Core::instantiate(
371
            DefaultFormValidator::class,
372
            [
373
                'name'  => $formName,
374
                'dummy' => true
375
            ]
376
        );
377
378
        return $validator;
379
    }
380
}
381