Completed
Push — unit-test-view-helpers ( 33f1c6...2824c7 )
by Romain
02:16
created

FieldViewHelper::injectFormService()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
c 0
b 0
f 0
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\Service\StringService;
20
use Romm\Formz\ViewHelpers\Service\FieldService;
21
use Romm\Formz\ViewHelpers\Service\FormService;
22
use TYPO3\CMS\Core\Utility\GeneralUtility;
23
use TYPO3\CMS\Extbase\Utility\ArrayUtility;
24
use TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface;
25
use TYPO3\CMS\Fluid\View\StandaloneView;
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
     * Unique instance of view, stored to save some performance.
54
     *
55
     * @var StandaloneView
56
     */
57
    protected static $view;
58
59
    /**
60
     * @inheritdoc
61
     */
62
    public function initializeArguments()
63
    {
64
        $this->registerArgument('name', 'string', 'Name of the field which should be rendered.', true);
65
        $this->registerArgument('layout', 'string', 'Path of the TypoScript layout which will be used.', true);
66
        $this->registerArgument('arguments', 'array', 'Arbitrary arguments which will be sent to the field template.', false);
67
    }
68
69
    /**
70
     * @inheritdoc
71
     */
72
    public function render()
73
    {
74
        $this->formService->checkIsInsideFormViewHelper();
75
76
        $formObject = $this->formService->getFormObject();
77
78
        $formConfiguration = $formObject->getConfiguration();
79
80
        $viewConfiguration = $formConfiguration->getFormzConfiguration()->getView();
81
        $fieldName = $this->arguments['name'];
82
83
        if (false === is_string($this->arguments['name'])) {
84
            throw new \Exception('The argument "name" of the view helper "' . __CLASS__ . '" must be a string.', 1465243479);
85
        } elseif (false === $formConfiguration->hasField($fieldName)) {
86
            throw new \Exception('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.', 1465243619);
87
        }
88
89
        $this->fieldService->setCurrentField($formConfiguration->getField($fieldName));
90
91
        $closure = $this->buildRenderChildrenClosure();
92
        $closure();
93
94
        $layout = self::getLayout($this->arguments, $viewConfiguration);
95
96
        /** @var StandaloneView $view */
97
        $view = self::$view = (null === self::$view)
98
            ? Core::instantiate(StandaloneView::class)
99
            : self::$view;
100
101
        $templateArguments = is_array($this->arguments['arguments'])
102
            ? $this->arguments['arguments']
103
            : [];
104
        $templateArguments = ArrayUtility::arrayMergeRecursiveOverrule($templateArguments, $this->fieldService->getFieldOptions());
105
106
        /*
107
         * Keeping a trace of potential original arguments which will be
108
         * replaced in the section, to restore them at the end of the view
109
         * helper.
110
         */
111
        $originalArguments = self::getOriginalArguments($this->renderingContext);
112
113
        $templateArguments['layout'] = $layout->getLayout();
114
        $templateArguments['formName'] = $formObject->getName();
115
        $templateArguments['fieldName'] = $fieldName;
116
        $templateArguments['fieldId'] = (true === isset($templateArguments['fieldId']))
117
            ? $templateArguments['fieldId']
118
            : StringService::get()->sanitizeString('formz-' . $formObject->getName() . '-' . $fieldName);
119
120
        $currentView = $this->renderingContext
121
            ->getViewHelperVariableContainer()
122
            ->getView();
123
124
        $view->setTemplatePathAndFilename($layout->getTemplateFile());
125
        $view->setLayoutRootPaths($viewConfiguration->getLayoutRootPaths());
126
        $view->setPartialRootPaths($viewConfiguration->getPartialRootPaths());
127
        $view->setRenderingContext($this->renderingContext);
128
        $view->assignMultiple($templateArguments);
129
130
        $result = $view->render();
131
132
        $this->renderingContext
133
            ->getViewHelperVariableContainer()
134
            ->setView($currentView);
135
136
        $this->fieldService
137
            ->removeCurrentField()
138
            ->resetFieldOptions();
139
        SectionViewHelper::resetSectionClosures();
140
141
        self::restoreOriginalArguments($this->renderingContext, $templateArguments, $originalArguments);
142
143
        return $result;
144
    }
145
146
    /**
147
     * Returns the layout instance used by this field.
148
     *
149
     * @param array $arguments
150
     * @param View  $viewConfiguration
151
     * @return Layout
152
     * @throws \Exception
153
     */
154
    protected static function getLayout(array $arguments, View $viewConfiguration)
155
    {
156
        $layoutFound = true;
157
        list($layoutName, $templateName) = GeneralUtility::trimExplode('.', $arguments['layout']);
158
        if (false === is_string($templateName)) {
159
            $templateName = 'default';
160
        }
161
162
        if (false === is_string($layoutName)) {
163
            $layoutFound = false;
164
        } elseif (false === $viewConfiguration->hasLayout($layoutName)) {
165
            $layoutFound = false;
166
        } elseif (false === $viewConfiguration->getLayout($layoutName)->hasItem($templateName)) {
167
            $layoutFound = false;
168
        }
169
170
        if (false === $layoutFound) {
171
            throw new \Exception('The layout "' . $arguments['layout'] . '" could not be found. Please check your TypoScript configuration.', 1465243586);
172
        }
173
174
        return $viewConfiguration->getLayout($layoutName)->getItem($templateName);
175
    }
176
177
    /**
178
     * Returns the value of the current variable in the variable container at
179
     * the index `$key`, or null if it is not found.
180
     *
181
     * @param RenderingContextInterface $renderingContext
182
     * @param string                    $key
183
     * @return mixed|null
184
     */
185
    protected static function getTemplateVariableContainerValue(RenderingContextInterface $renderingContext, $key)
186
    {
187
        $templateVariableContainer = $renderingContext->getTemplateVariableContainer();
188
189
        return ($templateVariableContainer->exists($key))
190
            ? $templateVariableContainer->get($key)
191
            : null;
192
    }
193
194
    /**
195
     * Returns some arguments which may already have been initialized.
196
     *
197
     * @param RenderingContextInterface $renderingContext
198
     * @return array
199
     */
200
    protected static function getOriginalArguments(RenderingContextInterface $renderingContext)
201
    {
202
        return [
203
            'layout'    => self::getTemplateVariableContainerValue($renderingContext, 'layout'),
204
            'formName'  => self::getTemplateVariableContainerValue($renderingContext, 'formName'),
205
            'fieldName' => self::getTemplateVariableContainerValue($renderingContext, 'fieldName'),
206
            'fieldId'   => self::getTemplateVariableContainerValue($renderingContext, 'fieldId')
207
        ];
208
    }
209
210
    /**
211
     * Will restore original arguments in the template variable container.
212
     *
213
     * @param RenderingContextInterface $renderingContext
214
     * @param array                     $templateArguments
215
     * @param array                     $originalArguments
216
     */
217
    protected static function restoreOriginalArguments(RenderingContextInterface $renderingContext, array $templateArguments, array $originalArguments)
218
    {
219
        $templateVariableContainer = $renderingContext->getTemplateVariableContainer();
220
221
        $identifiers = $templateVariableContainer->getAllIdentifiers();
222
        foreach ($identifiers as $identifier) {
223
            if (array_key_exists($identifier, $templateArguments)) {
224
                $templateVariableContainer->remove($identifier);
225
            }
226
        }
227
228
        foreach ($originalArguments as $key => $value) {
229
            if (null !== $value) {
230
                if ($templateVariableContainer->exists($key)) {
231
                    $templateVariableContainer->remove($key);
232
                }
233
234
                $templateVariableContainer->add($key, $value);
235
            }
236
        }
237
    }
238
239
    /**
240
     * @param FormService $service
241
     */
242
    public function injectFormService(FormService $service)
243
    {
244
        $this->formService = $service;
245
    }
246
247
    /**
248
     * @param FieldService $service
249
     */
250
    public function injectFieldService(FieldService $service)
251
    {
252
        $this->fieldService = $service;
253
    }
254
}
255