TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Fluid\ViewHelpers\Form;
|
||||
|
||||
use TYPO3\CMS\Core\Country\Country;
|
||||
use TYPO3\CMS\Core\Country\CountryFilter;
|
||||
use TYPO3\CMS\Core\Country\CountryProvider;
|
||||
use TYPO3\CMS\Extbase\Utility\LocalizationUtility;
|
||||
use TYPO3Fluid\Fluid\Core\ViewHelper\InvalidArgumentValueException;
|
||||
|
||||
/**
|
||||
* ViewHelper which renders a `<select>` tag with all or specific countries as options.
|
||||
*
|
||||
* ```
|
||||
* <f:form.countrySelect name="country" value="AT" />
|
||||
* <f:form.countrySelect name="country" value="DE"
|
||||
* optionLabelField="localizedOfficialName"
|
||||
* prioritizedCountries="{0: 'DE', 1: 'AT', 2: 'CH'}"
|
||||
* alternativeLanguage="fr"
|
||||
* sortByOptionLabel="true"
|
||||
* />
|
||||
* ```
|
||||
*
|
||||
* @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<string, string>
|
||||
*/
|
||||
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 <f:form.countrySelect> 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 = '<option value="' . htmlspecialchars($value) . '"';
|
||||
if ($isSelected) {
|
||||
$output .= ' selected="selected"';
|
||||
}
|
||||
if (($this->arguments['prioritizedCountries'] ?? []) !== []
|
||||
&& in_array($value, $this->arguments['prioritizedCountries'], true)
|
||||
) {
|
||||
$output .= ' data-prioritized="1"';
|
||||
}
|
||||
$output .= '>' . htmlspecialchars($label) . '</option>';
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user