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