Issues (48)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

Classes/ViewHelpers/FieldViewHelper.php (6 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Field\FieldViewHelperService;
25
use Romm\Formz\Service\ViewHelper\Form\FormViewHelperService;
26
use Romm\Formz\Service\ViewHelper\Slot\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
     * @inheritdoc
73
     */
74
    public function initializeArguments()
75
    {
76
        parent::initializeArguments();
77
78
        $this->registerArgument('name', 'string', 'Name of the field which should be rendered.', true);
79
        $this->registerArgument('layout', 'string', 'Path of the TypoScript layout which will be used.', true);
80
        $this->registerArgument('arguments', 'array', 'Arbitrary arguments which will be sent to the field template.', false, []);
81
    }
82
83
    /**
84
     * @inheritdoc
85
     */
86
    public function render()
87
    {
88
        /*
89
         * First, we check if this view helper is called from within the
90
         * `FormViewHelper`, because it would not make sense anywhere else.
91
         */
92
        if (false === $this->formService->formContextExists()) {
93
            throw ContextNotFoundException::fieldViewHelperFormContextNotFound();
94
        }
95
96
        /*
97
         * Then, we inject the wanted field in the `FieldService` so we can know
98
         * later which field we're working with.
99
         */
100
        $this->injectFieldInService($this->arguments['name']);
101
102
        /*
103
         * Activating the slot service, which will be used all along the
104
         * rendering of this very field.
105
         */
106
        $this->slotService->activate($this->renderingContext);
0 ignored issues
show
$this->renderingContext of type object<TYPO3Fluid\Fluid\...deringContextInterface> is not a sub-type of object<TYPO3\CMS\Fluid\C...deringContextInterface>. It seems like you assume a child interface of the interface TYPO3Fluid\Fluid\Core\Re...nderingContextInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
107
108
        /*
109
         * Calling this here will process every view helper beneath this one,
110
         * allowing options and slots to be used correctly in the field layout.
111
         */
112
        $this->renderChildren();
113
114
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '8.0.0', '<')) {
115
            $restoreCallback = $this->storeViewDataLegacy();
0 ignored issues
show
Deprecated Code introduced by
The method Romm\Formz\ViewHelpers\F...::storeViewDataLegacy() has been deprecated with message: Will be deleted when TYPO3 7.6 is not supported anymore.

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...
116
        }
117
118
        $templateArguments = $this->getTemplateArguments();
119
120
        $result = $this->renderLayoutView($templateArguments);
121
122
        /*
123
         * Resetting all services data.
124
         */
125
        $this->fieldService->removeCurrentField();
126
        $this->slotService->resetState();
127
128
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '8.0.0', '<')) {
129
            /** @noinspection PhpUndefinedVariableInspection */
130
            $restoreCallback($templateArguments);
0 ignored issues
show
The variable $restoreCallback does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
131
        }
132
133
        return $result;
134
    }
135
136
    /**
137
     * Will return the associated Fluid view for this field (configured with the
138
     * `layout` argument).
139
     *
140
     * @param array $templateArguments
141
     * @return string
142
     */
143
    protected function renderLayoutView(array $templateArguments)
144
    {
145
        $fieldName = $this->arguments['name'];
146
        $formObject = $this->formService->getFormObject();
147
        $formConfiguration = $formObject->getConfiguration();
148
        $viewConfiguration = $formConfiguration->getRootConfiguration()->getView();
149
        $layout = $this->getLayout($viewConfiguration);
150
151
        $templateArguments['layout'] = $layout->getLayout();
152
        $templateArguments['formName'] = $formObject->getName();
153
        $templateArguments['fieldName'] = $fieldName;
154
        $templateArguments['fieldId'] = ($templateArguments['fieldId']) ?: StringService::get()->sanitizeString('formz-' . $formObject->getName() . '-' . $fieldName);
155
156
        $currentView = $this->viewHelperVariableContainer->getView();
157
        $currentVariables = [];
158
159
        $view = $this->fieldService->getView($layout);
160
161
        /*
162
         * Warning: we need to store the layouts/partials paths before
163
         * manipulating the rendering context!
164
         */
165
        $layoutPaths = $this->getPaths('layout');
0 ignored issues
show
Deprecated Code introduced by
The method Romm\Formz\ViewHelpers\FieldViewHelper::getPaths() has been deprecated with message: Must be removed when TYPO3 7.6 is not supported anymore!

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...
166
        $partialPaths = $this->getPaths('partial');
0 ignored issues
show
Deprecated Code introduced by
The method Romm\Formz\ViewHelpers\FieldViewHelper::getPaths() has been deprecated with message: Must be removed when TYPO3 7.6 is not supported anymore!

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...
167
168
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '8.0.0', '<')) {
169
            $view->setRenderingContext($this->renderingContext);
170
        } else {
171
            $currentVariables = $this->renderingContext->getVariableProvider()->getAll();
172
173
            /*
174
             * Updating the view dependencies: the variable container as well as
175
             * the controller context must be injected in the view.
176
             */
177
            $this->viewHelperVariableContainer->setView($view);
178
179
            $view->getRenderingContext()->setViewHelperVariableContainer($this->viewHelperVariableContainer);
180
181
            $view->setControllerContext($this->controllerContext);
182
183
            /*
184
             * Adding current variables to the field view variables.
185
             */
186
            $tmpVariables = $currentVariables;
187
            ArrayUtility::mergeRecursiveWithOverrule($tmpVariables, $templateArguments);
0 ignored issues
show
It seems like $tmpVariables defined by $currentVariables on line 186 can also be of type object<ArrayAccess>; however, TYPO3\CMS\Core\Utility\A...RecursiveWithOverrule() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
188
            $templateArguments = $tmpVariables;
189
        }
190
191
        $view->setLayoutRootPaths($layoutPaths);
192
        $view->setPartialRootPaths($partialPaths);
193
        $view->assignMultiple($templateArguments);
194
195
        $result = $view->render();
196
197
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '8.0.0', '>=')) {
198
            /*
199
             * Because the view can be used several times in nested fields, we
200
             * need to restore the variables after the view was rendered.
201
             */
202
            $viewVariableProvider = $view->getRenderingContext()->getVariableProvider();
203
204
            foreach ($viewVariableProvider->getAllIdentifiers() as $identifier) {
205
                $viewVariableProvider->remove($identifier);
206
            }
207
208
            foreach ($currentVariables as $key => $value) {
209
                $viewVariableProvider->add($key, $value);
210
            }
211
212
            /*
213
             * Resetting the view of the variable container with the original
214
             * view.
215
             */
216
            $this->viewHelperVariableContainer->setView($currentView);
217
        }
218
219
        return $result;
220
    }
221
222
    /**
223
     * Temporary solution for TYPO3 6.2 to 7.6 that will store the current view
224
     * variables, to be able to restore them later.
225
     *
226
     * A callback function is returned; it will be called once the field layout
227
     * view was processed, and will restore all the view data.
228
     *
229
     * @return \Closure
230
     *
231
     * @deprecated Will be deleted when TYPO3 7.6 is not supported anymore.
232
     */
233
    protected function storeViewDataLegacy()
234
    {
235
        $originalArguments = [];
236
237
        $variableProvider = $this->getVariableProvider();
238
239
        foreach (self::$reservedVariablesNames as $key) {
240
            if ($variableProvider->exists($key)) {
241
                $originalArguments[$key] = $variableProvider->get($key);
242
            }
243
        }
244
245
        $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
246
        $currentView = $viewHelperVariableContainer->getView();
247
248
        return function (array $templateArguments) use ($originalArguments, $variableProvider, $viewHelperVariableContainer, $currentView) {
249
            $viewHelperVariableContainer->setView($currentView);
250
251
            /*
252
             * Cleaning up the variables in the provider: the original
253
             * values are restored to make the provider like it was before
254
             * the field rendering started.
255
             */
256
            foreach ($variableProvider->getAllIdentifiers() as $identifier) {
257
                if (array_key_exists($identifier, $templateArguments)) {
258
                    $variableProvider->remove($identifier);
259
                }
260
            }
261
262
            foreach ($originalArguments as $key => $value) {
263
                if ($variableProvider->exists($key)) {
264
                    $variableProvider->remove($key);
265
                }
266
267
                $variableProvider->add($key, $value);
268
            }
269
        };
270
    }
271
272
    /**
273
     * Will check that the given field exists in the current form definition and
274
     * inject it in the `FieldService` as `currentField`.
275
     *
276
     * Throws an error if the field is not found or incorrect.
277
     *
278
     * @param string $fieldName
279
     * @throws InvalidArgumentTypeException
280
     * @throws PropertyNotAccessibleException
281
     */
282
    protected function injectFieldInService($fieldName)
283
    {
284
        $formObject = $this->formService->getFormObject();
285
        $formConfiguration = $formObject->getConfiguration();
286
287
        if (false === is_string($fieldName)) {
288
            throw InvalidArgumentTypeException::fieldViewHelperInvalidTypeNameArgument();
289
        } elseif (false === $formConfiguration->hasField($fieldName)) {
290
            throw PropertyNotAccessibleException::fieldViewHelperFieldNotAccessibleInForm($formObject, $fieldName);
291
        }
292
293
        $this->fieldService->setCurrentField($formConfiguration->getField($fieldName));
294
    }
295
296
    /**
297
     * Returns the layout instance used by this field.
298
     *
299
     * @param View $viewConfiguration
300
     * @return Layout
301
     * @throws EntryNotFoundException
302
     * @throws InvalidArgumentTypeException
303
     * @throws InvalidArgumentValueException
304
     */
305
    protected function getLayout(View $viewConfiguration)
306
    {
307
        $layout = $this->arguments['layout'];
308
309
        if (false === is_string($layout)) {
310
            throw InvalidArgumentTypeException::invalidTypeNameArgumentFieldViewHelper($layout);
311
        }
312
313
        list($layoutName, $templateName) = GeneralUtility::trimExplode('.', $layout);
314
315
        if (empty($templateName)) {
316
            $templateName = 'default';
317
        }
318
319
        if (empty($layoutName)) {
320
            throw InvalidArgumentValueException::fieldViewHelperEmptyLayout();
321
        }
322
323
        if (false === $viewConfiguration->hasLayout($layoutName)) {
324
            throw EntryNotFoundException::fieldViewHelperLayoutNotFound($layout);
325
        }
326
327
        if (false === $viewConfiguration->getLayout($layoutName)->hasItem($templateName)) {
328
            throw EntryNotFoundException::fieldViewHelperLayoutItemNotFound($layout, $templateName);
329
        }
330
331
        return $viewConfiguration->getLayout($layoutName)->getItem($templateName);
332
    }
333
334
    /**
335
     * Merging the arguments with the ones registered with the
336
     * `OptionViewHelper`.
337
     *
338
     * @return array
339
     */
340
    protected function getTemplateArguments()
341
    {
342
        $templateArguments = $this->arguments['arguments'] ?: [];
343
        ArrayUtility::mergeRecursiveWithOverrule($templateArguments, $this->fieldService->getFieldOptions());
344
345
        return $templateArguments;
346
    }
347
348
    /**
349
     * This function will determinate the layout/partial root paths that should
350
     * be given to the standalone view. This must be a merge between the paths
351
     * given in the TypoScript configuration and the paths of the current view.
352
     *
353
     * This way, the user can use the layouts/partials from both the form
354
     * rendering extension, as well as the ones used by the field layout.
355
     *
356
     * Please note that TYPO3 v8+ has this behaviour by default, meaning only
357
     * the TypoScript configuration paths are needed, however in TYPO3 v7.6- we
358
     * need to access the root paths, which is *not* granted by Fluid... We are
359
     * then forced to use reflection, please don't do this at home!
360
     *
361
     * @param string $type `partial` or `layout`
362
     * @return array
363
     *
364
     * @deprecated Must be removed when TYPO3 7.6 is not supported anymore!
365
     */
366
    protected function getPaths($type)
367
    {
368
        $viewConfiguration = $this->formService->getFormObject()->getConfiguration()->getRootConfiguration()->getView();
369
370
        $paths = $type === 'partial'
371
            ? $viewConfiguration->getAbsolutePartialRootPaths()
372
            : $viewConfiguration->getAbsoluteLayoutRootPaths();
373
374
        if (version_compare(VersionNumberUtility::getCurrentTypo3Version(), '8.0.0', '>=')) {
375
            $templatePaths = $this->renderingContext->getTemplatePaths();
376
377
            $currentPaths = $type === 'partial'
378
                ? $templatePaths->getPartialRootPaths()
379
                : $templatePaths->getLayoutRootPaths();
380
        } else {
381
            $currentView = $this->renderingContext->getViewHelperVariableContainer()->getView();
382
            $propertyName = $type === 'partial'
383
                ? 'getPartialRootPaths'
384
                : 'getLayoutRootPaths';
385
386
            $reflectionClass = new \ReflectionClass($currentView);
387
            $method = $reflectionClass->getMethod($propertyName);
388
            $method->setAccessible(true);
389
390
            $currentPaths = $method->invoke($currentView);
391
        }
392
393
        return array_unique(array_merge($paths, $currentPaths));
394
    }
395
396
    /**
397
     * @param FormViewHelperService $service
398
     */
399
    public function injectFormService(FormViewHelperService $service)
400
    {
401
        $this->formService = $service;
402
    }
403
404
    /**
405
     * @param FieldViewHelperService $service
406
     */
407
    public function injectFieldService(FieldViewHelperService $service)
408
    {
409
        $this->fieldService = $service;
410
    }
411
412
    /**
413
     * @param SlotViewHelperService $slotService
414
     */
415
    public function injectSlotService(SlotViewHelperService $slotService)
416
    {
417
        $this->slotService = $slotService;
418
    }
419
}
420