[FEATURE] Complete the Property Mapper whitelist change for fluid
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / ViewHelpers / FormViewHelper.php
1 <?php
2 namespace TYPO3\CMS\Fluid\ViewHelpers;
3
4 /* *
5 * This script is backported from the TYPO3 Flow package "TYPO3.Fluid". *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU General Public License as published by the Free *
9 * Software Foundation, either version 3 of the License, or (at your *
10 * *
11 * *
12 * This script is distributed in the hope that it will be useful, but *
13 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
14 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
15 * Public License for more details. *
16 * *
17 * You should have received a copy of the GNU General Public License *
18 * along with the script. *
19 * If not, see http://www.gnu.org/licenses/gpl.html *
20 * *
21 * The TYPO3 project - inspiring people to share! *
22 * */
23 /**
24 * Form view helper. Generates a <form> Tag.
25 *
26 * = Basic usage =
27 *
28 * Use <f:form> to output an HTML <form> tag which is targeted at the specified action, in the current controller and package.
29 * It will submit the form data via a POST request. If you want to change this, use method="get" as an argument.
30 * <code title="Example">
31 * <f:form action="...">...</f:form>
32 * </code>
33 *
34 * = A complex form with a specified encoding type =
35 *
36 * <code title="Form with enctype set">
37 * <f:form action=".." controller="..." package="..." enctype="multipart/form-data">...</f:form>
38 * </code>
39 *
40 * = A Form which should render a domain object =
41 *
42 * <code title="Binding a domain object to a form">
43 * <f:form action="..." name="customer" object="{customer}">
44 * <f:form.hidden property="id" />
45 * <f:form.textbox property="name" />
46 * </f:form>
47 * </code>
48 * This automatically inserts the value of {customer.name} inside the textbox and adjusts the name of the textbox accordingly.
49 */
50 class FormViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormViewHelper {
51
52 /**
53 * @var string
54 */
55 protected $tagName = 'form';
56
57 /**
58 * @var \TYPO3\CMS\Extbase\Security\Channel\RequestHashService
59 */
60 protected $requestHashService;
61
62 /**
63 * @var \TYPO3\CMS\Extbase\Security\Cryptography\HashService
64 */
65 protected $hashService;
66
67 /**
68 * @var \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService
69 */
70 protected $mvcPropertyMappingConfigurationService;
71
72 /**
73 * @var \TYPO3\CMS\Extbase\Service\ExtensionService
74 */
75 protected $extensionService;
76
77 /**
78 * We need the arguments of the formActionUri on requesthash calculation
79 * therefore we will store them in here right after calling uriBuilder
80 *
81 * @var array
82 */
83 protected $formActionUriArguments;
84
85 /**
86 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
87 */
88 protected $configurationManager;
89
90 /**
91 * Inject a request hash service
92 *
93 * @param \TYPO3\CMS\Extbase\Security\Channel\RequestHashService $requestHashService The request hash service
94 * @return void
95 */
96 public function injectRequestHashService(\TYPO3\CMS\Extbase\Security\Channel\RequestHashService $requestHashService) {
97 $this->requestHashService = $requestHashService;
98 }
99
100 /**
101 * @param \TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService
102 */
103 public function injectHashService(\TYPO3\CMS\Extbase\Security\Cryptography\HashService $hashService) {
104 $this->hashService = $hashService;
105 }
106
107 /**
108 * @param \TYPO3\CMS\Extbase\Service\ExtensionService $extensionService
109 * @return void
110 */
111 public function injectExtensionService(\TYPO3\CMS\Extbase\Service\ExtensionService $extensionService) {
112 $this->extensionService = $extensionService;
113 }
114
115 /**
116 * @param \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager
117 * @return void
118 */
119 public function injectConfigurationManager(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface $configurationManager) {
120 $this->configurationManager = $configurationManager;
121 }
122
123 /**
124 * @param \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMapperConfigurationService
125 * @return void
126 */
127 public function injectMvcPropertyMapperConfigurationService(\TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService $mvcPropertyMapperConfigurationService) {
128 $this->mvcPropertyMappingConfigurationService = $mvcPropertyMapperConfigurationService;
129 }
130
131 /**
132 * Initialize arguments.
133 *
134 * @return void
135 */
136 public function initializeArguments() {
137 $this->registerTagAttribute('enctype', 'string', 'MIME type with which the form is submitted');
138 $this->registerTagAttribute('method', 'string', 'Transfer type (GET or POST)');
139 $this->registerTagAttribute('name', 'string', 'Name of form');
140 $this->registerTagAttribute('onreset', 'string', 'JavaScript: On reset of the form');
141 $this->registerTagAttribute('onsubmit', 'string', 'JavaScript: On submit of the form');
142 $this->registerUniversalTagAttributes();
143 }
144
145 /**
146 * Render the form.
147 *
148 * @param string $action Target action
149 * @param array $arguments Arguments
150 * @param string $controller Target controller
151 * @param string $extensionName Target Extension Name (without "tx_" prefix and no underscores). If NULL the current extension name is used
152 * @param string $pluginName Target plugin. If empty, the current plugin name is used
153 * @param integer $pageUid Target page uid
154 * @param mixed $object Object to use for the form. Use in conjunction with the "property" attribute on the sub tags
155 * @param integer $pageType Target page type
156 * @param boolean $noCache set this to disable caching for the target page. You should not need this.
157 * @param boolean $noCacheHash set this to supress the cHash query parameter created by TypoLink. You should not need this.
158 * @param string $section The anchor to be added to the action URI (only active if $actionUri is not set)
159 * @param string $format The requested format (e.g. ".html") of the target page (only active if $actionUri is not set)
160 * @param array $additionalParams additional action URI query parameters that won't be prefixed like $arguments (overrule $arguments) (only active if $actionUri is not set)
161 * @param boolean $absolute If set, an absolute action URI is rendered (only active if $actionUri is not set)
162 * @param boolean $addQueryString If set, the current query parameters will be kept in the action URI (only active if $actionUri is not set)
163 * @param array $argumentsToBeExcludedFromQueryString arguments to be removed from the action URI. Only active if $addQueryString = TRUE and $actionUri is not set
164 * @param string $fieldNamePrefix Prefix that will be added to all field names within this form. If not set the prefix will be tx_yourExtension_plugin
165 * @param string $actionUri can be used to overwrite the "action" attribute of the form tag
166 * @param string $objectName name of the object that is bound to this form. If this argument is not specified, the name attribute of this form is used to determine the FormObjectName
167 * @return string rendered form
168 */
169 public function render($action = NULL, array $arguments = array(), $controller = NULL, $extensionName = NULL, $pluginName = NULL, $pageUid = NULL, $object = NULL, $pageType = 0, $noCache = FALSE, $noCacheHash = FALSE, $section = '', $format = '', array $additionalParams = array(), $absolute = FALSE, $addQueryString = FALSE, array $argumentsToBeExcludedFromQueryString = array(), $fieldNamePrefix = NULL, $actionUri = NULL, $objectName = NULL) {
170 $this->setFormActionUri();
171 if (strtolower($this->arguments['method']) === 'get') {
172 $this->tag->addAttribute('method', 'get');
173 } else {
174 $this->tag->addAttribute('method', 'post');
175 }
176 $this->addFormObjectNameToViewHelperVariableContainer();
177 $this->addFormObjectToViewHelperVariableContainer();
178 $this->addFieldNamePrefixToViewHelperVariableContainer();
179 $this->addFormFieldNamesToViewHelperVariableContainer();
180 $formContent = $this->renderChildren();
181 $content = chr(10) . '<div style="display: none">';
182 $content .= $this->renderHiddenIdentityField($this->arguments['object'], $this->getFormObjectName());
183 $content .= $this->renderAdditionalIdentityFields();
184 $content .= $this->renderHiddenReferrerFields();
185 if ($this->configurationManager->isFeatureEnabled('rewrittenPropertyMapper') === FALSE) {
186 // Render hmac after everything else has been rendered
187 $content .= $this->renderRequestHashField();
188 } else {
189 // Render the trusted list of all properties after everything else has been rendered
190 $content .= $this->renderTrustedPropertiesField();
191 }
192 $content .= chr(10) . '</div>' . chr(10);
193 $content .= $formContent;
194 $this->tag->setContent($content);
195 $this->removeFieldNamePrefixFromViewHelperVariableContainer();
196 $this->removeFormObjectFromViewHelperVariableContainer();
197 $this->removeFormObjectNameFromViewHelperVariableContainer();
198 $this->removeFormFieldNamesFromViewHelperVariableContainer();
199 $this->removeCheckboxFieldNamesFromViewHelperVariableContainer();
200 return $this->tag->render();
201 }
202
203 /**
204 * Sets the "action" attribute of the form tag
205 *
206 * @return void
207 */
208 protected function setFormActionUri() {
209 if ($this->hasArgument('actionUri')) {
210 $formActionUri = $this->arguments['actionUri'];
211 } else {
212 $uriBuilder = $this->controllerContext->getUriBuilder();
213 $formActionUri = $uriBuilder->reset()->setTargetPageUid($this->arguments['pageUid'])->setTargetPageType($this->arguments['pageType'])->setNoCache($this->arguments['noCache'])->setUseCacheHash(!$this->arguments['noCacheHash'])->setSection($this->arguments['section'])->setCreateAbsoluteUri($this->arguments['absolute'])->setArguments((array) $this->arguments['additionalParams'])->setAddQueryString($this->arguments['addQueryString'])->setArgumentsToBeExcludedFromQueryString((array) $this->arguments['argumentsToBeExcludedFromQueryString'])->setFormat($this->arguments['format'])->uriFor($this->arguments['action'], $this->arguments['arguments'], $this->arguments['controller'], $this->arguments['extensionName'], $this->arguments['pluginName']);
214 $this->formActionUriArguments = $uriBuilder->getArguments();
215 }
216 $this->tag->addAttribute('action', $formActionUri);
217 }
218
219 /**
220 * Render additional identity fields which were registered by form elements.
221 * This happens if a form field is defined like property="bla.blubb" - then we might need an identity property for the sub-object "bla".
222 *
223 * @return string HTML-string for the additional identity properties
224 */
225 protected function renderAdditionalIdentityFields() {
226 if ($this->viewHelperVariableContainer->exists('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'additionalIdentityProperties')) {
227 $additionalIdentityProperties = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'additionalIdentityProperties');
228 $output = '';
229 foreach ($additionalIdentityProperties as $identity) {
230 $output .= chr(10) . $identity;
231 }
232 return $output;
233 }
234 return '';
235 }
236
237 /**
238 * Renders hidden form fields for referrer information about
239 * the current controller and action.
240 *
241 * @return string Hidden fields with referrer information
242 * @todo filter out referrer information that is equal to the target (e.g. same packageKey)
243 */
244 protected function renderHiddenReferrerFields() {
245 $request = $this->controllerContext->getRequest();
246 $extensionName = $request->getControllerExtensionName();
247 $controllerName = $request->getControllerName();
248 $actionName = $request->getControllerActionName();
249 $result = chr(10);
250 if ($this->configurationManager->isFeatureEnabled('rewrittenPropertyMapper')) {
251 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[@extension]') . '" value="' . $extensionName . '" />' . chr(10);
252 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[@controller]') . '" value="' . $controllerName . '" />' . chr(10);
253 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[@action]') . '" value="' . $actionName . '" />' . chr(10);
254 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[arguments]') . '" value="' . htmlspecialchars($this->hashService->appendHmac(base64_encode(serialize($request->getArguments())))) . '" />' . chr(10);
255 } else {
256 // @deprecated since Fluid 1.4.0, will be removed with Fluid 6.1.
257 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[extensionName]') . '" value="' . $extensionName . '" />' . chr(10);
258 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[controllerName]') . '" value="' . $controllerName . '" />' . chr(10);
259 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[actionName]') . '" value="' . $actionName . '" />' . chr(10);
260 }
261 return $result;
262 }
263
264 /**
265 * Adds the form object name to the ViewHelperVariableContainer if "objectName" argument or "name" attribute is specified.
266 *
267 * @return void
268 */
269 protected function addFormObjectNameToViewHelperVariableContainer() {
270 $formObjectName = $this->getFormObjectName();
271 if ($formObjectName !== NULL) {
272 $this->viewHelperVariableContainer->add('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObjectName', $formObjectName);
273 }
274 }
275
276 /**
277 * Removes the form name from the ViewHelperVariableContainer.
278 *
279 * @return void
280 */
281 protected function removeFormObjectNameFromViewHelperVariableContainer() {
282 $formObjectName = $this->getFormObjectName();
283 if ($formObjectName !== NULL) {
284 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObjectName');
285 }
286 }
287
288 /**
289 * Returns the name of the object that is bound to this form.
290 * If the "objectName" argument has been specified, this is returned. Otherwise the name attribute of this form.
291 * If neither objectName nor name arguments have been set, NULL is returned.
292 *
293 * @return string specified Form name or NULL if neither $objectName nor $name arguments have been specified
294 */
295 protected function getFormObjectName() {
296 $formObjectName = NULL;
297 if ($this->hasArgument('objectName')) {
298 $formObjectName = $this->arguments['objectName'];
299 } elseif ($this->hasArgument('name')) {
300 $formObjectName = $this->arguments['name'];
301 }
302 return $formObjectName;
303 }
304
305 /**
306 * Adds the object that is bound to this form to the ViewHelperVariableContainer if the formObject attribute is specified.
307 *
308 * @return void
309 */
310 protected function addFormObjectToViewHelperVariableContainer() {
311 if ($this->hasArgument('object')) {
312 $this->viewHelperVariableContainer->add('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObject', $this->arguments['object']);
313 $this->viewHelperVariableContainer->add('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'additionalIdentityProperties', array());
314 }
315 }
316
317 /**
318 * Removes the form object from the ViewHelperVariableContainer.
319 *
320 * @return void
321 */
322 protected function removeFormObjectFromViewHelperVariableContainer() {
323 if ($this->hasArgument('object')) {
324 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObject');
325 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'additionalIdentityProperties');
326 }
327 }
328
329 /**
330 * Adds the field name prefix to the ViewHelperVariableContainer
331 *
332 * @return void
333 */
334 protected function addFieldNamePrefixToViewHelperVariableContainer() {
335 $fieldNamePrefix = $this->getFieldNamePrefix();
336 $this->viewHelperVariableContainer->add('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'fieldNamePrefix', $fieldNamePrefix);
337 }
338
339 /**
340 * Get the field name prefix
341 *
342 * @return string
343 */
344 protected function getFieldNamePrefix() {
345 if ($this->hasArgument('fieldNamePrefix')) {
346 return $this->arguments['fieldNamePrefix'];
347 } else {
348 return $this->getDefaultFieldNamePrefix();
349 }
350 }
351
352 /**
353 * Removes field name prefix from the ViewHelperVariableContainer
354 *
355 * @return void
356 */
357 protected function removeFieldNamePrefixFromViewHelperVariableContainer() {
358 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'fieldNamePrefix');
359 }
360
361 /**
362 * Adds a container for form field names to the ViewHelperVariableContainer
363 *
364 * @return void
365 */
366 protected function addFormFieldNamesToViewHelperVariableContainer() {
367 $this->viewHelperVariableContainer->add('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formFieldNames', array());
368 }
369
370 /**
371 * Removes the container for form field names from the ViewHelperVariableContainer
372 *
373 * @return void
374 */
375 protected function removeFormFieldNamesFromViewHelperVariableContainer() {
376 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formFieldNames');
377 }
378
379 /**
380 * Render the request hash field
381 *
382 * @return string the hmac field
383 */
384 protected function renderRequestHashField() {
385 $formFieldNames = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formFieldNames');
386 $this->postProcessUriArgumentsForRequesthash($this->formActionUriArguments, $formFieldNames);
387 $requestHash = $this->requestHashService->generateRequestHash($formFieldNames, $this->getFieldNamePrefix());
388 // in v4, we need to prefix __hmac as well to make it show up in the request object.
389 return '<input type="hidden" name="' . $this->prefixFieldName('__hmac') . '" value="' . htmlspecialchars($requestHash) . '" />';
390 }
391
392 /**
393 * Add the URI arguments after postprocessing to the request hash as well.
394 */
395 protected function postProcessUriArgumentsForRequestHash($arguments, &$results, $currentPrefix = '', $level = 0) {
396 if (!count($arguments)) {
397 return;
398 }
399 foreach ($arguments as $argumentName => $argumentValue) {
400 if (is_array($argumentValue)) {
401 $prefix = $level == 0 ? $argumentName : $currentPrefix . '[' . $argumentName . ']';
402 $this->postProcessUriArgumentsForRequestHash($argumentValue, $results, $prefix, $level + 1);
403 } else {
404 $results[] = $level == 0 ? $argumentName : $currentPrefix . '[' . $argumentName . ']';
405 }
406 }
407 }
408
409 /**
410 * Retrieves the default field name prefix for this form
411 *
412 * @return string default field name prefix
413 */
414 protected function getDefaultFieldNamePrefix() {
415 $request = $this->controllerContext->getRequest();
416 if ($this->hasArgument('extensionName')) {
417 $extensionName = $this->arguments['extensionName'];
418 } else {
419 $extensionName = $request->getControllerExtensionName();
420 }
421 if ($this->hasArgument('pluginName')) {
422 $pluginName = $this->arguments['pluginName'];
423 } else {
424 $pluginName = $request->getPluginName();
425 }
426 if ($extensionName !== NULL && $pluginName != NULL) {
427 return $this->extensionService->getPluginNamespace($extensionName, $pluginName);
428 } else {
429 return '';
430 }
431 }
432
433 /**
434 * Remove Checkbox field names from ViewHelper variable container, to start from scratch when a new form starts.
435 */
436 protected function removeCheckboxFieldNamesFromViewHelperVariableContainer() {
437 if ($this->viewHelperVariableContainer->exists('TYPO3\\CMS\\Fluid\\ViewHelpers\\Form\\CheckboxViewHelper', 'checkboxFieldNames')) {
438 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\Form\\CheckboxViewHelper', 'checkboxFieldNames');
439 }
440 }
441
442 /**
443 * Render the request hash field
444 *
445 * @return string The hmac field
446 */
447 protected function renderTrustedPropertiesField() {
448 $formFieldNames = $this->viewHelperVariableContainer->get('TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper', 'formFieldNames');
449 $requestHash = $this->mvcPropertyMappingConfigurationService->generateTrustedPropertiesToken($formFieldNames, $this->getFieldNamePrefix());
450 return '<input type="hidden" name="' . $this->prefixFieldName('__trustedProperties') . '" value="' . htmlspecialchars($requestHash) . '" />';
451 }
452 }
453
454 ?>