‪TYPO3CMS  ‪main
SelectViewHelper.php
Go to the documentation of this file.
1 <?php
2 
3 declare(strict_types=1);
4 
5 /*
6  * This file is part of the TYPO3 CMS project.
7  *
8  * It is free software; you can redistribute it and/or modify it under
9  * the terms of the GNU General Public License, either version 2
10  * of the License, or any later version.
11  *
12  * For the full copyright and license information, please read the
13  * LICENSE.txt file that was distributed with this source code.
14  *
15  * The TYPO3 project - inspiring people to share!
16  */
17 
19 
21 use TYPO3Fluid\Fluid\Core\ViewHelper\Exception;
22 
97 {
101  protected ‪$tagName = 'select';
102 
106  protected ‪$selectedValue;
107 
108  public function ‪initializeArguments(): void
109  {
110  parent::initializeArguments();
111  $this->registerUniversalTagAttributes();
112  $this->registerTagAttribute('size', 'string', 'Size of select field, a numeric value to show the amount of items to be visible at the same time - equivalent to HTML <select> site attribute');
113  $this->registerTagAttribute('disabled', 'string', 'Specifies that the input element should be disabled when the page loads');
114  $this->registerArgument('options', 'array', 'Associative array with internal IDs as key, and the values are displayed in the select box. Can be combined with or replaced by child f:form.select.* nodes.');
115  $this->registerArgument('optionsAfterContent', 'boolean', 'If true, places auto-generated option tags after those rendered in the tag content. If false, automatic options come first.', false, false);
116  $this->registerArgument('optionValueField', 'string', 'If specified, will call the appropriate getter on each object to determine the value.');
117  $this->registerArgument('optionLabelField', 'string', 'If specified, will call the appropriate getter on each object to determine the label.');
118  $this->registerArgument('sortByOptionLabel', 'boolean', 'If true, List will be sorted by label.', false, false);
119  $this->registerArgument('selectAllByDefault', 'boolean', 'If specified options are selected if none was set before.', false, false);
120  $this->registerArgument('errorClass', 'string', 'CSS class to set if there are errors for this ViewHelper', false, 'f3-form-error');
121  $this->registerArgument('prependOptionLabel', 'string', 'If specified, will provide an option at first position with the specified label.');
122  $this->registerArgument('prependOptionValue', 'string', 'If specified, will provide an option at first position with the specified value.');
123  $this->registerArgument('multiple', 'boolean', 'If set multiple options may be selected.', false, false);
124  $this->registerArgument('required', 'boolean', 'If set no empty value is allowed.', false, false);
125  }
126 
127  public function ‪render(): string
128  {
129  if ($this->arguments['required']) {
130  $this->tag->addAttribute('required', 'required');
131  }
132  $name = $this->‪getName();
133  if ($this->arguments['multiple']) {
134  $this->tag->addAttribute('multiple', 'multiple');
135  $name .= '[]';
136  }
137  $this->tag->addAttribute('name', $name);
138  $options = $this->‪getOptions();
139 
140  $viewHelperVariableContainer = $this->renderingContext->getViewHelperVariableContainer();
141 
143  $this->‪setErrorClassAttribute();
144  $content = '';
145 
146  // register field name for token generation.
148  // in case it is a multi-select, we need to register the field name
149  // as often as there are elements in the box
150  if ($this->arguments['multiple']) {
151  $content .= $this->‪renderHiddenFieldForEmptyValue();
152  // Register the field name additional times as required by the total number of
153  // options. Since we already registered it once above, we start the counter at 1
154  // instead of 0.
155  $optionsCount = count($options);
156  for ($i = 1; $i < $optionsCount; $i++) {
158  }
159  // save the parent field name so that any child f:form.select.option
160  // tag will know to call registerFieldNameForFormTokenGeneration
161  // this is the reason why "self::class" is used instead of static::class (no LSB)
162  $viewHelperVariableContainer->addOrUpdate(
163  self::class,
164  'registerFieldNameForFormTokenGeneration',
165  $name
166  );
167  }
168 
169  $viewHelperVariableContainer->addOrUpdate(self::class, 'selectedValue', $this->‪getSelectedValue());
170  $prependContent = $this->‪renderPrependOptionTag();
171  $tagContent = $this->‪renderOptionTags($options);
172  $childContent = $this->renderChildren();
173  $viewHelperVariableContainer->remove(self::class, 'selectedValue');
174  $viewHelperVariableContainer->remove(self::class, 'registerFieldNameForFormTokenGeneration');
175  if (isset($this->arguments['optionsAfterContent']) && $this->arguments['optionsAfterContent']) {
176  $tagContent = $childContent . $tagContent;
177  } else {
178  $tagContent .= $childContent;
179  }
180  $tagContent = $prependContent . $tagContent;
181 
182  $this->tag->forceClosingTag(true);
183  $this->tag->setContent($tagContent);
184  $content .= $this->tag->render();
185  return $content;
186  }
187 
191  protected function ‪renderPrependOptionTag(): string
192  {
193  ‪$output = '';
194  if ($this->hasArgument('prependOptionLabel')) {
195  $value = $this->hasArgument('prependOptionValue') ? $this->arguments['prependOptionValue'] : '';
196  $label = $this->arguments['prependOptionLabel'];
197  ‪$output .= $this->‪renderOptionTag((string)$value, (string)$label, false) . LF;
198  }
199  return ‪$output;
200  }
201 
205  protected function ‪renderOptionTags(array $options): string
206  {
207  ‪$output = '';
208  foreach ($options as $value => $label) {
209  $isSelected = $this->‪isSelected($value);
210  ‪$output .= $this->‪renderOptionTag((string)$value, (string)$label, $isSelected) . LF;
211  }
212  return ‪$output;
213  }
214 
220  protected function ‪getOptions(): array
221  {
222  if (!is_array($this->arguments['options']) && !$this->arguments['options'] instanceof \Traversable) {
223  return [];
224  }
225  $options = [];
226  $optionsArgument = $this->arguments['options'];
227  foreach ($optionsArgument as $key => $value) {
228  if (!is_object($value) && !is_array($value)) {
229  $options[$key] = $value;
230  continue;
231  }
232  if (is_array($value)) {
233  if (!$this->hasArgument('optionValueField')) {
234  throw new \InvalidArgumentException('Missing parameter "optionValueField" in SelectViewHelper for array value options.', 1682693720);
235  }
236  if (!$this->hasArgument('optionLabelField')) {
237  throw new \InvalidArgumentException('Missing parameter "optionLabelField" in SelectViewHelper for array value options.', 1682693721);
238  }
239  $key = ‪ObjectAccess::getPropertyPath($value, $this->arguments['optionValueField']);
240  $value = ‪ObjectAccess::getPropertyPath($value, $this->arguments['optionLabelField']);
241  $options[$key] = $value;
242  continue;
243  }
244  if ($this->hasArgument('optionValueField')) {
245  $key = ‪ObjectAccess::getPropertyPath($value, $this->arguments['optionValueField']);
246  if (is_object($key)) {
247  if (method_exists($key, '__toString')) {
248  $key = (string)$key;
249  } else {
250  throw new Exception('Identifying value for object of class "' . get_debug_type($value) . '" was an object.', 1247827428);
251  }
252  }
253  } elseif ($this->persistenceManager->getIdentifierByObject($value) !== null) {
254  // @todo use $this->persistenceManager->isNewObject() once it is implemented
255  $key = $this->persistenceManager->getIdentifierByObject($value);
256  } elseif (is_object($value) && method_exists($value, '__toString')) {
257  $key = (string)$value;
258  } elseif (is_object($value)) {
259  throw new Exception('No identifying value for object of class "' . get_class($value) . '" found.', 1247826696);
260  }
261  if ($this->hasArgument('optionLabelField')) {
262  $value = ‪ObjectAccess::getPropertyPath($value, $this->arguments['optionLabelField']);
263  if (is_object($value)) {
264  if (method_exists($value, '__toString')) {
265  $value = (string)$value;
266  } else {
267  throw new Exception('Label value for object of class "' . get_class($value) . '" was an object without a __toString() method.', 1247827553);
268  }
269  }
270  } elseif (is_object($value) && method_exists($value, '__toString')) {
271  $value = (string)$value;
272  } elseif ($this->persistenceManager->getIdentifierByObject($value) !== null) {
273  // @todo use $this->persistenceManager->isNewObject() once it is implemented
274  $value = $this->persistenceManager->getIdentifierByObject($value);
275  }
276  $options[$key] = $value;
277  }
278  if ($this->arguments['sortByOptionLabel']) {
279  asort($options, SORT_LOCALE_STRING);
280  }
281  return $options;
282  }
283 
290  protected function ‪isSelected($value): bool
291  {
293  if ($value === ‪$selectedValue || (string)$value === ‪$selectedValue) {
294  return true;
295  }
296  if ($this->hasArgument('multiple')) {
297  if (‪$selectedValue === null && $this->arguments['selectAllByDefault'] === true) {
298  return true;
299  }
300  if (is_array(‪$selectedValue) && in_array($value, ‪$selectedValue)) {
301  return true;
302  }
303  }
304  return false;
305  }
306 
312  protected function ‪getSelectedValue()
313  {
314  $this->‪setRespectSubmittedDataValue(true);
315  $value = $this->‪getValueAttribute();
316  if (!is_array($value) && !$value instanceof \Traversable) {
317  return $this->‪getOptionValueScalar($value);
318  }
319  $selectedValues = [];
320  foreach ($value as $selectedValueElement) {
321  $selectedValues[] = $this->‪getOptionValueScalar($selectedValueElement);
322  }
323  return $selectedValues;
324  }
325 
332  protected function ‪getOptionValueScalar($valueElement)
333  {
334  if (is_object($valueElement)) {
335  if ($this->hasArgument('optionValueField')) {
336  return ‪ObjectAccess::getPropertyPath($valueElement, $this->arguments['optionValueField']);
337  }
338  // @todo use $this->persistenceManager->isNewObject() once it is implemented
339  if ($this->persistenceManager->getIdentifierByObject($valueElement) !== null) {
340  return $this->persistenceManager->getIdentifierByObject($valueElement);
341  }
342  return (string)$valueElement;
343  }
344  return $valueElement;
345  }
346 
355  protected function ‪renderOptionTag(string $value, string $label, bool $isSelected): string
356  {
357  ‪$output = '<option value="' . htmlspecialchars($value) . '"';
358  if ($isSelected) {
359  ‪$output .= ' selected="selected"';
360  }
361  ‪$output .= '>' . htmlspecialchars($label) . '</option>';
362  return ‪$output;
363  }
364 }
‪TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper\setErrorClassAttribute
‪setErrorClassAttribute()
Definition: AbstractFormFieldViewHelper.php:331
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\$selectedValue
‪mixed $selectedValue
Definition: SelectViewHelper.php:104
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess\getPropertyPath
‪static mixed getPropertyPath(object|array $subject, string $propertyPath)
Definition: ObjectAccess.php:128
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\renderOptionTags
‪renderOptionTags(array $options)
Definition: SelectViewHelper.php:203
‪TYPO3\CMS\Fluid\ViewHelpers\Form
Definition: AbstractFormFieldViewHelper.php:18
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\renderPrependOptionTag
‪renderPrependOptionTag()
Definition: SelectViewHelper.php:189
‪TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormViewHelper\registerFieldNameForFormTokenGeneration
‪registerFieldNameForFormTokenGeneration(string $fieldName)
Definition: AbstractFormViewHelper.php:100
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\initializeArguments
‪initializeArguments()
Definition: SelectViewHelper.php:106
‪TYPO3\CMS\Extbase\Reflection\ObjectAccess
Definition: ObjectAccess.php:39
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\$tagName
‪string $tagName
Definition: SelectViewHelper.php:100
‪TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper\renderHiddenFieldForEmptyValue
‪renderHiddenFieldForEmptyValue()
Definition: AbstractFormFieldViewHelper.php:372
‪TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper\setRespectSubmittedDataValue
‪setRespectSubmittedDataValue(bool $respectSubmittedDataValue)
Definition: AbstractFormFieldViewHelper.php:69
‪TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper\getName
‪getName()
Definition: AbstractFormFieldViewHelper.php:79
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper
Definition: SelectViewHelper.php:97
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\render
‪render()
Definition: SelectViewHelper.php:125
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\getOptions
‪array getOptions()
Definition: SelectViewHelper.php:218
‪$output
‪$output
Definition: annotationChecker.php:114
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\getOptionValueScalar
‪string getOptionValueScalar($valueElement)
Definition: SelectViewHelper.php:330
‪TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper
Definition: AbstractFormFieldViewHelper.php:37
‪TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper\addAdditionalIdentityPropertiesIfNeeded
‪addAdditionalIdentityPropertiesIfNeeded()
Definition: AbstractFormFieldViewHelper.php:241
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\isSelected
‪bool isSelected($value)
Definition: SelectViewHelper.php:288
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\getSelectedValue
‪mixed getSelectedValue()
Definition: SelectViewHelper.php:310
‪TYPO3\CMS\Fluid\ViewHelpers\Form\AbstractFormFieldViewHelper\getValueAttribute
‪mixed getValueAttribute()
Definition: AbstractFormFieldViewHelper.php:147
‪TYPO3\CMS\Fluid\ViewHelpers\Form\SelectViewHelper\renderOptionTag
‪string renderOptionTag(string $value, string $label, bool $isSelected)
Definition: SelectViewHelper.php:353