From 2d913cc83152306a55bf3a949f6a14714a05c154 Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Mon, 29 Jan 2024 15:21:37 +0000 Subject: [PATCH 01/10] language menu with env val support (WIP) --- src/config/app.php | 2 +- src/controllers/SitesController.php | 17 ---- src/controllers/UsersController.php | 21 +---- src/migrations/Install.php | 2 +- ...129_150719_sites_language_amend_length.php | 34 +++++++ src/models/Site.php | 37 ++++++-- src/templates/_includes/forms.twig | 18 ++++ .../_includes/forms/languageMenu.twig | 13 +++ src/templates/settings/sites/_edit.twig | 7 +- src/templates/users/_preferences.twig | 5 +- src/translations/en/app.php | 1 + src/web/twig/variables/Cp.php | 91 +++++++++++++++++++ 12 files changed, 198 insertions(+), 50 deletions(-) create mode 100644 src/migrations/m240129_150719_sites_language_amend_length.php create mode 100644 src/templates/_includes/forms/languageMenu.twig diff --git a/src/config/app.php b/src/config/app.php index 1293bb2d184..ad44a7c91c7 100644 --- a/src/config/app.php +++ b/src/config/app.php @@ -4,7 +4,7 @@ 'id' => 'CraftCMS', 'name' => 'Craft CMS', 'version' => '5.0.0-alpha.8', - 'schemaVersion' => '5.0.0.16', + 'schemaVersion' => '5.0.0.17', 'minVersionRequired' => '4.4.0', 'basePath' => dirname(__DIR__), // Defines the @app alias 'runtimePath' => '@storage/runtime', // Defines the @runtime alias diff --git a/src/controllers/SitesController.php b/src/controllers/SitesController.php index 057ed5f5300..10bfa63c5a2 100644 --- a/src/controllers/SitesController.php +++ b/src/controllers/SitesController.php @@ -266,22 +266,6 @@ public function actionEditSite(?int $siteId = null, ?Site $siteModel = null, ?in ], ]; - $languageOptions = []; - $languageId = Craft::$app->getLocale()->getLanguageID(); - - foreach (Craft::$app->getI18n()->getAllLocales() as $locale) { - $languageOptions[] = [ - 'label' => $locale->getDisplayName(Craft::$app->language), - 'value' => $locale->id, - 'data' => [ - 'data' => [ - 'hint' => $locale->id, - 'keywords' => $locale->getLanguageID() !== $languageId ? $locale->getDisplayName() : false, - ], - ], - ]; - } - return $this->renderTemplate('settings/sites/_edit.twig', [ 'brandNewSite' => $brandNewSite, 'title' => $title, @@ -289,7 +273,6 @@ public function actionEditSite(?int $siteId = null, ?Site $siteModel = null, ?in 'site' => $siteModel, 'groupId' => $groupId, 'groupOptions' => $groupOptions, - 'languageOptions' => $languageOptions, ]); } diff --git a/src/controllers/UsersController.php b/src/controllers/UsersController.php index fba19005551..e7798c59cf1 100644 --- a/src/controllers/UsersController.php +++ b/src/controllers/UsersController.php @@ -1083,29 +1083,11 @@ public function actionPreferences(): Response $userLanguage = Craft::$app->language; } - // Formatting Locale - $allLocales = $i18n->getAllLocales(); - ArrayHelper::multisort($allLocales, fn(Locale $locale) => $locale->getDisplayName()); - - $localeOptions = [ - ['label' => Craft::t('app', 'Same as language'), 'value' => ''], - ]; - array_push($localeOptions, ...array_map(fn(Locale $locale) => [ - 'label' => $locale->getDisplayName(Craft::$app->language), - 'value' => $locale->id, - 'data' => [ - 'data' => [ - 'hint' => $locale->getLanguageID() !== $languageId ? $locale->getDisplayName() : false, - 'hintLang' => $locale->id, - ], - ], - ], $allLocales)); - $userLocale = $user->getPreferredLocale(); if ( !$userLocale || - !ArrayHelper::contains($allLocales, fn(Locale $locale) => $locale->id === $userLocale) + !ArrayHelper::contains($i18n->getAllLocales(), fn(Locale $locale) => $locale->id === $userLocale) ) { $userLocale = Craft::$app->getConfig()->getGeneral()->defaultCpLocale; } @@ -1113,7 +1095,6 @@ public function actionPreferences(): Response $response->action('users/save-preferences'); $response->contentTemplate('users/_preferences', compact( 'languageOptions', - 'localeOptions', 'userLanguage', 'userLocale', )); diff --git a/src/migrations/Install.php b/src/migrations/Install.php index 9784428d217..84f383a1c27 100644 --- a/src/migrations/Install.php +++ b/src/migrations/Install.php @@ -606,7 +606,7 @@ public function createTables(): void 'enabled' => $this->string()->notNull()->defaultValue('true'), 'name' => $this->string()->notNull(), 'handle' => $this->string()->notNull(), - 'language' => $this->string(12)->notNull(), + 'language' => $this->string()->notNull(), 'hasUrls' => $this->boolean()->defaultValue(false)->notNull(), 'baseUrl' => $this->string(), 'sortOrder' => $this->smallInteger()->unsigned(), diff --git a/src/migrations/m240129_150719_sites_language_amend_length.php b/src/migrations/m240129_150719_sites_language_amend_length.php new file mode 100644 index 00000000000..88b489e0973 --- /dev/null +++ b/src/migrations/m240129_150719_sites_language_amend_length.php @@ -0,0 +1,34 @@ +getDb()->tableExists(Table::SITES)) { + $this->alterColumn(Table::SITES, 'language', $this->string()->notNull()); + } + + return true; + } + + /** + * @inheritdoc + */ + public function safeDown(): bool + { + echo "m240129_150719_sites_language_amend_length cannot be reverted.\n"; + return false; + } +} diff --git a/src/models/Site.php b/src/models/Site.php index 62190df36d5..2ed6ffae4c4 100644 --- a/src/models/Site.php +++ b/src/models/Site.php @@ -46,11 +46,6 @@ class Site extends Model */ public ?string $handle = null; - /** - * @var string|null Name - */ - public ?string $language = null; - /** * @var bool Primary site? */ @@ -102,6 +97,13 @@ class Site extends Model */ private bool|string $_enabled = true; + /** + * @var string|null Language + * @see getLanguage() + * @see setLanguage() + */ + private ?string $_language = null; + /** * Returns the site’s name. * @@ -182,6 +184,29 @@ public function setEnabled(bool|string $name): void $this->_enabled = $name; } + /** + * Returns the site’s language. + * + * @param bool $parse Whether to parse the language for an environment variable + * @return string + * @since 5.0.0 + */ + public function getLanguage(bool $parse = true): string + { + return ($parse ? App::parseEnv($this->_language) : $this->_language) ?? ''; + } + + /** + * Sets the site’s language. + * + * @param string $language + * @since 5.0.0 + */ + public function setLanguage(string $language): void + { + $this->_language = $language; + } + /** * @inheritdoc */ @@ -299,7 +324,7 @@ public function getConfig(): array 'siteGroup' => $this->getGroup()->uid, 'name' => $this->_name, 'handle' => $this->handle, - 'language' => $this->language, + 'language' => $this->getLanguage(false), 'hasUrls' => $this->hasUrls, 'baseUrl' => $this->_baseUrl ?: null, 'sortOrder' => $this->sortOrder, diff --git a/src/templates/_includes/forms.twig b/src/templates/_includes/forms.twig index 89a40319264..670d1739562 100644 --- a/src/templates/_includes/forms.twig +++ b/src/templates/_includes/forms.twig @@ -168,6 +168,11 @@ {% endmacro %} +{% macro languageMenu(config) %} + {% include "_includes/forms/languageMenu" with config only %} +{% endmacro %} + + {% macro fieldLayoutDesigner(config) %} {% include "_includes/forms/fieldLayoutDesigner" with config only %} {% endmacro %} @@ -434,6 +439,19 @@ {% endmacro %} +{% macro languageMenuField(config) %} + {% set config = config|merge({id: config.id ?? "languagemenu#{random()}"}) %} + {% if (config.includeEnvVars ?? false) and config.tip is not defined and (config.value ?? '')[0:1] != '$' %} + {% set config = config|merge({ + tip: 'This can be set to an environment variable with a valid language ID ({examples}).'|t('app', { + examples: '`en`/`en-GB`', + }), + }) %} + {% endif %} + {{ _self.field(config, 'template:_includes/forms/languageMenu') }} +{% endmacro %} + + {% macro fieldLayoutDesignerField(config) %} {{ _self.field({ label: 'Field Layout'|t('app'), diff --git a/src/templates/_includes/forms/languageMenu.twig b/src/templates/_includes/forms/languageMenu.twig new file mode 100644 index 00000000000..32a7c3b51b6 --- /dev/null +++ b/src/templates/_includes/forms/languageMenu.twig @@ -0,0 +1,13 @@ +{% set id = id ?? "languagemenu#{random()}" %} +{% set value = value ?? null -%} +{% set options = options ?? [] %} + + +{% if includeEnvVars ?? false %} + {% set options = options|merge(craft.cp.getLanguageEnvOptions()) %} +{% endif %} + +{% include '_includes/forms/selectize' with { + includeEnvVars: false, + value: value, +}%} diff --git a/src/templates/settings/sites/_edit.twig b/src/templates/settings/sites/_edit.twig index 1f12b240c53..31046f6fe16 100644 --- a/src/templates/settings/sites/_edit.twig +++ b/src/templates/settings/sites/_edit.twig @@ -57,14 +57,15 @@ required: true }) }} - {{ forms.selectizeField({ + {{ forms.languageMenuField({ label: "Language"|t('app'), instructions: "The language content in this site will use."|t('app'), id: 'language', name: 'language', - value: site.language, - options: languageOptions, + value: site.getLanguage(false), + options: craft.cp.getLanguageOptions(), errors: site.getErrors('language'), + includeEnvVars: true, }) }} {% if (craft.app.isMultiSite or not site.id) %} diff --git a/src/templates/users/_preferences.twig b/src/templates/users/_preferences.twig index cc1c132b5af..89decd506a2 100644 --- a/src/templates/users/_preferences.twig +++ b/src/templates/users/_preferences.twig @@ -12,13 +12,14 @@ value: userLanguage, }) }} - {{ forms.selectizeField({ + {{ forms.languageMenuField({ id: 'preferredLocale', name: 'preferredLocale', label: 'Formatting Locale'|t('app'), instructions: 'The locale that should be used for date and number formatting.'|t('app'), - options: localeOptions, + options: [{'label' : 'Same as language'|t('app'), 'value' : ''}]|merge(craft.cp.getLanguageOptions(false, true, true)), value: userLocale, + includeEnvVars: true, }) }} {{ forms.selectField({ diff --git a/src/translations/en/app.php b/src/translations/en/app.php index 88dd6232ff7..25d4066689e 100644 --- a/src/translations/en/app.php +++ b/src/translations/en/app.php @@ -1606,6 +1606,7 @@ 'This can be left blank if you just want an unlabeled separator.' => 'This can be left blank if you just want an unlabeled separator.', 'This can be set to an environment variable matching one of the option values.' => 'This can be set to an environment variable matching one of the option values.', 'This can be set to an environment variable with a boolean value ({examples}).' => 'This can be set to an environment variable with a boolean value ({examples}).', + 'This can be set to an environment variable with a valid language ID ({examples}).' => 'This can be set to an environment variable with a valid language ID ({examples}).', 'This can be set to an environment variable with a value of a [supported time zone]({url}).' => 'This can be set to an environment variable with a value of a [supported time zone]({url}).', 'This can be set to an environment variable, or a Twig template that outputs an ID.' => 'This can be set to an environment variable, or a Twig template that outputs an ID.', 'This can be set to an environment variable, or begin with an alias.' => 'This can be set to an environment variable, or begin with an alias.', diff --git a/src/web/twig/variables/Cp.php b/src/web/twig/variables/Cp.php index e0c67b7a10e..93e13554b27 100644 --- a/src/web/twig/variables/Cp.php +++ b/src/web/twig/variables/Cp.php @@ -21,6 +21,7 @@ use craft\helpers\Cp as CpHelper; use craft\helpers\StringHelper; use craft\helpers\UrlHelper; +use craft\i18n\Locale; use craft\models\FieldLayout; use craft\models\Site; use craft\models\Volume; @@ -752,6 +753,48 @@ public function getBooleanEnvOptions(): array return $this->_envOptions($options); } + + + /** + * Returns environment variable options for a language menu. + * + * @return array + * @since 5.0.0 + */ + public function getLanguageEnvOptions(): array + { + $options = []; + $allLanguages = array_map(fn(Locale $locale) => $locale->id, Craft::$app->getI18n()->getAllLocales()); + + foreach (array_keys($_SERVER) as $var) { + if (!is_string($var)) { + continue; + } + $value = App::env($var); + if ($value === null || $value === '') { + continue; + } + +// $booleanValue = is_bool($value) ? $value : filter_var($value, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE); + $languageValue = null; + if (in_array($value, $allLanguages, true)) { + $languageValue = $value; + } + + if ($languageValue !== null) { + $options[] = [ + 'label' => "$$var", + 'value' => "$$var", + 'data' => [ + 'hint' => $languageValue, + ], + ]; + } + } + + return $this->_envOptions($options); + } + /** * @param array $options * @return array @@ -825,6 +868,54 @@ public function getTimeZoneOptions(): array return $options; } + /** + * Returns all known language options for a language input. + * + * @return array + * @since 5.0.0 + */ + public function getLanguageOptions(bool $showLocaleIds = true, bool $showLocalizedNames = false, bool $sort = false): array + { + $options = []; + + $languageId = Craft::$app->getLocale()->getLanguageID(); + $allLocales = Craft::$app->getI18n()->getAllLocales(); + + if ($sort) { + ArrayHelper::multisort($allLocales, fn(Locale $locale) => $locale->getDisplayName()); + } + + foreach ($allLocales as $locale) { + $name = $locale->getLanguageID() !== $languageId ? $locale->getDisplayName() : ''; + $option = [ + 'label' => $locale->getDisplayName(Craft::$app->language), + 'value' => $locale->id, + 'data' => [ + 'data' => [ + 'keywords' => $name, + ], + ], + ]; + $data = []; + + if ($showLocaleIds) { + $data = [ + 'hint' => $locale->id, + ]; + } else if ($showLocalizedNames) { + $data = [ + 'hint' => $name, + 'hintLang' => $locale->id, + ]; + } + + $option['data']['data'] = $option['data']['data'] + $data; + $options[] = $option; + } + + return $options; + } + /** * Returns all options for a filesystem input. * From b08ebbec295f6d6d1cfe54fac5e8e493fb388fac Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Mon, 29 Jan 2024 17:44:11 +0000 Subject: [PATCH 02/10] updates for getting the user locale --- src/controllers/UsersController.php | 3 ++- src/elements/User.php | 5 +++-- src/helpers/App.php | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/controllers/UsersController.php b/src/controllers/UsersController.php index e7798c59cf1..e2a53f8f9dc 100644 --- a/src/controllers/UsersController.php +++ b/src/controllers/UsersController.php @@ -23,6 +23,7 @@ use craft\events\InvalidUserTokenEvent; use craft\events\LoginFailureEvent; use craft\events\UserEvent; +use craft\helpers\App; use craft\helpers\ArrayHelper; use craft\helpers\Assets; use craft\helpers\Cp; @@ -1087,7 +1088,7 @@ public function actionPreferences(): Response if ( !$userLocale || - !ArrayHelper::contains($i18n->getAllLocales(), fn(Locale $locale) => $locale->id === $userLocale) + !ArrayHelper::contains($i18n->getAllLocales(), fn(Locale $locale) => $locale->id === App::parseEnv($userLocale)) ) { $userLocale = Craft::$app->getConfig()->getGeneral()->defaultCpLocale; } diff --git a/src/elements/User.php b/src/elements/User.php index c5d5c68c25c..aaaf4364cdd 100644 --- a/src/elements/User.php +++ b/src/elements/User.php @@ -2025,7 +2025,8 @@ public function getPreferredLanguage(): ?string */ public function getPreferredLocale(): ?string { - return $this->_validateLocale($this->getPreference('locale'), true); + $locale = $this->getPreference('locale'); + return $this->_validateLocale(App::parseEnv($locale), true) ? $locale : null; } /** @@ -2111,7 +2112,7 @@ protected function attributeHtml(string $attribute): string case 'preferredLocale': $locale = $this->getPreferredLocale(); - return $locale ? Craft::$app->getI18n()->getLocaleById($locale)->getDisplayName(Craft::$app->language) : ''; + return $locale ? Craft::$app->getI18n()->getLocaleById(App::parseEnv($locale))->getDisplayName(Craft::$app->language) : ''; } return parent::attributeHtml($attribute); diff --git a/src/helpers/App.php b/src/helpers/App.php index 39272af35be..9112765895a 100644 --- a/src/helpers/App.php +++ b/src/helpers/App.php @@ -1212,7 +1212,7 @@ public static function createFormattingLocale(): Locale // If they have a preferred locale, use it $usersService = Craft::$app->getUsers(); if (($locale = $usersService->getUserPreference($id, 'locale')) !== null) { - return $i18n->getLocaleById($locale); + return $i18n->getLocaleById(App::parseEnv($locale)); } // Otherwise see if they have a preferred language From 64183223db328f6f34452f803f24ca142a7646c8 Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Tue, 30 Jan 2024 10:34:06 +0000 Subject: [PATCH 03/10] switched user language + bug fixes --- src/controllers/UsersController.php | 23 +++----------- src/elements/User.php | 13 +++++--- src/helpers/App.php | 2 +- src/models/Site.php | 1 + src/services/Users.php | 9 +++++- .../_includes/forms/languageMenu.twig | 8 ++--- src/templates/users/_preferences.twig | 6 ++-- src/web/twig/variables/Cp.php | 31 ++++++++++++++----- 8 files changed, 55 insertions(+), 38 deletions(-) diff --git a/src/controllers/UsersController.php b/src/controllers/UsersController.php index e2a53f8f9dc..3ae5703779d 100644 --- a/src/controllers/UsersController.php +++ b/src/controllers/UsersController.php @@ -1060,31 +1060,19 @@ public function actionPreferences(): Response $response = $this->asEditScreen($user, self::SCREEN_PREFERENCES); $i18n = Craft::$app->getI18n(); - $appLocales = $i18n->getAppLocales(); - ArrayHelper::multisort($appLocales, fn(Locale $locale) => $locale->getDisplayName()); - $languageId = Craft::$app->getLocale()->getLanguageID(); - - $languageOptions = array_map(fn(Locale $locale) => [ - 'label' => $locale->getDisplayName(Craft::$app->language), - 'value' => $locale->id, - 'data' => [ - 'data' => [ - 'hint' => $locale->getLanguageID() !== $languageId ? $locale->getDisplayName() : '', - 'hintLang' => $locale->id, - ], - ], - ], $appLocales); - $userLanguage = $user->getPreferredLanguage(); + // user language + $userLanguage = $user->getPreferredLanguage(false); if ( !$userLanguage || - !ArrayHelper::contains($appLocales, fn(Locale $locale) => $locale->id === $userLanguage) + !ArrayHelper::contains($i18n->getAppLocales(), fn(Locale $locale) => $locale->id === App::parseEnv($userLanguage)) ) { $userLanguage = Craft::$app->language; } - $userLocale = $user->getPreferredLocale(); + // user locale + $userLocale = $user->getPreferredLocale(false); if ( !$userLocale || @@ -1095,7 +1083,6 @@ public function actionPreferences(): Response $response->action('users/save-preferences'); $response->contentTemplate('users/_preferences', compact( - 'languageOptions', 'userLanguage', 'userLocale', )); diff --git a/src/elements/User.php b/src/elements/User.php index aaaf4364cdd..b1d245643d6 100644 --- a/src/elements/User.php +++ b/src/elements/User.php @@ -2010,9 +2010,11 @@ public function getPreference(string $key, mixed $default = null): mixed * * @return string|null The preferred language */ - public function getPreferredLanguage(): ?string + public function getPreferredLanguage(bool $parse = true): ?string { - return $this->_validateLocale($this->getPreference('language'), false); + $language = $this->getPreference('language'); + $parsedLanguage = App::parseEnv($language); + return $this->_validateLocale($parsedLanguage, false) ? ($parse ? $parsedLanguage : $language) : null; } /** @@ -2023,10 +2025,11 @@ public function getPreferredLanguage(): ?string * @return string|null The preferred locale * @since 3.5.0 */ - public function getPreferredLocale(): ?string + public function getPreferredLocale(bool $parse = true): ?string { $locale = $this->getPreference('locale'); - return $this->_validateLocale(App::parseEnv($locale), true) ? $locale : null; + $parsedLocale = App::parseEnv($locale); + return $this->_validateLocale($parsedLocale, true) ? ($parse ? $parsedLocale : $locale) : null; } /** @@ -2112,7 +2115,7 @@ protected function attributeHtml(string $attribute): string case 'preferredLocale': $locale = $this->getPreferredLocale(); - return $locale ? Craft::$app->getI18n()->getLocaleById(App::parseEnv($locale))->getDisplayName(Craft::$app->language) : ''; + return $locale ? Craft::$app->getI18n()->getLocaleById($locale)->getDisplayName(Craft::$app->language) : ''; } return parent::attributeHtml($attribute); diff --git a/src/helpers/App.php b/src/helpers/App.php index 9112765895a..39272af35be 100644 --- a/src/helpers/App.php +++ b/src/helpers/App.php @@ -1212,7 +1212,7 @@ public static function createFormattingLocale(): Locale // If they have a preferred locale, use it $usersService = Craft::$app->getUsers(); if (($locale = $usersService->getUserPreference($id, 'locale')) !== null) { - return $i18n->getLocaleById(App::parseEnv($locale)); + return $i18n->getLocaleById($locale); } // Otherwise see if they have a preferred language diff --git a/src/models/Site.php b/src/models/Site.php index 2ed6ffae4c4..d3310165229 100644 --- a/src/models/Site.php +++ b/src/models/Site.php @@ -26,6 +26,7 @@ * @property bool|string $enabled Enabled * @property string|null $baseUrl The site’s base URL * @property string $name The site’s name + * @property string $language The site’s language * @author Pixel & Tonic, Inc. * @since 3.0.0 */ diff --git a/src/services/Users.php b/src/services/Users.php index 269605df270..d1c68d46c32 100644 --- a/src/services/Users.php +++ b/src/services/Users.php @@ -23,6 +23,7 @@ use craft\events\UserEvent; use craft\events\UserGroupsAssignEvent; use craft\events\UserPhotoEvent; +use craft\helpers\App; use craft\helpers\Assets as AssetsHelper; use craft\helpers\DateTimeHelper; use craft\helpers\Db; @@ -402,11 +403,17 @@ public function saveUserPreferences(User $user, array $preferences): void * @param int $userId The user’s ID * @param string $key The preference’s key * @param mixed $default The default value, if the preference hasn’t been set + * @param bool $parse Whether to parse the value we got * @return mixed The user’s preference */ - public function getUserPreference(int $userId, string $key, mixed $default = null): mixed + public function getUserPreference(int $userId, string $key, mixed $default = null, bool $parse = true): mixed { $preferences = $this->getUserPreferences($userId); + + if ($preferences[$key] && $parse) { + return App::parseEnv($preferences[$key]); + } + return $preferences[$key] ?? $default; } diff --git a/src/templates/_includes/forms/languageMenu.twig b/src/templates/_includes/forms/languageMenu.twig index 32a7c3b51b6..d33209a9ae5 100644 --- a/src/templates/_includes/forms/languageMenu.twig +++ b/src/templates/_includes/forms/languageMenu.twig @@ -1,13 +1,13 @@ {% set id = id ?? "languagemenu#{random()}" %} {% set value = value ?? null -%} {% set options = options ?? [] %} +{% set appOnly = appOnly ?? false %} {% if includeEnvVars ?? false %} - {% set options = options|merge(craft.cp.getLanguageEnvOptions()) %} + {% set options = options|merge(craft.cp.getLanguageEnvOptions(appOnly)) %} {% endif %} -{% include '_includes/forms/selectize' with { +{% include '_includes/forms/selectize' with { includeEnvVars: false, - value: value, -}%} +} %} diff --git a/src/templates/users/_preferences.twig b/src/templates/users/_preferences.twig index 89decd506a2..78314fe4ebd 100644 --- a/src/templates/users/_preferences.twig +++ b/src/templates/users/_preferences.twig @@ -3,13 +3,15 @@

{{ 'General'|t('app') }}

- {{ forms.selectizeField({ + {{ forms.languageMenuField({ id: 'preferredLanguage', name: 'preferredLanguage', label: 'Language'|t('app'), instructions: 'The language that the control panel should use.'|t('app'), - options: languageOptions, + options: craft.cp.getLanguageOptions(false, true, true, true), value: userLanguage, + includeEnvVars: true, + appOnly: true, }) }} {{ forms.languageMenuField({ diff --git a/src/web/twig/variables/Cp.php b/src/web/twig/variables/Cp.php index 93e13554b27..de5324c5c91 100644 --- a/src/web/twig/variables/Cp.php +++ b/src/web/twig/variables/Cp.php @@ -758,13 +758,18 @@ public function getBooleanEnvOptions(): array /** * Returns environment variable options for a language menu. * + * @param bool $appOnly Whether to limit the env options to those that match available app locales * @return array * @since 5.0.0 */ - public function getLanguageEnvOptions(): array + public function getLanguageEnvOptions(bool $appOnly = false): array { $options = []; - $allLanguages = array_map(fn(Locale $locale) => $locale->id, Craft::$app->getI18n()->getAllLocales()); + if ($appOnly) { + $allLanguages = array_map(fn(Locale $locale) => $locale->id, Craft::$app->getI18n()->getAppLocales()); + } else { + $allLanguages = array_map(fn(Locale $locale) => $locale->id, Craft::$app->getI18n()->getAllLocales()); + } foreach (array_keys($_SERVER) as $var) { if (!is_string($var)) { @@ -775,7 +780,6 @@ public function getLanguageEnvOptions(): array continue; } -// $booleanValue = is_bool($value) ? $value : filter_var($value, FILTER_VALIDATE_BOOL, FILTER_NULL_ON_FAILURE); $languageValue = null; if (in_array($value, $allLanguages, true)) { $languageValue = $value; @@ -871,15 +875,28 @@ public function getTimeZoneOptions(): array /** * Returns all known language options for a language input. * + * @param bool $showLocaleIds Whether to show the hint as locale id; e.g. en, en-GB + * @param bool $showLocalizedNames Whether to show the hint as localizes names; e.g. English, English (United Kingdom) + * @param bool $sort Whether to sort the locales by their display name + * @param bool $appLocales Whether to limit the returned locales to just app locales (cp translation options) or show them all * @return array * @since 5.0.0 */ - public function getLanguageOptions(bool $showLocaleIds = true, bool $showLocalizedNames = false, bool $sort = false): array - { + public function getLanguageOptions( + bool $showLocaleIds = true, + bool $showLocalizedNames = false, + bool $sort = false, + bool $appLocales = false, + ): array { $options = []; $languageId = Craft::$app->getLocale()->getLanguageID(); - $allLocales = Craft::$app->getI18n()->getAllLocales(); + + if ($appLocales) { + $allLocales = Craft::$app->getI18n()->getAppLocales(); + } else { + $allLocales = Craft::$app->getI18n()->getAllLocales(); + } if ($sort) { ArrayHelper::multisort($allLocales, fn(Locale $locale) => $locale->getDisplayName()); @@ -902,7 +919,7 @@ public function getLanguageOptions(bool $showLocaleIds = true, bool $showLocaliz $data = [ 'hint' => $locale->id, ]; - } else if ($showLocalizedNames) { + } elseif ($showLocalizedNames) { $data = [ 'hint' => $name, 'hintLang' => $locale->id, From c6757b947d85a22e71d34defc29f1d8e36fd50d4 Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Tue, 30 Jan 2024 10:42:58 +0000 Subject: [PATCH 04/10] tweak --- src/config/app.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config/app.php b/src/config/app.php index f84aa53a509..d95f3725db8 100644 --- a/src/config/app.php +++ b/src/config/app.php @@ -3,8 +3,8 @@ return [ 'id' => 'CraftCMS', 'name' => 'Craft CMS', - 'version' => '5.0.0-alpha.8', - 'schemaVersion' => '5.0.0.18', + 'version' => '5.0.0-alpha.9', + 'schemaVersion' => '5.0.0.17', 'minVersionRequired' => '4.4.0', 'basePath' => dirname(__DIR__), // Defines the @app alias 'runtimePath' => '@storage/runtime', // Defines the @runtime alias From f98a2093b9513624cdfaea0f8823d93e33779abd Mon Sep 17 00:00:00 2001 From: Iwona Just Date: Tue, 30 Jan 2024 11:14:10 +0000 Subject: [PATCH 05/10] doh --- src/services/Users.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/services/Users.php b/src/services/Users.php index d1c68d46c32..4c9ab35b2a4 100644 --- a/src/services/Users.php +++ b/src/services/Users.php @@ -410,7 +410,7 @@ public function getUserPreference(int $userId, string $key, mixed $default = nul { $preferences = $this->getUserPreferences($userId); - if ($preferences[$key] && $parse) { + if (isset($preferences[$key]) && $parse) { return App::parseEnv($preferences[$key]); } From e8d6fc0c3dfe4d007eea34fc5738ffc5904b7228 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Tue, 30 Jan 2024 08:52:56 -0800 Subject: [PATCH 06/10] Drop env support for user prefs --- src/controllers/UsersController.php | 4 ++-- src/elements/User.php | 12 ++++-------- src/templates/users/_preferences.twig | 2 -- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/controllers/UsersController.php b/src/controllers/UsersController.php index 3ae5703779d..9d78e4bd2ce 100644 --- a/src/controllers/UsersController.php +++ b/src/controllers/UsersController.php @@ -1062,7 +1062,7 @@ public function actionPreferences(): Response $i18n = Craft::$app->getI18n(); // user language - $userLanguage = $user->getPreferredLanguage(false); + $userLanguage = $user->getPreferredLanguage(); if ( !$userLanguage || @@ -1072,7 +1072,7 @@ public function actionPreferences(): Response } // user locale - $userLocale = $user->getPreferredLocale(false); + $userLocale = $user->getPreferredLocale(); if ( !$userLocale || diff --git a/src/elements/User.php b/src/elements/User.php index b1d245643d6..c5d5c68c25c 100644 --- a/src/elements/User.php +++ b/src/elements/User.php @@ -2010,11 +2010,9 @@ public function getPreference(string $key, mixed $default = null): mixed * * @return string|null The preferred language */ - public function getPreferredLanguage(bool $parse = true): ?string + public function getPreferredLanguage(): ?string { - $language = $this->getPreference('language'); - $parsedLanguage = App::parseEnv($language); - return $this->_validateLocale($parsedLanguage, false) ? ($parse ? $parsedLanguage : $language) : null; + return $this->_validateLocale($this->getPreference('language'), false); } /** @@ -2025,11 +2023,9 @@ public function getPreferredLanguage(bool $parse = true): ?string * @return string|null The preferred locale * @since 3.5.0 */ - public function getPreferredLocale(bool $parse = true): ?string + public function getPreferredLocale(): ?string { - $locale = $this->getPreference('locale'); - $parsedLocale = App::parseEnv($locale); - return $this->_validateLocale($parsedLocale, true) ? ($parse ? $parsedLocale : $locale) : null; + return $this->_validateLocale($this->getPreference('locale'), true); } /** diff --git a/src/templates/users/_preferences.twig b/src/templates/users/_preferences.twig index 78314fe4ebd..e68aae3e3df 100644 --- a/src/templates/users/_preferences.twig +++ b/src/templates/users/_preferences.twig @@ -10,7 +10,6 @@ instructions: 'The language that the control panel should use.'|t('app'), options: craft.cp.getLanguageOptions(false, true, true, true), value: userLanguage, - includeEnvVars: true, appOnly: true, }) }} @@ -21,7 +20,6 @@ instructions: 'The locale that should be used for date and number formatting.'|t('app'), options: [{'label' : 'Same as language'|t('app'), 'value' : ''}]|merge(craft.cp.getLanguageOptions(false, true, true)), value: userLocale, - includeEnvVars: true, }) }} {{ forms.selectField({ From 67faa3108bdbdd88d53d4fbff2e87040076ef6ef Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Tue, 30 Jan 2024 09:01:31 -0800 Subject: [PATCH 07/10] Drop $parse arg from getUserPreference() --- src/services/Users.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/services/Users.php b/src/services/Users.php index 4c9ab35b2a4..269605df270 100644 --- a/src/services/Users.php +++ b/src/services/Users.php @@ -23,7 +23,6 @@ use craft\events\UserEvent; use craft\events\UserGroupsAssignEvent; use craft\events\UserPhotoEvent; -use craft\helpers\App; use craft\helpers\Assets as AssetsHelper; use craft\helpers\DateTimeHelper; use craft\helpers\Db; @@ -403,17 +402,11 @@ public function saveUserPreferences(User $user, array $preferences): void * @param int $userId The user’s ID * @param string $key The preference’s key * @param mixed $default The default value, if the preference hasn’t been set - * @param bool $parse Whether to parse the value we got * @return mixed The user’s preference */ - public function getUserPreference(int $userId, string $key, mixed $default = null, bool $parse = true): mixed + public function getUserPreference(int $userId, string $key, mixed $default = null): mixed { $preferences = $this->getUserPreferences($userId); - - if (isset($preferences[$key]) && $parse) { - return App::parseEnv($preferences[$key]); - } - return $preferences[$key] ?? $default; } From a3d454f53bcaa29d4582a6ac62c2cd967ad44b5f Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Tue, 30 Jan 2024 09:17:06 -0800 Subject: [PATCH 08/10] getLanguageOptions() cleanup --- src/templates/settings/sites/_edit.twig | 2 +- src/templates/users/_preferences.twig | 4 ++-- src/web/twig/variables/Cp.php | 27 ++++++++++--------------- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/templates/settings/sites/_edit.twig b/src/templates/settings/sites/_edit.twig index 31046f6fe16..d29a4c40913 100644 --- a/src/templates/settings/sites/_edit.twig +++ b/src/templates/settings/sites/_edit.twig @@ -63,7 +63,7 @@ id: 'language', name: 'language', value: site.getLanguage(false), - options: craft.cp.getLanguageOptions(), + options: craft.cp.getLanguageOptions(true), errors: site.getErrors('language'), includeEnvVars: true, }) }} diff --git a/src/templates/users/_preferences.twig b/src/templates/users/_preferences.twig index e68aae3e3df..7baf72f6517 100644 --- a/src/templates/users/_preferences.twig +++ b/src/templates/users/_preferences.twig @@ -8,7 +8,7 @@ name: 'preferredLanguage', label: 'Language'|t('app'), instructions: 'The language that the control panel should use.'|t('app'), - options: craft.cp.getLanguageOptions(false, true, true, true), + options: craft.cp.getLanguageOptions(false, true, true), value: userLanguage, appOnly: true, }) }} @@ -18,7 +18,7 @@ name: 'preferredLocale', label: 'Formatting Locale'|t('app'), instructions: 'The locale that should be used for date and number formatting.'|t('app'), - options: [{'label' : 'Same as language'|t('app'), 'value' : ''}]|merge(craft.cp.getLanguageOptions(false, true, true)), + options: [{'label' : 'Same as language'|t('app'), 'value' : ''}]|merge(craft.cp.getLanguageOptions(false, true)), value: userLocale, }) }} diff --git a/src/web/twig/variables/Cp.php b/src/web/twig/variables/Cp.php index de5324c5c91..f28350b4cb8 100644 --- a/src/web/twig/variables/Cp.php +++ b/src/web/twig/variables/Cp.php @@ -877,15 +877,13 @@ public function getTimeZoneOptions(): array * * @param bool $showLocaleIds Whether to show the hint as locale id; e.g. en, en-GB * @param bool $showLocalizedNames Whether to show the hint as localizes names; e.g. English, English (United Kingdom) - * @param bool $sort Whether to sort the locales by their display name * @param bool $appLocales Whether to limit the returned locales to just app locales (cp translation options) or show them all * @return array * @since 5.0.0 */ public function getLanguageOptions( - bool $showLocaleIds = true, + bool $showLocaleIds = false, bool $showLocalizedNames = false, - bool $sort = false, bool $appLocales = false, ): array { $options = []; @@ -898,9 +896,7 @@ public function getLanguageOptions( $allLocales = Craft::$app->getI18n()->getAllLocales(); } - if ($sort) { - ArrayHelper::multisort($allLocales, fn(Locale $locale) => $locale->getDisplayName()); - } + ArrayHelper::multisort($allLocales, fn(Locale $locale) => $locale->getDisplayName()); foreach ($allLocales as $locale) { $name = $locale->getLanguageID() !== $languageId ? $locale->getDisplayName() : ''; @@ -913,20 +909,19 @@ public function getLanguageOptions( ], ], ]; - $data = []; + $hints = []; if ($showLocaleIds) { - $data = [ - 'hint' => $locale->id, - ]; - } elseif ($showLocalizedNames) { - $data = [ - 'hint' => $name, - 'hintLang' => $locale->id, - ]; + $hints[] = $locale->id; + } + if ($showLocalizedNames) { + $hints[] = $name; + $option['data']['data']['hintLang'] = $locale->id; + } + if (!empty($hints)) { + $option['data']['data']['hint'] = implode(', ', $hints); } - $option['data']['data'] = $option['data']['data'] + $data; $options[] = $option; } From 374709e3788e43cbcae286fd50e187f364f70892 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Tue, 30 Jan 2024 09:18:09 -0800 Subject: [PATCH 09/10] Extra newlines [ci skip] --- src/web/twig/variables/Cp.php | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/web/twig/variables/Cp.php b/src/web/twig/variables/Cp.php index f28350b4cb8..ceb5a199605 100644 --- a/src/web/twig/variables/Cp.php +++ b/src/web/twig/variables/Cp.php @@ -753,8 +753,6 @@ public function getBooleanEnvOptions(): array return $this->_envOptions($options); } - - /** * Returns environment variable options for a language menu. * From b9eb22d1861ef57b3a6dda10ad8f89f1cf1ff7ef Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Tue, 30 Jan 2024 09:34:58 -0800 Subject: [PATCH 10/10] Release notes [ci skip] --- CHANGELOG-WIP.md | 5 ++++- CHANGELOG.md | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index 81ceddf9ca4..f820c9e6770 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -66,6 +66,7 @@ - Entry types are now managed independently of sections. - Entry types are no longer required to have a Title Format, if the Title field isn’t shown. - Entry types now have a “Show the Slug field” setting. ([#13799](https://github.com/craftcms/cms/discussions/13799)) +- Sites’ Language settings can now be set to environment variables. ([#14235](https://github.com/craftcms/cms/pull/14235), [#14135](https://github.com/craftcms/cms/discussions/14135)) - Added the “Addresses” field type. ([#11438](https://github.com/craftcms/cms/discussions/11438)) - Matrix fields now manage nested entries, rather than Matrix blocks. During the upgrade, existing Matrix block types will be converted to entry types; their nested fields will be made global; and Matrix blocks will be converted to entries. - Matrix fields now have “Entry URI Format” and “Template” settings for each site. @@ -110,7 +111,7 @@ - Migrations that modify the project config no longer need to worry about whether the same changes were already applied to the incoming project config YAML files. - Selectize menus no longer apply special styling to options with the value `new`. The `_includes/forms/selectize.twig` control panel template should be used instead (or `craft\helpers\Cp::selectizeHtml()`/`selectizeFieldHtml()`), which will append an styled “Add” option when `addOptionFn` and `addOptionLabel` settings are passed. ([#11946](https://github.com/craftcms/cms/issues/11946)) - Added the `chip()`, `customSelect()`, `disclosureMenu()`, `elementCard()`, `elementChip()`, `elementIndex()`, `iconSvg()`, and `siteMenuItems()` global functions for control panel templates. -- Added the `colorSelect` and `colorSelectField` form macros. +- Added the `colorSelect`, `colorSelectField`, `languageMenu`, and `languageMenuField` form macros. - The `assets/move-asset` and `assets/move-folder` actions no longer include `success` keys in responses. ([#12159](https://github.com/craftcms/cms/pull/12159)) - The `assets/upload` controller action now includes `errors` object in failure responses. ([#12159](https://github.com/craftcms/cms/pull/12159)) - Element action triggers’ `validateSelection()` and `activate()` methods are now passed an `elementIndex` argument, with a reference to the trigger’s corresponding element index. @@ -293,6 +294,8 @@ - Added `craft\models\FieldLayout::getThumbField()`. - Added `craft\models\FsListing::getAdjustedUri()`. - Added `craft\models\Section::getCpEditUrl()`. +- Added `craft\models\Site::getLanguage()`. +- Added `craft\models\Site::setLanguage()`. - Added `craft\models\Volume::$altTranslationKeyFormat`. - Added `craft\models\Volume::$altTranslationMethod`. - Added `craft\models\Volume::getSubpath()`. diff --git a/CHANGELOG.md b/CHANGELOG.md index ef06b43263c..f1cf56a02df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Release Notes for Craft CMS 5 +## Unreleased + +- Sites’ Language settings can now be set to environment variables. ([#14235](https://github.com/craftcms/cms/pull/14235), [#14135](https://github.com/craftcms/cms/discussions/14135)) +- Added the `languageMenu` and `languageMenuField` form macros. +- Added `craft\models\Site::getLanguage()`. +- Added `craft\models\Site::setLanguage()`. + ## 5.0.0-alpha.9 - 2024-01-29 - Added live conditional field support to inline-editable Matrix blocks. ([#14223](https://github.com/craftcms/cms/pull/14223))