Completed
Push — middleware-wip ( f76eb7...e5756f )
by Romain
07:43
created

FieldViewHelper::renderLayoutView()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 37
Code Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 37
rs 8.439
c 0
b 0
f 0
cc 5
eloc 24
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\ViewHelpers;
15
16
use Romm\Formz\Configuration\View\Layouts\Layout;
17
use Romm\Formz\Configuration\View\View;
18
use Romm\Formz\Exceptions\ContextNotFoundException;
19
use Romm\Formz\Exceptions\EntryNotFoundException;
20
use Romm\Formz\Exceptions\InvalidArgumentTypeException;
21
use Romm\Formz\Exceptions\InvalidArgumentValueException;
22
use Romm\Formz\Exceptions\PropertyNotAccessibleException;
23
use Romm\Formz\Service\StringService;
24
use Romm\Formz\Service\ViewHelper\FieldViewHelperService;
25
use Romm\Formz\Service\ViewHelper\FormViewHelperService;
26
use Romm\Formz\Service\ViewHelper\SlotViewHelperService;
27
use TYPO3\CMS\Core\Utility\ArrayUtility;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
30
31
/**
32
 * This view helper is used to automatize the rendering of a field layout. It
33
 * will use the TypoScript properties at the path `config.tx_formz.view.layout`.
34
 *
35
 * You need to indicate the name of the field which will be rendered, and the
36
 * name of the layout which should be used (it must be present in the TypoScript
37
 * configuration).
38
 *
39
 * Example of layout: `bootstrap.3-cols`. You may indicate only the group, then
40
 * the name of the layout will be set to `default` (if you use the layout group
41
 * `bootstrap`, the layout `default` will be used, only if it does exist of
42
 * course).
43
 */
44
class FieldViewHelper extends AbstractViewHelper
45
{
46
    /**
47
     * @var bool
48
     */
49
    protected $escapeOutput = false;
50
51
    /**
52
     * @var array
53
     */
54
    public static $reservedVariablesNames = ['layout', 'formName', 'fieldName', 'fieldId'];
55
56
    /**
57
     * @var FormViewHelperService
58
     */
59
    protected $formService;
60
61
    /**
62
     * @var FieldViewHelperService
63
     */
64
    protected $fieldService;
65
66
    /**
67
     * @var SlotViewHelperService
68
     */
69
    protected $slotService;
70
71
    /**
72
     * @var array
73
     */
74
    protected $originalArguments = [];
75
76
    /**
77
     * @inheritdoc
78
     */
79
    public function initializeArguments()
80
    {
81
        $this->registerArgument('name', 'string', 'Name of the field which should be rendered.', true);
82
        $this->registerArgument('layout', 'string', 'Path of the TypoScript layout which will be used.', true);
83
        $this->registerArgument('arguments', 'array', 'Arbitrary arguments which will be sent to the field template.', false, []);
84
    }
85
86
    /**
87
     * @inheritdoc
88
     */
89
    public function render()
90
    {
91
        /*
92
         * First, we check if this view helper is called from within the
93
         * `FormViewHelper`, because it would not make sense anywhere else.
94
         */
95
        if (false === $this->formService->formContextExists()) {
96
            throw ContextNotFoundException::fieldViewHelperFormContextNotFound();
97
        }
98
99
        /*
100
         * Then, we inject the wanted field in the `FieldService` so we can know
101
         * later which field we're working with.
102
         */
103
        $this->injectFieldInService($this->arguments['name']);
104
105
        /*
106
         * Calling this here will process every view helper beneath this one,
107
         * allowing options and slots to be used correctly in the field layout.
108
         */
109
        $this->renderChildren();
110
111
        /*
112
         * We need to store original arguments declared for the current view
113
         * context, because we may override them during the rendering of this
114
         * view helper.
115
         */
116
        $this->storeOriginalArguments();
117
118
        /*
119
         * We merge the arguments with the ones registered with the
120
         * `OptionViewHelper`.
121
         */
122
        $templateArguments = $this->arguments['arguments'] ?: [];
123
        ArrayUtility::mergeRecursiveWithOverrule($templateArguments, $this->fieldService->getFieldOptions());
124
125
        $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
126
        $currentView = $viewHelperVariableContainer->getView();
127
128
        $result = $this->renderLayoutView($templateArguments);
129
130
        /*
131
         * Resetting all services data.
132
         */
133
        $this->fieldService->resetState();
134
        $this->slotService->resetState();
135
136
        $viewHelperVariableContainer->setView($currentView);
137
        $this->restoreOriginalArguments($templateArguments);
138
139
        return $result;
140
    }
141
142
    /**
143
     * Will render the associated Fluid view for this field (configured with the
144
     * `layout` argument).
145
     *
146
     * @param array $templateArguments
147
     * @return string
148
     */
149
    protected function renderLayoutView(array $templateArguments)
150
    {
151
        $fieldName = $this->arguments['name'];
152
        $formObject = $this->formService->getFormObject();
153
        $formConfiguration = $formObject->getDefinition();
154
        $viewConfiguration = $formConfiguration->getRootConfiguration()->getView();
155
        $layout = $this->getLayout($viewConfiguration);
156
157
        $templateArguments['layout'] = $layout->getLayout();
158
        $templateArguments['formName'] = $formObject->getName();
159
        $templateArguments['fieldName'] = $fieldName;
160
        $templateArguments['fieldId'] = ($templateArguments['fieldId']) ?: StringService::get()->sanitizeString('formz-' . $formObject->getName() . '-' . $fieldName);
161
162
        $view = $this->fieldService->getView($layout);
163
164
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '8.0.0', '<')) {
165
            $view->setRenderingContext($this->renderingContext);
166
        } else {
167
            $view->setControllerContext($this->controllerContext);
168
169
            $variableProvider = $this->getVariableProvider();
170
171
            foreach ($templateArguments as $key => $value) {
172
                if ($variableProvider->exists($key)) {
173
                    $variableProvider->remove($key);
174
                }
175
176
                $variableProvider->add($key, $value);
177
            }
178
        }
179
180
        $view->setLayoutRootPaths($viewConfiguration->getAbsoluteLayoutRootPaths());
181
        $view->setPartialRootPaths($viewConfiguration->getAbsolutePartialRootPaths());
182
        $view->assignMultiple($templateArguments);
183
184
        return $view->render();
185
    }
186
187
    /**
188
     * Will check that the given field exists in the current form definition and
189
     * inject it in the `FieldService` as `currentField`.
190
     *
191
     * Throws an error if the field is not found or incorrect.
192
     *
193
     * @param string $fieldName
194
     * @throws InvalidArgumentTypeException
195
     * @throws PropertyNotAccessibleException
196
     */
197
    protected function injectFieldInService($fieldName)
198
    {
199
        $formObject = $this->formService->getFormObject();
200
        $formConfiguration = $formObject->getDefinition();
201
202
        if (false === is_string($fieldName)) {
203
            throw InvalidArgumentTypeException::fieldViewHelperInvalidTypeNameArgument();
204
        } elseif (false === $formConfiguration->hasField($fieldName)) {
205
            throw PropertyNotAccessibleException::fieldViewHelperFieldNotAccessibleInForm($formObject, $fieldName);
206
        }
207
208
        $this->fieldService->setCurrentField($formConfiguration->getField($fieldName));
209
    }
210
211
    /**
212
     * Returns the layout instance used by this field.
213
     *
214
     * @param View $viewConfiguration
215
     * @return Layout
216
     * @throws EntryNotFoundException
217
     * @throws InvalidArgumentTypeException
218
     * @throws InvalidArgumentValueException
219
     */
220
    protected function getLayout(View $viewConfiguration)
221
    {
222
        $layout = $this->arguments['layout'];
223
224
        if (false === is_string($layout)) {
225
            throw InvalidArgumentTypeException::invalidTypeNameArgumentFieldViewHelper($layout);
226
        }
227
228
        list($layoutName, $templateName) = GeneralUtility::trimExplode('.', $layout);
229
230
        if (empty($templateName)) {
231
            $templateName = 'default';
232
        }
233
234
        if (empty($layoutName)) {
235
            throw InvalidArgumentValueException::fieldViewHelperEmptyLayout();
236
        }
237
238
        if (false === $viewConfiguration->hasLayout($layoutName)) {
239
            throw EntryNotFoundException::fieldViewHelperLayoutNotFound($layout);
240
        }
241
242
        if (false === $viewConfiguration->getLayout($layoutName)->hasItem($templateName)) {
243
            throw EntryNotFoundException::fieldViewHelperLayoutItemNotFound($layout, $templateName);
244
        }
245
246
        return $viewConfiguration->getLayout($layoutName)->getItem($templateName);
247
    }
248
249
    /**
250
     * Stores some arguments which may already have been initialized, and could
251
     * be overridden in the local scope.
252
     */
253
    protected function storeOriginalArguments()
254
    {
255
        $this->originalArguments = [];
256
        $variableProvider = $this->getVariableProvider();
257
258
        foreach (self::$reservedVariablesNames as $key) {
259
            if ($variableProvider->exists($key)) {
260
                $this->originalArguments[$key] = $variableProvider->get($key);
261
            }
262
        }
263
    }
264
265
    /**
266
     * Will restore original arguments in the template variable container.
267
     *
268
     * @param array $templateArguments
269
     */
270
    protected function restoreOriginalArguments(array $templateArguments)
271
    {
272
        $variableProvider = $this->getVariableProvider();
273
274
        foreach ($variableProvider->getAllIdentifiers() as $identifier) {
275
            if (array_key_exists($identifier, $templateArguments)) {
276
                $variableProvider->remove($identifier);
277
            }
278
        }
279
280
        foreach ($this->originalArguments as $key => $value) {
281
            if ($variableProvider->exists($key)) {
282
                $variableProvider->remove($key);
283
            }
284
285
            $variableProvider->add($key, $value);
286
        }
287
    }
288
289
    /**
290
     * @param FormViewHelperService $service
291
     */
292
    public function injectFormService(FormViewHelperService $service)
293
    {
294
        $this->formService = $service;
295
    }
296
297
    /**
298
     * @param FieldViewHelperService $service
299
     */
300
    public function injectFieldService(FieldViewHelperService $service)
301
    {
302
        $this->fieldService = $service;
303
    }
304
305
    /**
306
     * @param SlotViewHelperService $slotService
307
     */
308
    public function injectSlotService(SlotViewHelperService $slotService)
309
    {
310
        $this->slotService = $slotService;
311
    }
312
}
313