Completed
Push — unit-test-view-helpers ( d4d7d8...d9d29f )
by Romain
06:46
created

FieldViewHelper::injectFieldInService()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 12
nc 3
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\ViewHelpers;
15
16
use Romm\Formz\Configuration\View\Layouts\Layout;
17
use Romm\Formz\Configuration\View\View;
18
use Romm\Formz\Exceptions\EntryNotFoundException;
19
use Romm\Formz\Exceptions\InvalidArgumentTypeException;
20
use Romm\Formz\Service\StringService;
21
use Romm\Formz\ViewHelpers\Service\FieldService;
22
use Romm\Formz\ViewHelpers\Service\FormService;
23
use Romm\Formz\ViewHelpers\Service\SectionService;
24
use TYPO3\CMS\Core\Utility\GeneralUtility;
25
use TYPO3\CMS\Extbase\Utility\ArrayUtility;
26
27
/**
28
 * This view helper is used to automatize the rendering of a field layout. It
29
 * will use the TypoScript properties at the path `config.tx_formz.view.layout`.
30
 *
31
 * You need to indicate the name of the field which will be rendered, and the
32
 * name of the layout which should be used (it must be present in the TypoScript
33
 * configuration).
34
 *
35
 * Example of layout: `bootstrap.3-cols`. You may indicate only the group, then
36
 * the name of the layout will be set to `default` (if you use the layout group
37
 * `bootstrap`, the layout `default` will be used, only if it does exist of
38
 * course).
39
 */
40
class FieldViewHelper extends AbstractViewHelper
41
{
42
    /**
43
     * @var FormService
44
     */
45
    protected $formService;
46
47
    /**
48
     * @var FieldService
49
     */
50
    protected $fieldService;
51
52
    /**
53
     * @var SectionService
54
     */
55
    protected $sectionService;
56
57
    /**
58
     * @var array
59
     */
60
    protected $originalArguments = [];
61
62
    /**
63
     * @inheritdoc
64
     */
65
    public function initializeArguments()
66
    {
67
        $this->registerArgument('name', 'string', 'Name of the field which should be rendered.', true);
68
        $this->registerArgument('layout', 'string', 'Path of the TypoScript layout which will be used.', true);
69
        $this->registerArgument('arguments', 'array', 'Arbitrary arguments which will be sent to the field template.', false, []);
70
    }
71
72
    /**
73
     * @inheritdoc
74
     */
75
    public function render()
76
    {
77
        $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
78
79
        /*
80
         * First, we check if this view helper is called from within the
81
         * `FormViewHelper`, because it would not make sense anywhere else.
82
         */
83
        $this->formService->checkIsInsideFormViewHelper();
84
85
        /*
86
         * Then, we inject the wanted field in the `FieldService` so we can know
87
         * later which field we're working with.
88
         */
89
        $this->injectFieldInService($this->arguments['name']);
90
91
        /*
92
         * Calling this here will process every view helper beneath this one,
93
         * allowing options and sections to be used correctly in the field
94
         * layout.
95
         */
96
        $this->renderChildren();
97
98
        /*
99
         * We need to store original arguments declared for the current view
100
         * context, because we may override them during the rendering of this
101
         * view helper.
102
         */
103
        $this->storeOriginalArguments();
104
105
        /*
106
         * We merge the arguments with the ones registered with the
107
         * `OptionViewHelper`.
108
         */
109
        $templateArguments = $this->arguments['arguments'];
110
        $templateArguments = ArrayUtility::arrayMergeRecursiveOverrule($templateArguments, $this->fieldService->getFieldOptions());
111
112
        $currentView = $viewHelperVariableContainer->getView();
113
114
        $result = $this->renderLayoutView($templateArguments);
115
116
        /*
117
         * Resetting all services data.
118
         */
119
        $this->fieldService->removeCurrentField();
120
        $this->fieldService->resetFieldOptions();
121
        $this->sectionService->resetSectionClosures();
122
123
        $viewHelperVariableContainer->setView($currentView);
124
        $this->restoreOriginalArguments($templateArguments);
125
126
        return $result;
127
    }
128
129
    /**
130
     * Will render the associated Fluid view for this field (configured with the
131
     * `layout` argument).
132
     *
133
     * @param array $templateArguments
134
     * @return string
135
     */
136
    protected function renderLayoutView(array $templateArguments)
137
    {
138
        $fieldName = $this->arguments['name'];
139
        $formObject = $this->formService->getFormObject();
140
        $formConfiguration = $formObject->getConfiguration();
141
        $viewConfiguration = $formConfiguration->getFormzConfiguration()->getView();
142
        $layout = $this->getLayout($viewConfiguration);
143
144
        $templateArguments['layout'] = $layout->getLayout();
145
        $templateArguments['formName'] = $formObject->getName();
146
        $templateArguments['fieldName'] = $fieldName;
147
        $templateArguments['fieldId'] = ($templateArguments['fieldId']) ?: StringService::get()->sanitizeString('formz-' . $formObject->getName() . '-' . $fieldName);
148
149
        $view = $this->fieldService->getView();
150
        $view->setTemplatePathAndFilename($layout->getTemplateFile());
151
        $view->setLayoutRootPaths($viewConfiguration->getLayoutRootPaths());
152
        $view->setPartialRootPaths($viewConfiguration->getPartialRootPaths());
153
        $view->setRenderingContext($this->renderingContext);
154
        $view->assignMultiple($templateArguments);
155
156
        return $view->render();
157
    }
158
159
    /**
160
     * Will check that the given field exists in the current form definition and
161
     * inject it in the `FieldService` as `currentField`.
162
     *
163
     * Throws an error if the field is not found or incorrect.
164
     *
165
     * @param string $fieldName
166
     * @throws EntryNotFoundException
167
     * @throws InvalidArgumentTypeException
168
     */
169
    protected function injectFieldInService($fieldName)
170
    {
171
        $formObject = $this->formService->getFormObject();
172
        $formConfiguration = $formObject->getConfiguration();
173
174
        if (false === is_string($fieldName)) {
175
            throw new InvalidArgumentTypeException(
176
                'The argument "name" of the view helper "' . __CLASS__ . '" must be a string.',
177
                1465243479
178
            );
179
        } elseif (false === $formConfiguration->hasField($fieldName)) {
180
            throw new EntryNotFoundException(
181
                'The form "' . $formObject->getClassName() . '" does not have an accessible property "' . $fieldName . '". Please be sure this property exists, and it has a proper getter to access its value.',
182
                1465243619);
183
        }
184
185
        $this->fieldService->setCurrentField($formConfiguration->getField($fieldName));
0 ignored issues
show
Bug introduced by
It seems like $formConfiguration->getField($fieldName) can be null; however, setCurrentField() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
186
    }
187
188
    /**
189
     * Returns the layout instance used by this field.
190
     *
191
     * @param View $viewConfiguration
192
     * @return Layout
193
     * @throws EntryNotFoundException
194
     */
195
    protected function getLayout(View $viewConfiguration)
196
    {
197
        $layoutFound = true;
198
        $layout = $this->arguments['layout'];
199
200
        list($layoutName, $templateName) = GeneralUtility::trimExplode('.', $layout);
201
202
        if (false === is_string($templateName)) {
203
            $templateName = 'default';
204
        }
205
206
        if (false === is_string($layoutName)) {
207
            $layoutFound = false;
208
        } elseif (false === $viewConfiguration->hasLayout($layoutName)) {
209
            $layoutFound = false;
210
        } elseif (false === $viewConfiguration->getLayout($layoutName)->hasItem($templateName)) {
211
            $layoutFound = false;
212
        }
213
214
        if (false === $layoutFound) {
215
            throw new EntryNotFoundException(
216
                'The layout "' . $layout . '" could not be found. Please check your TypoScript configuration.',
217
                1465243586
218
            );
219
        }
220
221
        return $viewConfiguration->getLayout($layoutName)->getItem($templateName);
222
    }
223
224
    /**
225
     * Returns the value of the current variable in the variable container at
226
     * the index `$key`, or null if it is not found.
227
     *
228
     * @param string $key
229
     * @return mixed|null
230
     */
231
    protected function getTemplateVariableContainerValue($key)
232
    {
233
        $templateVariableContainer = $this->renderingContext->getTemplateVariableContainer();
234
235
        return ($templateVariableContainer->exists($key))
236
            ? $templateVariableContainer->get($key)
237
            : null;
238
    }
239
240
    /**
241
     * Stores some arguments which may already have been initialized.
242
     */
243
    protected function storeOriginalArguments()
244
    {
245
        $this->originalArguments = [
246
            'layout'    => $this->getTemplateVariableContainerValue('layout'),
247
            'formName'  => $this->getTemplateVariableContainerValue('formName'),
248
            'fieldName' => $this->getTemplateVariableContainerValue('fieldName'),
249
            'fieldId'   => $this->getTemplateVariableContainerValue('fieldId')
250
        ];
251
    }
252
253
    /**
254
     * Will restore original arguments in the template variable container.
255
     *
256
     * @param array $templateArguments
257
     */
258
    protected function restoreOriginalArguments(array $templateArguments)
259
    {
260
        $templateVariableContainer = $this->renderingContext->getTemplateVariableContainer();
261
262
        $identifiers = $templateVariableContainer->getAllIdentifiers();
263
        foreach ($identifiers as $identifier) {
264
            if (array_key_exists($identifier, $templateArguments)) {
265
                $templateVariableContainer->remove($identifier);
266
            }
267
        }
268
269
        foreach ($this->originalArguments as $key => $value) {
270
            if (null !== $value) {
271
                if ($templateVariableContainer->exists($key)) {
272
                    $templateVariableContainer->remove($key);
273
                }
274
275
                $templateVariableContainer->add($key, $value);
276
            }
277
        }
278
    }
279
280
    /**
281
     * @param FormService $service
282
     */
283
    public function injectFormService(FormService $service)
284
    {
285
        $this->formService = $service;
286
    }
287
288
    /**
289
     * @param FieldService $service
290
     */
291
    public function injectFieldService(FieldService $service)
292
    {
293
        $this->fieldService = $service;
294
    }
295
296
    /**
297
     * @param SectionService $sectionService
298
     */
299
    public function injectSectionService(SectionService $sectionService)
300
    {
301
        $this->sectionService = $sectionService;
302
    }
303
}
304