Completed
Push — middleware ( 3e173d...488069 )
by Romain
02:53
created

FieldViewHelper::injectSlotService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
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\Core\Core;
19
use Romm\Formz\Exceptions\ContextNotFoundException;
20
use Romm\Formz\Exceptions\EntryNotFoundException;
21
use Romm\Formz\Exceptions\InvalidArgumentTypeException;
22
use Romm\Formz\Exceptions\InvalidArgumentValueException;
23
use Romm\Formz\Exceptions\PropertyNotAccessibleException;
24
use Romm\Formz\Service\StringService;
25
use Romm\Formz\Service\ViewHelper\FieldViewHelperService;
26
use Romm\Formz\Service\ViewHelper\FormViewHelperService;
27
use Romm\Formz\Service\ViewHelper\SlotViewHelperService;
28
use TYPO3\CMS\Core\Utility\GeneralUtility;
29
use TYPO3\CMS\Core\Utility\VersionNumberUtility;
30
use TYPO3\CMS\Extbase\Utility\ArrayUtility;
31
use TYPO3\CMS\Fluid\View\StandaloneView;
32
33
/**
34
 * This view helper is used to automatize the rendering of a field layout. It
35
 * will use the TypoScript properties at the path `config.tx_formz.view.layout`.
36
 *
37
 * You need to indicate the name of the field which will be rendered, and the
38
 * name of the layout which should be used (it must be present in the TypoScript
39
 * configuration).
40
 *
41
 * Example of layout: `bootstrap.3-cols`. You may indicate only the group, then
42
 * the name of the layout will be set to `default` (if you use the layout group
43
 * `bootstrap`, the layout `default` will be used, only if it does exist of
44
 * course).
45
 */
46
class FieldViewHelper extends AbstractViewHelper
47
{
48
    /**
49
     * @var bool
50
     */
51
    protected $escapeOutput = false;
52
53
    /**
54
     * @var array
55
     */
56
    public static $reservedVariablesNames = ['layout', 'formName', 'fieldName', 'fieldId'];
57
58
    /**
59
     * @var FormViewHelperService
60
     */
61
    protected $formService;
62
63
    /**
64
     * @var FieldViewHelperService
65
     */
66
    protected $fieldService;
67
68
    /**
69
     * @var SlotViewHelperService
70
     */
71
    protected $slotService;
72
73
    /**
74
     * @var array
75
     */
76
    protected $originalArguments = [];
77
78
    /**
79
     * @inheritdoc
80
     */
81
    public function initializeArguments()
82
    {
83
        $this->registerArgument('name', 'string', 'Name of the field which should be rendered.', true);
84
        $this->registerArgument('layout', 'string', 'Path of the TypoScript layout which will be used.', true);
85
        $this->registerArgument('arguments', 'array', 'Arbitrary arguments which will be sent to the field template.', false, []);
86
    }
87
88
    /**
89
     * @inheritdoc
90
     */
91
    public function render()
92
    {
93
        $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
94
95
        /*
96
         * First, we check if this view helper is called from within the
97
         * `FormViewHelper`, because it would not make sense anywhere else.
98
         */
99
        if (false === $this->formService->formContextExists()) {
100
            throw ContextNotFoundException::fieldViewHelperFormContextNotFound();
101
        }
102
103
        /*
104
         * Then, we inject the wanted field in the `FieldService` so we can know
105
         * later which field we're working with.
106
         */
107
        $this->injectFieldInService($this->arguments['name']);
108
109
        /*
110
         * Calling this here will process every view helper beneath this one,
111
         * allowing options and slots to be used correctly in the field layout.
112
         */
113
        $this->renderChildren();
114
115
        /*
116
         * We need to store original arguments declared for the current view
117
         * context, because we may override them during the rendering of this
118
         * view helper.
119
         */
120
        $this->storeOriginalArguments();
121
122
        /*
123
         * We merge the arguments with the ones registered with the
124
         * `OptionViewHelper`.
125
         */
126
        $templateArguments = $this->arguments['arguments'] ?: [];
127
        $templateArguments = ArrayUtility::arrayMergeRecursiveOverrule($templateArguments, $this->fieldService->getFieldOptions());
0 ignored issues
show
Deprecated Code introduced by
The method TYPO3\CMS\Extbase\Utilit...ergeRecursiveOverrule() has been deprecated with message: since TYPO3 v8, will be removed in TYPO3 v9, use array_replace_recursive() instead if possible, other see the ArrayUtility in EXT:core

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
128
129
        $currentView = $viewHelperVariableContainer->getView();
130
131
        $result = $this->renderLayoutView($templateArguments);
132
133
        /*
134
         * Resetting all services data.
135
         */
136
        $this->fieldService->resetState();
137
        $this->slotService->resetState();
138
139
        $viewHelperVariableContainer->setView($currentView);
140
        $this->restoreOriginalArguments($templateArguments);
141
142
        return $result;
143
    }
144
145
    /**
146
     * Will render the associated Fluid view for this field (configured with the
147
     * `layout` argument).
148
     *
149
     * @param array $templateArguments
150
     * @return string
151
     */
152
    protected function renderLayoutView(array $templateArguments)
153
    {
154
        $fieldName = $this->arguments['name'];
155
        $formObject = $this->formService->getFormObject();
156
        $formConfiguration = $formObject->getConfiguration();
157
        $viewConfiguration = $formConfiguration->getFormzConfiguration()->getView();
158
        $layout = $this->getLayout($viewConfiguration);
159
160
        $templateArguments['layout'] = $layout->getLayout();
161
        $templateArguments['formName'] = $formObject->getName();
162
        $templateArguments['fieldName'] = $fieldName;
163
        $templateArguments['fieldId'] = ($templateArguments['fieldId']) ?: StringService::get()->sanitizeString('formz-' . $formObject->getName() . '-' . $fieldName);
164
165
        /** @var StandaloneView $view */
166
        $view = Core::instantiate(StandaloneView::class);
167
        $view->setTemplatePathAndFilename($layout->getTemplateFile());
168
        $view->setLayoutRootPaths($viewConfiguration->getLayoutRootPaths());
169
        $view->setPartialRootPaths($viewConfiguration->getPartialRootPaths());
170
171
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '8.0.0', '<')) {
172
            $view->setRenderingContext($this->renderingContext);
173
        } else {
174
            $variableProvider = $this->getVariableProvider();
175
            foreach ($templateArguments as $key => $value) {
176
                if ($variableProvider->exists($key)) {
177
                    $variableProvider->remove($key);
178
                }
179
180
                $variableProvider->add($key, $value);
181
            }
182
        }
183
184
        $view->assignMultiple($templateArguments);
185
186
        return $view->render();
187
    }
188
189
    /**
190
     * Will check that the given field exists in the current form definition and
191
     * inject it in the `FieldService` as `currentField`.
192
     *
193
     * Throws an error if the field is not found or incorrect.
194
     *
195
     * @param string $fieldName
196
     * @throws InvalidArgumentTypeException
197
     * @throws PropertyNotAccessibleException
198
     */
199
    protected function injectFieldInService($fieldName)
200
    {
201
        $formObject = $this->formService->getFormObject();
202
        $formConfiguration = $formObject->getConfiguration();
203
204
        if (false === is_string($fieldName)) {
205
            throw InvalidArgumentTypeException::fieldViewHelperInvalidTypeNameArgument();
206
        } elseif (false === $formConfiguration->hasField($fieldName)) {
207
            throw PropertyNotAccessibleException::fieldViewHelperFieldNotAccessibleInForm($formObject, $fieldName);
208
        }
209
210
        $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...
211
    }
212
213
    /**
214
     * Returns the layout instance used by this field.
215
     *
216
     * @param View $viewConfiguration
217
     * @return Layout
218
     * @throws EntryNotFoundException
219
     * @throws InvalidArgumentTypeException
220
     * @throws InvalidArgumentValueException
221
     */
222
    protected function getLayout(View $viewConfiguration)
223
    {
224
        $layout = $this->arguments['layout'];
225
226
        if (false === is_string($layout)) {
227
            throw InvalidArgumentTypeException::invalidTypeNameArgumentFieldViewHelper($layout);
228
        }
229
230
        list($layoutName, $templateName) = GeneralUtility::trimExplode('.', $layout);
231
232
        if (empty($templateName)) {
233
            $templateName = 'default';
234
        }
235
236
        if (empty($layoutName)) {
237
            throw InvalidArgumentValueException::fieldViewHelperEmptyLayout();
238
        }
239
240
        if (false === $viewConfiguration->hasLayout($layoutName)) {
241
            throw EntryNotFoundException::fieldViewHelperLayoutNotFound($layout);
242
        }
243
244
        if (false === $viewConfiguration->getLayout($layoutName)->hasItem($templateName)) {
245
            throw EntryNotFoundException::fieldViewHelperLayoutItemNotFound($layout, $templateName);
246
        }
247
248
        return $viewConfiguration->getLayout($layoutName)->getItem($templateName);
249
    }
250
251
    /**
252
     * Stores some arguments which may already have been initialized, and could
253
     * be overridden in the local scope.
254
     */
255
    protected function storeOriginalArguments()
256
    {
257
        $this->originalArguments = [];
258
        $variableProvider = $this->getVariableProvider();
259
260
        foreach (self::$reservedVariablesNames as $key) {
261
            if ($variableProvider->exists($key)) {
262
                $this->originalArguments[$key] = $variableProvider->get($key);
263
            }
264
        }
265
    }
266
267
    /**
268
     * Will restore original arguments in the template variable container.
269
     *
270
     * @param array $templateArguments
271
     */
272
    protected function restoreOriginalArguments(array $templateArguments)
273
    {
274
        $variableProvider = $this->getVariableProvider();
275
276
        foreach ($variableProvider->getAllIdentifiers() as $identifier) {
277
            if (array_key_exists($identifier, $templateArguments)) {
278
                $variableProvider->remove($identifier);
279
            }
280
        }
281
282
        foreach ($this->originalArguments as $key => $value) {
283
            if ($variableProvider->exists($key)) {
284
                $variableProvider->remove($key);
285
            }
286
287
            $variableProvider->add($key, $value);
288
        }
289
    }
290
291
    /**
292
     * @param FormViewHelperService $service
293
     */
294
    public function injectFormService(FormViewHelperService $service)
295
    {
296
        $this->formService = $service;
297
    }
298
299
    /**
300
     * @param FieldViewHelperService $service
301
     */
302
    public function injectFieldService(FieldViewHelperService $service)
303
    {
304
        $this->fieldService = $service;
305
    }
306
307
    /**
308
     * @param SlotViewHelperService $slotService
309
     */
310
    public function injectSlotService(SlotViewHelperService $slotService)
311
    {
312
        $this->slotService = $slotService;
313
    }
314
}
315