[CLEANUP] Rework/simplify copyright header and remove @package
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / ViewHelpers / Form / AbstractFormFieldViewHelper.php
1 <?php
2 namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is backported from the TYPO3 Flow package "TYPO3.Fluid".
8 *
9 * It is free software; you can redistribute it and/or modify it under
10 * the terms of the GNU General Public License, either version 2
11 * of the License, or any later version.
12 *
13 * For the full copyright and license information, please read the
14 * LICENSE.txt file that was distributed with this source code.
15 *
16 * The TYPO3 project - inspiring people to share!
17 */
18
19 use TYPO3\CMS\Extbase\Reflection\ObjectAccess;
20
21 /**
22 * Abstract Form View Helper. Bundles functionality related to direct property access of objects in other Form ViewHelpers.
23 *
24 * If you set the "property" attribute to the name of the property to resolve from the object, this class will
25 * automatically set the name and value of a form element.
26 *
27 * @api
28 */
29 abstract class AbstractFormFieldViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormViewHelper {
30
31 /**
32 * @var \TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface
33 * @inject
34 */
35 protected $configurationManager;
36
37 /**
38 * Initialize arguments.
39 *
40 * @return void
41 * @api
42 */
43 public function initializeArguments() {
44 parent::initializeArguments();
45 $this->registerArgument('name', 'string', 'Name of input tag');
46 $this->registerArgument('value', 'mixed', 'Value of input tag');
47 $this->registerArgument('property', 'string', 'Name of Object Property. If used in conjunction with <f:form object="...">, "name" and "value" properties will be ignored.');
48 }
49
50 /**
51 * Get the name of this form element.
52 * Either returns arguments['name'], or the correct name for Object Access.
53 *
54 * In case property is something like bla.blubb (hierarchical), then [bla][blubb] is generated.
55 *
56 * @return string Name
57 */
58 protected function getName() {
59 $name = $this->getNameWithoutPrefix();
60 return $this->prefixFieldName($name);
61 }
62
63 /**
64 * Get the name of this form element, without prefix.
65 *
66 * @return string name
67 */
68 protected function getNameWithoutPrefix() {
69 if ($this->isObjectAccessorMode()) {
70 $formObjectName = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObjectName');
71 if (!empty($formObjectName)) {
72 $propertySegments = explode('.', $this->arguments['property']);
73 $propertyPath = '';
74 foreach ($propertySegments as $segment) {
75 $propertyPath .= '[' . $segment . ']';
76 }
77 $name = $formObjectName . $propertyPath;
78 } else {
79 $name = $this->arguments['property'];
80 }
81 } else {
82 $name = $this->arguments['name'];
83 }
84 if ($this->hasArgument('value') && is_object($this->arguments['value'])) {
85 // TODO: Use $this->persistenceManager->isNewObject() once it is implemented
86 if (NULL !== $this->persistenceManager->getIdentifierByObject($this->arguments['value'])) {
87 $name .= '[__identity]';
88 }
89 }
90 return $name;
91 }
92
93 /**
94 * Get the value of this form element.
95 * Either returns arguments['value'], or the correct value for Object Access.
96 *
97 * @param boolean $convertObjects whether or not to convert objects to identifiers
98 * @return mixed Value
99 */
100 protected function getValue($convertObjects = TRUE) {
101 $value = NULL;
102 if ($this->hasArgument('value')) {
103 $value = $this->arguments['value'];
104 } elseif ($this->configurationManager->isFeatureEnabled('rewrittenPropertyMapper') && $this->hasMappingErrorOccurred()) {
105 $this->addAdditionalIdentityPropertiesIfNeeded();
106 $value = $this->getLastSubmittedFormData();
107 } elseif ($this->isObjectAccessorMode() && $this->viewHelperVariableContainer->exists('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObject')) {
108 $this->addAdditionalIdentityPropertiesIfNeeded();
109 $value = $this->getPropertyValue();
110 }
111 if ($convertObjects === TRUE && is_object($value)) {
112 $identifier = $this->persistenceManager->getIdentifierByObject($value);
113 if ($identifier !== NULL) {
114 $value = $identifier;
115 }
116 }
117 return $value;
118 }
119
120 /**
121 * Checks if a property mapping error has occurred in the last request.
122 *
123 * @return boolean TRUE if a mapping error occurred, FALSE otherwise
124 */
125 protected function hasMappingErrorOccurred() {
126 return $this->controllerContext->getRequest()->getOriginalRequest() !== NULL;
127 }
128
129 /**
130 * Get the form data which has last been submitted; only returns valid data in case
131 * a property mapping error has occurred. Check with hasMappingErrorOccurred() before!
132 *
133 * @return mixed
134 */
135 protected function getLastSubmittedFormData() {
136 $propertyPath = rtrim(preg_replace('/(\\]\\[|\\[|\\])/', '.', $this->getNameWithoutPrefix()), '.');
137 $value = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getPropertyPath($this->controllerContext->getRequest()->getOriginalRequest()->getArguments(), $propertyPath);
138 return $value;
139 }
140
141 /**
142 * Add additional identity properties in case the current property is hierarchical (of the form "bla.blubb").
143 * Then, [bla][__identity] has to be generated as well.
144 *
145 * @return void
146 */
147 protected function addAdditionalIdentityPropertiesIfNeeded() {
148 $propertySegments = explode('.', $this->arguments['property']);
149 if (count($propertySegments) >= 2) {
150 // hierarchical property. If there is no "." inside (thus $propertySegments == 1), we do not need to do anything
151 $formObject = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObject');
152 $objectName = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObjectName');
153 // If Count == 2 -> we need to go through the for-loop exactly once
154 for ($i = 1; $i < count($propertySegments); $i++) {
155 $object = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getPropertyPath($formObject, implode('.', array_slice($propertySegments, 0, $i)));
156 $objectName .= '[' . $propertySegments[($i - 1)] . ']';
157 $hiddenIdentityField = $this->renderHiddenIdentityField($object, $objectName);
158 // Add the hidden identity field to the ViewHelperVariableContainer
159 $additionalIdentityProperties = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'additionalIdentityProperties');
160 $additionalIdentityProperties[$objectName] = $hiddenIdentityField;
161 $this->viewHelperVariableContainer->addOrUpdate('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'additionalIdentityProperties', $additionalIdentityProperties);
162 }
163 }
164 }
165
166 /**
167 * Get the current property of the object bound to this form.
168 *
169 * @return mixed Value
170 */
171 protected function getPropertyValue() {
172 if (!$this->viewHelperVariableContainer->exists('TYPO3\CMS\Fluid\ViewHelpers\FormViewHelper', 'formObject')) {
173 return NULL;
174 }
175 $formObject = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObject');
176 $propertyName = $this->arguments['property'];
177 if (is_array($formObject)) {
178 return isset($formObject[$propertyName]) ? $formObject[$propertyName] : NULL;
179 }
180 return \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getPropertyPath($formObject, $propertyName);
181 }
182
183 /**
184 * Internal method which checks if we should evaluate a domain object or just output arguments['name'] and arguments['value']
185 *
186 * @return boolean TRUE if we should evaluate the domain object, FALSE otherwise.
187 */
188 protected function isObjectAccessorMode() {
189 return $this->hasArgument('property') && $this->viewHelperVariableContainer->exists('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObjectName');
190 }
191
192 /**
193 * Add an CSS class if this view helper has errors
194 *
195 * @return void
196 */
197 protected function setErrorClassAttribute() {
198 if ($this->hasArgument('class')) {
199 $cssClass = $this->arguments['class'] . ' ';
200 } else {
201 $cssClass = '';
202 }
203 if ($this->configurationManager->isFeatureEnabled('rewrittenPropertyMapper')) {
204 $mappingResultsForProperty = $this->getMappingResultsForProperty();
205 if ($mappingResultsForProperty->hasErrors()) {
206 if ($this->hasArgument('errorClass')) {
207 $cssClass .= $this->arguments['errorClass'];
208 } else {
209 $cssClass .= 'error';
210 }
211 $this->tag->addAttribute('class', $cssClass);
212 }
213 } else {
214 // @deprecated since Fluid 1.4.0, will will be removed two versions after Fluid 6.1.
215 $errors = $this->getErrorsForProperty();
216 if (count($errors) > 0) {
217 if ($this->hasArgument('errorClass')) {
218 $cssClass .= $this->arguments['errorClass'];
219 } else {
220 $cssClass .= 'error';
221 }
222 $this->tag->addAttribute('class', $cssClass);
223 }
224 }
225 }
226
227 /**
228 * Get errors for the property and form name of this view helper
229 *
230 * @return array<Tx_Extbase_Error_Result> Array of errors
231 */
232 protected function getMappingResultsForProperty() {
233 if (!$this->isObjectAccessorMode()) {
234 return new \TYPO3\CMS\Extbase\Error\Result();
235 }
236 $originalRequestMappingResults = $this->controllerContext->getRequest()->getOriginalRequestMappingResults();
237 $formObjectName = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObjectName');
238 return $originalRequestMappingResults->forProperty($formObjectName)->forProperty($this->arguments['property']);
239 }
240
241 /**
242 * Get errors for the property and form name of this view helper
243 *
244 * @return array An array of Tx_Fluid_Error_Error objects
245 * @deprecated since Fluid 1.4.0, will will be removed two versions after Fluid 6.1.
246 */
247 protected function getErrorsForProperty() {
248 if (!$this->isObjectAccessorMode()) {
249 return array();
250 }
251 $errors = $this->controllerContext->getRequest()->getErrors();
252 $formObjectName = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'formObjectName');
253 $propertyName = $this->arguments['property'];
254 foreach ($errors as $error) {
255 if ($error instanceof \TYPO3\CMS\Extbase\Validation\PropertyError && $error->getPropertyName() === $formObjectName) {
256 $formErrors = $error->getErrors();
257 foreach ($formErrors as $formError) {
258 if ($formError instanceof \TYPO3\CMS\Extbase\Validation\PropertyError && $formError->getPropertyName() === $propertyName) {
259 return $formError->getErrors();
260 }
261 }
262 }
263 }
264 return array();
265 }
266
267 /**
268 * Renders a hidden field with the same name as the element, to make sure the empty value is submitted
269 * in case nothing is selected. This is needed for checkbox and multiple select fields
270 *
271 * @return string the hidden field.
272 */
273 protected function renderHiddenFieldForEmptyValue() {
274 $hiddenFieldNames = array();
275 if ($this->viewHelperVariableContainer->exists('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'renderedHiddenFields')) {
276 $hiddenFieldNames = $this->viewHelperVariableContainer->get('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'renderedHiddenFields');
277 }
278 $fieldName = $this->getName();
279 if (substr($fieldName, -2) === '[]') {
280 $fieldName = substr($fieldName, 0, -2);
281 }
282 if (!in_array($fieldName, $hiddenFieldNames)) {
283 $hiddenFieldNames[] = $fieldName;
284 $this->viewHelperVariableContainer->addOrUpdate('TYPO3\\CMS\\Fluid\\ViewHelpers\\FormViewHelper', 'renderedHiddenFields', $hiddenFieldNames);
285 return '<input type="hidden" name="' . htmlspecialchars($fieldName) . '" value="" />';
286 }
287 return '';
288 }
289 }