Completed
Push — unit-test-view-helpers ( 96d65b...71f2bd )
by Romain
03:27
created

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