` tag with all or specific countries as options. * * ``` * * * ``` * * @see https://docs.typo3.org/permalink/t3viewhelper:typo3-fluid-form-countryselect */ final class CountrySelectViewHelper extends AbstractFormFieldViewHelper { /** * @var string */ protected $tagName = 'select'; public function __construct( private readonly CountryProvider $countryProvider ) { parent::__construct(); } public function initializeArguments(): void { parent::initializeArguments(); $this->registerArgument('excludeCountries', 'array', 'Array with country codes that should not be shown.', false, []); $this->registerArgument('onlyCountries', 'array', 'If set, only the country codes in the list are rendered.', false, []); $this->registerArgument('optionLabelField', 'string', 'If specified, will call the appropriate getter on each object to determine the label. Use "name", "localizedName", "officialName" or "localizedOfficialName"', false, 'localizedName'); $this->registerArgument('sortByOptionLabel', 'boolean', 'If true, List will be sorted by label.', false, false); $this->registerArgument('errorClass', 'string', 'CSS class to set if there are errors for this ViewHelper', false, 'f3-form-error'); $this->registerArgument('prependOptionLabel', 'string', 'If specified, will provide an option at first position with the specified label.'); $this->registerArgument('prependOptionValue', 'string', 'If specified, will provide an option at first position with the specified value.'); $this->registerArgument('multiple', 'boolean', 'If set multiple options may be selected.', false, false); $this->registerArgument('required', 'boolean', 'If set no empty value is allowed.', false, false); $this->registerArgument('prioritizedCountries', 'array', 'A list of country codes which should be listed on top of the list.', false, []); $this->registerArgument('alternativeLanguage', 'string', 'If specified, the country list will be shown in the given language.'); } public function render(): string { if ($this->arguments['required']) { $this->tag->addAttribute('required', 'required'); } $name = $this->getName(); if ($this->arguments['multiple']) { $this->tag->addAttribute('multiple', 'multiple'); $name .= '[]'; } $this->addAdditionalIdentityPropertiesIfNeeded(); $this->setErrorClassAttribute(); $this->registerFieldNameForFormTokenGeneration($name); $this->setRespectSubmittedDataValue(true); $this->tag->addAttribute('name', $name); $validCountries = $this->getCountryList(); $options = $this->createOptions($validCountries); $selectedValue = $this->getValueAttribute(); $tagContent = $this->renderPrependOptionTag(); foreach ($options as $value => $label) { $tagContent .= $this->renderOptionTag($value, $label, $value === $selectedValue); } $this->tag->forceClosingTag(true); $this->tag->setContent($tagContent); return $this->tag->render(); } /** * @param Country[] $countries * @return array */ private function createOptions(array $countries): array { $options = []; foreach ($countries as $code => $country) { switch ($this->arguments['optionLabelField']) { case 'localizedName': $options[$code] = $this->translate($country->getLocalizedNameLabel()); break; case 'name': $options[$code] = $country->getName(); break; case 'officialName': $options[$code] = $country->getOfficialName(); break; case 'localizedOfficialName': $name = $this->translate($country->getLocalizedOfficialNameLabel()); if (!$name) { $name = $this->translate($country->getLocalizedNameLabel()); } $options[$code] = $name; break; default: throw new InvalidArgumentValueException('Argument "optionLabelField" of must either be set to "localizedName", "name", "officialName", or "localizedOfficialName".', 1674076708); } } if ($this->arguments['sortByOptionLabel']) { asort($options, SORT_LOCALE_STRING); } else { ksort($options, SORT_NATURAL); } if (($this->arguments['prioritizedCountries'] ?? []) !== []) { $finalOptions = []; foreach ($this->arguments['prioritizedCountries'] as $countryCode) { if (isset($options[$countryCode])) { $label = $options[$countryCode]; $finalOptions[$countryCode] = $label; unset($options[$countryCode]); } } foreach ($options as $countryCode => $label) { $finalOptions[$countryCode] = $label; } $options = $finalOptions; } return $options; } private function translate(string $label): string { if ($this->arguments['alternativeLanguage']) { return (string)LocalizationUtility::translate($label, null, null, $this->arguments['alternativeLanguage']); } return (string)LocalizationUtility::translate($label); } /** * Render prepended option tag */ private function renderPrependOptionTag(): string { if ($this->hasArgument('prependOptionLabel')) { $value = $this->hasArgument('prependOptionValue') ? $this->arguments['prependOptionValue'] : ''; $label = $this->arguments['prependOptionLabel']; return $this->renderOptionTag((string)$value, (string)$label, false) . LF; } return ''; } /** * Render one option tag * * @param string $value value attribute of the option tag (will be escaped) * @param string $label content of the option tag (will be escaped) * @param bool $isSelected specifies whether to add selected attribute * @return string the rendered option tag */ private function renderOptionTag(string $value, string $label, bool $isSelected): string { $output = ''; return $output; } /** * @return Country[] */ private function getCountryList(): array { $filter = new CountryFilter(); $filter->setOnlyCountries($this->arguments['onlyCountries'] ?? []) ->setExcludeCountries($this->arguments['excludeCountries'] ?? []); return $this->countryProvider->getFiltered($filter); } /** * Converts an arbitrary value to a plain value. * Evaluates possible direct "Country" type properties. * * @param mixed $value The value to convert * @return mixed */ protected function convertToPlainValue($value) { if ($value instanceof Country) { return $value->getAlpha2IsoCode(); } return parent::convertToPlainValue($value); } }