[TASK] Remove unneeded parenthesis on array-access
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / ViewHelpers / Form / AbstractFormFieldViewHelper.php
1 <?php
2
3 namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
4
5 /* *
6 * This script is backported from the TYPO3 Flow package "TYPO3.Fluid". *
7 * *
8 * It is free software; you can redistribute it and/or modify it under *
9 * the terms of the GNU Lesser General Public License, either version 3 *
10 * of the License, or (at your option) any later version. *
11 * *
12 * *
13 * This script is distributed in the hope that it will be useful, but *
14 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
15 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
16 * General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU Lesser General Public *
19 * License along with the script. *
20 * If not, see http://www.gnu.org/licenses/lgpl.html *
21 * *
22 * The TYPO3 project - inspiring people to share! *
23 * */
24
25 use TYPO3\CMS\Core\Utility\GeneralUtility;
26 use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
27
28 /**
29 * Abstract Form View Helper. Bundles functionality related to direct property access of objects in other Form ViewHelpers.
30 *
31 * If you set the "property" attribute to the name of the property to resolve from the object, this class will
32 * automatically set the name and value of a form element.
33 *
34 * @api
35 */
36 abstract class AbstractFormFieldViewHelper extends AbstractFormViewHelper
37 {
38 /**
39 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
40 * @inject
41 */
42 protected $configurationManager;
43
44 /**
45 * @var bool
46 */
47 protected $respectSubmittedDataValue = false;
48
49 /**
50 * Initialize arguments.
51 *
52 * @return void
53 * @api
54 */
55 public function initializeArguments()
56 {
57 parent::initializeArguments();
58 $this->registerArgument('name', 'string', 'Name of input tag');
59 $this->registerArgument('value', 'mixed', 'Value of input tag');
60 $this->registerArgument(
61 'property', 'string',
62 'Name of Object Property. If used in conjunction with <f:form object="...">, "name" and "value" properties will be ignored.'
63 );
64 }
65
66 /**
67 * Getting the current configuration for respectSubmittedDataValue.
68 *
69 * @return bool
70 */
71 public function getRespectSubmittedDataValue()
72 {
73 return $this->respectSubmittedDataValue;
74 }
75
76 /**
77 * Define respectSubmittedDataValue to enable or disable the usage of the submitted values in the viewhelper.
78 *
79 * @param bool $respectSubmittedDataValue
80 * @return void
81 */
82 public function setRespectSubmittedDataValue($respectSubmittedDataValue)
83 {
84 $this->respectSubmittedDataValue = $respectSubmittedDataValue;
85 }
86
87 /**
88 * Get the name of this form element.
89 * Either returns arguments['name'], or the correct name for Object Access.
90 *
91 * In case property is something like bla.blubb (hierarchical), then [bla][blubb] is generated.
92 *
93 * @return string Name
94 */
95 protected function getName()
96 {
97 $name = $this->getNameWithoutPrefix();
98 return $this->prefixFieldName($name);
99 }
100
101 /**
102 * Shortcut for retrieving the request from the controller context
103 *
104 * @return \TYPO3\CMS\Extbase\Mvc\Request
105 */
106 protected function getRequest()
107 {
108 return $this->controllerContext->getRequest();
109 }
110
111 /**
112 * Get the name of this form element, without prefix.
113 *
114 * @return string name
115 */
116 protected function getNameWithoutPrefix()
117 {
118 if ($this->isObjectAccessorMode()) {
119 $formObjectName = $this->viewHelperVariableContainer->get(
120 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObjectName'
121 );
122 if (!empty($formObjectName)) {
123 $propertySegments = explode('.', $this->arguments['property']);
124 $propertyPath = '';
125 foreach ($propertySegments as $segment) {
126 $propertyPath .= '[' . $segment . ']';
127 }
128 $name = $formObjectName . $propertyPath;
129 } else {
130 $name = $this->arguments['property'];
131 }
132 } else {
133 $name = $this->arguments['name'];
134 }
135 if ($this->hasArgument('value') && is_object($this->arguments['value'])) {
136 // @todo Use $this->persistenceManager->isNewObject() once it is implemented
137 if (null !== $this->persistenceManager->getIdentifierByObject($this->arguments['value'])) {
138 $name .= '[__identity]';
139 }
140 }
141 return $name;
142 }
143
144 /**
145 * Get the value of this form element.
146 * Either returns arguments['value'], or the correct value for Object Access.
147 *
148 * @deprecated since TYPO3 CMS 7, will be removed in TYPO3 CMS 8
149 * @param bool $convertObjects whether or not to convert objects to identifiers
150 * @return mixed Value
151 */
152 protected function getValue($convertObjects = true)
153 {
154 $value = null;
155 GeneralUtility::logDeprecatedFunction();
156
157 if ($this->hasArgument('value')) {
158 $value = $this->arguments['value'];
159 } elseif ($this->isObjectAccessorMode()) {
160 if ($this->hasMappingErrorOccurred()) {
161 $value = $this->getLastSubmittedFormData();
162 } else {
163 $value = $this->getPropertyValue();
164 }
165 $this->addAdditionalIdentityPropertiesIfNeeded();
166 }
167
168 if ($convertObjects) {
169 $value = $this->convertToPlainValue($value);
170 }
171 return $value;
172 }
173
174 /**
175 * Returns the current value of this Form ViewHelper and converts it to an identifier string in case it's an object
176 * The value is determined as follows:
177 * * If property mapping errors occurred and the form is re-displayed, the *last submitted* value is returned
178 * * Else the bound property is returned (only in objectAccessor-mode)
179 * * As fallback the "value" argument of this ViewHelper is used
180 *
181 * Note: This method should *not* be used for form elements that must not change the value attribute, e.g. (radio) buttons and checkboxes.
182 *
183 * @return mixed Value
184 */
185 protected function getValueAttribute()
186 {
187 $value = null;
188
189 if ($this->respectSubmittedDataValue) {
190 $value = $this->getValueFromSubmittedFormData($value);
191 } elseif ($this->hasArgument('value')) {
192 $value = $this->arguments['value'];
193 } elseif ($this->isObjectAccessorMode()) {
194 $value = $this->getPropertyValue();
195 }
196
197 $value = $this->convertToPlainValue($value);
198 return $value;
199 }
200
201 /**
202 * If property mapping errors occurred and the form is re-displayed, the *last submitted* value is returned by this
203 * method.
204 *
205 * Note:
206 * This method should *not* be used for form elements that must not change the value attribute, e.g. (radio)
207 * buttons and checkboxes. The default behaviour is not to use this method. You need to set
208 * respectSubmittedDataValue to TRUE to enable the form data handling for the viewhelper.
209 *
210 * @param mixed $value
211 * @return mixed Value
212 */
213 protected function getValueFromSubmittedFormData($value)
214 {
215 $submittedFormData = null;
216 if ($this->hasMappingErrorOccurred()) {
217 $submittedFormData = $this->getLastSubmittedFormData();
218 }
219 if ($submittedFormData !== null) {
220 $value = $submittedFormData;
221 } elseif ($this->hasArgument('value')) {
222 $value = $this->arguments['value'];
223 } elseif ($this->isObjectAccessorMode()) {
224 $value = $this->getPropertyValue();
225 }
226
227 return $value;
228 }
229
230 /**
231 * Converts an arbitrary value to a plain value
232 *
233 * @param mixed $value The value to convert
234 * @return mixed
235 */
236 protected function convertToPlainValue($value)
237 {
238 if (is_object($value)) {
239 $identifier = $this->persistenceManager->getIdentifierByObject($value);
240 if ($identifier !== null) {
241 $value = $identifier;
242 }
243 }
244 return $value;
245 }
246
247 /**
248 * Checks if a property mapping error has occurred in the last request.
249 *
250 * @return bool TRUE if a mapping error occurred, FALSE otherwise
251 */
252 protected function hasMappingErrorOccurred()
253 {
254 return $this->getRequest()->getOriginalRequest() !== null;
255 }
256
257 /**
258 * Get the form data which has last been submitted; only returns valid data in case
259 * a property mapping error has occurred. Check with hasMappingErrorOccurred() before!
260 *
261 * @return mixed
262 */
263 protected function getLastSubmittedFormData()
264 {
265 $propertyPath = rtrim(preg_replace('/(\\]\\[|\\[|\\])/', '.', $this->getNameWithoutPrefix()), '.');
266 $value = ObjectAccess::getPropertyPath(
267 $this->controllerContext->getRequest()->getOriginalRequest()->getArguments(), $propertyPath
268 );
269 return $value;
270 }
271
272 /**
273 * Add additional identity properties in case the current property is hierarchical (of the form "bla.blubb").
274 * Then, [bla][__identity] has to be generated as well.
275 *
276 * @return void
277 */
278 protected function addAdditionalIdentityPropertiesIfNeeded()
279 {
280 if (!$this->isObjectAccessorMode()) {
281 return;
282 }
283
284 if (!$this->viewHelperVariableContainer->exists(
285 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObject'
286 )
287 ) {
288 return;
289 }
290 $propertySegments = explode('.', $this->arguments['property']);
291 // hierarchical property. If there is no "." inside (thus $propertySegments == 1), we do not need to do anything
292 if (count($propertySegments) < 2) {
293 return;
294 }
295 $formObject = $this->viewHelperVariableContainer->get(
296 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObject'
297 );
298 $objectName = $this->viewHelperVariableContainer->get(
299 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObjectName'
300 );
301 // If count == 2 -> we need to go through the for-loop exactly once
302 for ($i = 1; $i < count($propertySegments); $i++) {
303 $object = ObjectAccess::getPropertyPath($formObject, implode('.', array_slice($propertySegments, 0, $i)));
304 $objectName .= '[' . $propertySegments[$i - 1] . ']';
305 $hiddenIdentityField = $this->renderHiddenIdentityField($object, $objectName);
306 // Add the hidden identity field to the ViewHelperVariableContainer
307 $additionalIdentityProperties = $this->viewHelperVariableContainer->get(
308 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'additionalIdentityProperties'
309 );
310 $additionalIdentityProperties[$objectName] = $hiddenIdentityField;
311 $this->viewHelperVariableContainer->addOrUpdate(
312 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'additionalIdentityProperties',
313 $additionalIdentityProperties
314 );
315 }
316 }
317
318 /**
319 * Get the current property of the object bound to this form.
320 *
321 * @return mixed Value
322 */
323 protected function getPropertyValue()
324 {
325 if (!$this->viewHelperVariableContainer->exists(
326 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObject'
327 )
328 ) {
329 return null;
330 }
331 $formObject = $this->viewHelperVariableContainer->get(
332 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObject'
333 );
334 return ObjectAccess::getPropertyPath($formObject, $this->arguments['property']);
335 }
336
337 /**
338 * Internal method which checks if we should evaluate a domain object or just output arguments['name'] and arguments['value']
339 *
340 * @return bool TRUE if we should evaluate the domain object, FALSE otherwise.
341 */
342 protected function isObjectAccessorMode()
343 {
344 return $this->hasArgument('property') && $this->viewHelperVariableContainer->exists(
345 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObjectName'
346 );
347 }
348
349 /**
350 * Add an CSS class if this view helper has errors
351 *
352 * @return void
353 */
354 protected function setErrorClassAttribute()
355 {
356 if ($this->hasArgument('class')) {
357 $cssClass = $this->arguments['class'] . ' ';
358 } else {
359 $cssClass = '';
360 }
361
362 $mappingResultsForProperty = $this->getMappingResultsForProperty();
363 if ($mappingResultsForProperty->hasErrors()) {
364 if ($this->hasArgument('errorClass')) {
365 $cssClass .= $this->arguments['errorClass'];
366 } else {
367 $cssClass .= 'error';
368 }
369 $this->tag->addAttribute('class', $cssClass);
370 }
371 }
372
373 /**
374 * Get errors for the property and form name of this view helper
375 *
376 * @return \TYPO3\CMS\Extbase\Error\Result Array of errors
377 */
378 protected function getMappingResultsForProperty()
379 {
380 if (!$this->isObjectAccessorMode()) {
381 return new \TYPO3\CMS\Extbase\Error\Result();
382 }
383 $originalRequestMappingResults = $this->getRequest()->getOriginalRequestMappingResults();
384 $formObjectName = $this->viewHelperVariableContainer->get(
385 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'formObjectName'
386 );
387 return $originalRequestMappingResults->forProperty($formObjectName)->forProperty($this->arguments['property']);
388 }
389
390 /**
391 * Renders a hidden field with the same name as the element, to make sure the empty value is submitted
392 * in case nothing is selected. This is needed for checkbox and multiple select fields
393 *
394 * @return string the hidden field.
395 */
396 protected function renderHiddenFieldForEmptyValue()
397 {
398 $hiddenFieldNames = [];
399 if ($this->viewHelperVariableContainer->exists(
400 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'renderedHiddenFields'
401 )
402 ) {
403 $hiddenFieldNames = $this->viewHelperVariableContainer->get(
404 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'renderedHiddenFields'
405 );
406 }
407 $fieldName = $this->getName();
408 if (substr($fieldName, -2) === '[]') {
409 $fieldName = substr($fieldName, 0, -2);
410 }
411 if (!in_array($fieldName, $hiddenFieldNames)) {
412 $hiddenFieldNames[] = $fieldName;
413 $this->viewHelperVariableContainer->addOrUpdate(
414 \TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper::class, 'renderedHiddenFields', $hiddenFieldNames
415 );
416 return '<input type="hidden" name="' . htmlspecialchars($fieldName) . '" value="" />';
417 }
418 return '';
419 }
420 }