true, 'autocomplete' => true, 'autofocus' => true, 'disabled' => true, 'form' => true, 'multiple' => true, 'required' => true, 'size' => true, ]; /** * Attributes valid for options * * @var array */ protected $validOptionAttributes = [ 'disabled' => true, 'selected' => true, 'label' => true, 'value' => true, ]; /** * Attributes valid for option groups * * @var array */ protected $validOptgroupAttributes = [ 'disabled' => true, 'label' => true, ]; /** @var array */ protected $translatableAttributes = [ 'label' => true, ]; /** @var FormHidden|null */ protected $formHiddenHelper; /** * Invoke helper as functor * * Proxies to {@link render()}. * * @template T as null|ElementInterface * @psalm-param T $element * @psalm-return (T is null ? self : string) * @return string|FormSelect */ public function __invoke(?ElementInterface $element = null) { if (! $element) { return $this; } return $this->render($element); } /** * Render a form %s', $this->createAttributesString($attributes), $this->renderOptions($options, $value) ); // Render hidden element if ($element->useHiddenElement()) { $rendered = $this->renderHiddenElement($element) . $rendered; } return $rendered; } /** * Render an array of options * * Individual options should be of the form: * * * array( * 'value' => 'value', * 'label' => 'label', * 'disabled' => $booleanFlag, * 'selected' => $booleanFlag, * ) * * * @param array $options * @param array $selectedOptions Option values that should be marked as selected */ public function renderOptions(array $options, array $selectedOptions = []): string { $template = ''; $optionStrings = []; $escapeHtml = $this->getEscapeHtmlHelper(); $stringSelectedOptions = array_map('strval', $selectedOptions); foreach ($options as $key => $optionSpec) { $value = ''; $label = ''; $selected = false; $disabled = false; if (is_scalar($optionSpec)) { $optionSpec = [ 'label' => $optionSpec, 'value' => $key, ]; } if (isset($optionSpec['options']) && is_array($optionSpec['options'])) { $optionStrings[] = $this->renderOptgroup($optionSpec, $selectedOptions); continue; } if (isset($optionSpec['value'])) { $value = $optionSpec['value']; } if (isset($optionSpec['label'])) { $label = $optionSpec['label']; } if (isset($optionSpec['selected'])) { $selected = $optionSpec['selected']; } if (isset($optionSpec['disabled'])) { $disabled = $optionSpec['disabled']; } if (ArrayUtils::inArray((string) $value, $stringSelectedOptions, true)) { $selected = true; } $label = $this->translateLabel($label); $attributes = [ 'value' => $value, 'selected' => $selected, 'disabled' => $disabled, ]; if (isset($optionSpec['attributes']) && is_array($optionSpec['attributes'])) { $attributes = array_merge($attributes, $optionSpec['attributes']); } $this->validTagAttributes = $this->validOptionAttributes; $optionStrings[] = sprintf( $template, $this->createAttributesString($attributes), $escapeHtml($label) ); } return implode("\n", $optionStrings); } /** * Render an optgroup * * See {@link renderOptions()} for the options specification. Basically, * an optgroup is simply an option that has an additional "options" key * with an array following the specification for renderOptions(). * * @param array $optgroup * @param array $selectedOptions */ public function renderOptgroup(array $optgroup, array $selectedOptions = []): string { $template = '%s'; $options = []; if (isset($optgroup['options']) && is_array($optgroup['options'])) { $options = $optgroup['options']; unset($optgroup['options']); } $this->validTagAttributes = $this->validOptgroupAttributes; $attributes = $this->createAttributesString($optgroup); if (! empty($attributes)) { $attributes = ' ' . $attributes; } return sprintf( $template, $attributes, $this->renderOptions($options, $selectedOptions) ); } /** * Ensure that the value is set appropriately * * If the element's value attribute is an array, but there is no multiple * attribute, or that attribute does not evaluate to true, then we have * a domain issue -- you cannot have multiple options selected unless the * multiple attribute is present and enabled. * * @param array $attributes * @return array * @throws Exception\DomainException */ protected function validateMultiValue(mixed $value, array $attributes): array { if (null === $value) { return []; } if (! is_array($value)) { return [$value]; } if (! isset($attributes['multiple']) || ! $attributes['multiple']) { throw new Exception\DomainException(sprintf( '%s does not allow specifying multiple selected values when the element does not have a multiple ' . 'attribute set to a boolean true', self::class )); } return $value; } protected function renderHiddenElement(SelectElement $element): string { $hiddenElement = new Hidden($element->getName()); $hiddenElement->setValue($element->getUnselectedValue()); return $this->getFormHiddenHelper()->__invoke($hiddenElement); } protected function getFormHiddenHelper(): FormHidden { if (null !== $this->formHiddenHelper) { return $this->formHiddenHelper; } if (null !== $this->view && method_exists($this->view, 'plugin')) { $this->formHiddenHelper = $this->view->plugin('formhidden'); } if (null === $this->formHiddenHelper) { $this->formHiddenHelper = new FormHidden(); } return $this->formHiddenHelper; } }