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