Revert "[CLEANUP] Rework/simplify copyright header and remove @package"
[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 * @inject
60 */
61 protected $requestHashService;
62
63 /**
64 * @var \TYPO3\CMS\Extbase\Security\Cryptography\HashService
65 * @inject
66 */
67 protected $hashService;
68
69 /**
70 * @var \TYPO3\CMS\Extbase\Mvc\Controller\MvcPropertyMappingConfigurationService
71 * @inject
72 */
73 protected $mvcPropertyMappingConfigurationService;
74
75 /**
76 * @var \TYPO3\CMS\Extbase\Service\ExtensionService
77 * @inject
78 */
79 protected $extensionService;
80
81 /**
82 * We need the arguments of the formActionUri on requesthash calculation
83 * therefore we will store them in here right after calling uriBuilder
84 *
85 * @var array
86 */
87 protected $formActionUriArguments;
88
89 /**
90 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
91 * @inject
92 */
93 protected $configurationManager;
94
95 /**
96 * Initialize arguments.
97 *
98 * @return void
99 */
100 public function initializeArguments() {
101 $this->registerTagAttribute('enctype', 'string', 'MIME type with which the form is submitted');
102 $this->registerTagAttribute('method', 'string', 'Transfer type (GET or POST)');
103 $this->registerTagAttribute('name', 'string', 'Name of form');
104 $this->registerTagAttribute('onreset', 'string', 'JavaScript: On reset of the form');
105 $this->registerTagAttribute('onsubmit', 'string', 'JavaScript: On submit of the form');
106 $this->registerUniversalTagAttributes();
107 }
108
109 /**
110 * Render the form.
111 *
112 * @param string $action Target action
113 * @param array $arguments Arguments
114 * @param string $controller Target controller
115 * @param string $extensionName Target Extension Name (without "tx_" prefix and no underscores). If NULL the current extension name is used
116 * @param string $pluginName Target plugin. If empty, the current plugin name is used
117 * @param integer $pageUid Target page uid
118 * @param mixed $object Object to use for the form. Use in conjunction with the "property" attribute on the sub tags
119 * @param integer $pageType Target page type
120 * @param boolean $noCache set this to disable caching for the target page. You should not need this.
121 * @param boolean $noCacheHash set this to supress the cHash query parameter created by TypoLink. You should not need this.
122 * @param string $section The anchor to be added to the action URI (only active if $actionUri is not set)
123 * @param string $format The requested format (e.g. ".html") of the target page (only active if $actionUri is not set)
124 * @param array $additionalParams additional action URI query parameters that won't be prefixed like $arguments (overrule $arguments) (only active if $actionUri is not set)
125 * @param boolean $absolute If set, an absolute action URI is rendered (only active if $actionUri is not set)
126 * @param boolean $addQueryString If set, the current query parameters will be kept in the action URI (only active if $actionUri is not set)
127 * @param array $argumentsToBeExcludedFromQueryString arguments to be removed from the action URI. Only active if $addQueryString = TRUE and $actionUri is not set
128 * @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
129 * @param string $actionUri can be used to overwrite the "action" attribute of the form tag
130 * @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
131 * @param string $hiddenFieldClassName
132 * @return string rendered form
133 */
134 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, $hiddenFieldClassName = NULL) {
135 $this->setFormActionUri();
136 if (strtolower($this->arguments['method']) === 'get') {
137 $this->tag->addAttribute('method', 'get');
138 } else {
139 $this->tag->addAttribute('method', 'post');
140 }
141 $this->addFormObjectNameToViewHelperVariableContainer();
142 $this->addFormObjectToViewHelperVariableContainer();
143 $this->addFieldNamePrefixToViewHelperVariableContainer();
144 $this->addFormFieldNamesToViewHelperVariableContainer();
145 $formContent = $this->renderChildren();
146
147 if ($this->arguments['hiddenFieldClassName'] !== NULL) {
148 $content = chr(10) . '<div class="' . htmlspecialchars($this->arguments['hiddenFieldClassName']) . '">';
149 } else {
150 $content = chr(10) . '<div>';
151 }
152
153 $content .= $this->renderHiddenIdentityField($this->arguments['object'], $this->getFormObjectName());
154 $content .= $this->renderAdditionalIdentityFields();
155 $content .= $this->renderHiddenReferrerFields();
156 if ($this->configurationManager->isFeatureEnabled('rewrittenPropertyMapper') === FALSE) {
157 // Render hmac after everything else has been rendered
158 $content .= $this->renderRequestHashField();
159 } else {
160 // Render the trusted list of all properties after everything else has been rendered
161 $content .= $this->renderTrustedPropertiesField();
162 }
163 $content .= chr(10) . '</div>' . chr(10);
164 $content .= $formContent;
165 $this->tag->setContent($content);
166 $this->removeFieldNamePrefixFromViewHelperVariableContainer();
167 $this->removeFormObjectFromViewHelperVariableContainer();
168 $this->removeFormObjectNameFromViewHelperVariableContainer();
169 $this->removeFormFieldNamesFromViewHelperVariableContainer();
170 $this->removeCheckboxFieldNamesFromViewHelperVariableContainer();
171 return $this->tag->render();
172 }
173
174 /**
175 * Sets the "action" attribute of the form tag
176 *
177 * @return void
178 */
179 protected function setFormActionUri() {
180 if ($this->hasArgument('actionUri')) {
181 $formActionUri = $this->arguments['actionUri'];
182 } else {
183 $uriBuilder = $this->controllerContext->getUriBuilder();
184 $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']);
185 $this->formActionUriArguments = $uriBuilder->getArguments();
186 }
187 $this->tag->addAttribute('action', $formActionUri);
188 }
189
190 /**
191 * Render additional identity fields which were registered by form elements.
192 * This happens if a form field is defined like property="bla.blubb" - then we might need an identity property for the sub-object "bla".
193 *
194 * @return string HTML-string for the additional identity properties
195 */
196 protected function renderAdditionalIdentityFields() {
197 if ($this->viewHelperVariableContainer->exists('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'additionalIdentityProperties')) {
198 $additionalIdentityProperties = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'additionalIdentityProperties');
199 $output = '';
200 foreach ($additionalIdentityProperties as $identity) {
201 $output .= chr(10) . $identity;
202 }
203 return $output;
204 }
205 return '';
206 }
207
208 /**
209 * Renders hidden form fields for referrer information about
210 * the current controller and action.
211 *
212 * @return string Hidden fields with referrer information
213 * @todo filter out referrer information that is equal to the target (e.g. same packageKey)
214 */
215 protected function renderHiddenReferrerFields() {
216 $request = $this->controllerContext->getRequest();
217 $extensionName = $request->getControllerExtensionName();
218 $vendorName = $request->getControllerVendorName();
219 $controllerName = $request->getControllerName();
220 $actionName = $request->getControllerActionName();
221 $result = chr(10);
222 if ($this->configurationManager->isFeatureEnabled('rewrittenPropertyMapper')) {
223 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[@extension]') . '" value="' . $extensionName . '" />' . chr(10);
224 if ($vendorName !== NULL) {
225 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[@vendor]') . '" value="' . $vendorName . '" />' . chr(10);
226 }
227 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[@controller]') . '" value="' . $controllerName . '" />' . chr(10);
228 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[@action]') . '" value="' . $actionName . '" />' . chr(10);
229 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[arguments]') . '" value="' . htmlspecialchars($this->hashService->appendHmac(base64_encode(serialize($request->getArguments())))) . '" />' . chr(10);
230 } else {
231 // @deprecated since Fluid 1.4.0, will be removed two versions after Fluid 6.1.
232 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[extensionName]') . '" value="' . $extensionName . '" />' . chr(10);
233 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[controllerName]') . '" value="' . $controllerName . '" />' . chr(10);
234 $result .= '<input type="hidden" name="' . $this->prefixFieldName('__referrer[actionName]') . '" value="' . $actionName . '" />' . chr(10);
235 }
236 return $result;
237 }
238
239 /**
240 * Adds the form object name to the ViewHelperVariableContainer if "objectName" argument or "name" attribute is specified.
241 *
242 * @return void
243 */
244 protected function addFormObjectNameToViewHelperVariableContainer() {
245 $formObjectName = $this->getFormObjectName();
246 if ($formObjectName !== NULL) {
247 $this->viewHelperVariableContainer->add('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObjectName', $formObjectName);
248 }
249 }
250
251 /**
252 * Removes the form name from the ViewHelperVariableContainer.
253 *
254 * @return void
255 */
256 protected function removeFormObjectNameFromViewHelperVariableContainer() {
257 $formObjectName = $this->getFormObjectName();
258 if ($formObjectName !== NULL) {
259 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObjectName');
260 }
261 }
262
263 /**
264 * Returns the name of the object that is bound to this form.
265 * If the "objectName" argument has been specified, this is returned. Otherwise the name attribute of this form.
266 * If neither objectName nor name arguments have been set, NULL is returned.
267 *
268 * @return string specified Form name or NULL if neither $objectName nor $name arguments have been specified
269 */
270 protected function getFormObjectName() {
271 $formObjectName = NULL;
272 if ($this->hasArgument('objectName')) {
273 $formObjectName = $this->arguments['objectName'];
274 } elseif ($this->hasArgument('name')) {
275 $formObjectName = $this->arguments['name'];
276 }
277 return $formObjectName;
278 }
279
280 /**
281 * Adds the object that is bound to this form to the ViewHelperVariableContainer if the formObject attribute is specified.
282 *
283 * @return void
284 */
285 protected function addFormObjectToViewHelperVariableContainer() {
286 if ($this->hasArgument('object')) {
287 $this->viewHelperVariableContainer->add('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObject', $this->arguments['object']);
288 $this->viewHelperVariableContainer->add('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'additionalIdentityProperties', array());
289 }
290 }
291
292 /**
293 * Removes the form object from the ViewHelperVariableContainer.
294 *
295 * @return void
296 */
297 protected function removeFormObjectFromViewHelperVariableContainer() {
298 if ($this->hasArgument('object')) {
299 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObject');
300 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'additionalIdentityProperties');
301 }
302 }
303
304 /**
305 * Adds the field name prefix to the ViewHelperVariableContainer
306 *
307 * @return void
308 */
309 protected function addFieldNamePrefixToViewHelperVariableContainer() {
310 $fieldNamePrefix = $this->getFieldNamePrefix();
311 $this->viewHelperVariableContainer->add('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'fieldNamePrefix', $fieldNamePrefix);
312 }
313
314 /**
315 * Get the field name prefix
316 *
317 * @return string
318 */
319 protected function getFieldNamePrefix() {
320 if ($this->hasArgument('fieldNamePrefix')) {
321 return $this->arguments['fieldNamePrefix'];
322 } else {
323 return $this->getDefaultFieldNamePrefix();
324 }
325 }
326
327 /**
328 * Removes field name prefix from the ViewHelperVariableContainer
329 *
330 * @return void
331 */
332 protected function removeFieldNamePrefixFromViewHelperVariableContainer() {
333 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'fieldNamePrefix');
334 }
335
336 /**
337 * Adds a container for form field names to the ViewHelperVariableContainer
338 *
339 * @return void
340 */
341 protected function addFormFieldNamesToViewHelperVariableContainer() {
342 $this->viewHelperVariableContainer->add('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formFieldNames', array());
343 }
344
345 /**
346 * Removes the container for form field names from the ViewHelperVariableContainer
347 *
348 * @return void
349 */
350 protected function removeFormFieldNamesFromViewHelperVariableContainer() {
351 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formFieldNames');
352 }
353
354 /**
355 * Render the request hash field
356 *
357 * @return string the hmac field
358 */
359 protected function renderRequestHashField() {
360 $formFieldNames = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formFieldNames');
361 $this->postProcessUriArgumentsForRequesthash($this->formActionUriArguments, $formFieldNames);
362 $requestHash = $this->requestHashService->generateRequestHash($formFieldNames, $this->getFieldNamePrefix());
363 // in v4, we need to prefix __hmac as well to make it show up in the request object.
364 return '<input type="hidden" name="' . $this->prefixFieldName('__hmac') . '" value="' . htmlspecialchars($requestHash) . '" />';
365 }
366
367 /**
368 * Add the URI arguments after postprocessing to the request hash as well.
369 */
370 protected function postProcessUriArgumentsForRequestHash($arguments, &$results, $currentPrefix = '', $level = 0) {
371 if (!count($arguments)) {
372 return;
373 }
374 foreach ($arguments as $argumentName => $argumentValue) {
375 if (is_array($argumentValue)) {
376 $prefix = $level == 0 ? $argumentName : $currentPrefix . '[' . $argumentName . ']';
377 $this->postProcessUriArgumentsForRequestHash($argumentValue, $results, $prefix, $level + 1);
378 } else {
379 $results[] = $level == 0 ? $argumentName : $currentPrefix . '[' . $argumentName . ']';
380 }
381 }
382 }
383
384 /**
385 * Retrieves the default field name prefix for this form
386 *
387 * @return string default field name prefix
388 */
389 protected function getDefaultFieldNamePrefix() {
390 $request = $this->controllerContext->getRequest();
391 if ($this->hasArgument('extensionName')) {
392 $extensionName = $this->arguments['extensionName'];
393 } else {
394 $extensionName = $request->getControllerExtensionName();
395 }
396 if ($this->hasArgument('pluginName')) {
397 $pluginName = $this->arguments['pluginName'];
398 } else {
399 $pluginName = $request->getPluginName();
400 }
401 if ($extensionName !== NULL && $pluginName != NULL) {
402 return $this->extensionService->getPluginNamespace($extensionName, $pluginName);
403 } else {
404 return '';
405 }
406 }
407
408 /**
409 * Remove Checkbox field names from ViewHelper variable container, to start from scratch when a new form starts.
410 */
411 protected function removeCheckboxFieldNamesFromViewHelperVariableContainer() {
412 if ($this->viewHelperVariableContainer->exists('TYPO3\\CMS\\Fluid\\ViewHelpers\\Form\\CheckboxViewHelper', 'checkboxFieldNames')) {
413 $this->viewHelperVariableContainer->remove('TYPO3\\CMS\\Fluid\\ViewHelpers\\Form\\CheckboxViewHelper', 'checkboxFieldNames');
414 }
415 }
416
417 /**
418 * Render the request hash field
419 *
420 * @return string The hmac field
421 */
422 protected function renderTrustedPropertiesField() {
423 $formFieldNames = $this->viewHelperVariableContainer->get('TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper', 'formFieldNames');
424 $requestHash = $this->mvcPropertyMappingConfigurationService->generateTrustedPropertiesToken($formFieldNames, $this->getFieldNamePrefix());
425 return '<input type="hidden" name="' . $this->prefixFieldName('__trustedProperties') . '" value="' . htmlspecialchars($requestHash) . '" />';
426 }
427 }