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