Problem/Motivation

1. Admin language negotiation does not provide an opt-out. Once a user has something set (saved the user page once after the option was turned on) cannot opt out.
2. Currently the code uses a comparison with the site default language (which is a bug) to consider users opted out. This is silly and may entirely be untrue.
3. We can use the opt-out option to optimise the processing of the admin language. #1833010: Admin user language preference WSOD if ahead of path prefixes was almost not committed because of this problem and it was a critical issue.

Proposed resolution

1. Introduce a "None" option.
2. Don't return the site default if the user has nothing set (and/or None set).
3. Optimize the negotiation as per #1833010-22: Admin user language preference WSOD if ahead of path prefixes using this.

Remaining tasks

Review.

User interface changes

API changes

getPreferredAdminLangcode() will return a default empty string if passed and only fall back on default language if NULL is passed (no specific default).

Comments

gábor hojtsy’s picture

Issue summary: View changes
gábor hojtsy’s picture

Issue summary: View changes
Status: Active » Needs review
StatusFileSize
new3.8 KB
new42.38 KB

Here is the full solution to this. I am not sure what additional tests will this need.

1. I made the getter return the default value if not null, so passing in an empty string default will work (the method is defined to work with a string default on the interface).
2. Used the empty string to store no preference for the user (which it is by default anyway).
3. Reworked the negotiation method as per @alexpott's suggestions from #1833010-22: Admin user language preference WSOD if ahead of path prefixes, and improved it a little bit.
3a. Now it does the admin check last.
3b. Instead of checking logged-in user, it checks admin user right away. Even less to bother with later. Also while the user may have been an admin before if the user is not an admin anymore, the preference should not take effect anymore, so current permission status is best to check.
3c. No need to check against the current language list because getAdminPreferredLangcode() does that already for us.

So its both simpler, more peformant and more correct. :) Good finds :)

This is how it looks on the UI:

Note that I thought about using the more standard "None" but figured it may be misleading here. But it may mean the same thing to you as "No preference", so we can certainly do "None" if people think its essentially the same.

gábor hojtsy’s picture

StatusFileSize
new5.99 KB
new2.2 KB

With added tests for unsetting the language preference. There is already plenty test coverage for different combinations of settings and admin preference which proves we did not break things. I think this should be complete.

Status: Needs review » Needs work

The last submitted patch, 3: 2313157-admin-langcode-optimization-3.patch, failed testing.

alexpott’s picture

Version: 8.x-dev » 8.0.x-dev

gábor hojtsy’s picture

Assigned: gábor hojtsy » alexpott
Status: Needs work » Needs review

So any concerns left? Implemented based on suggestions form @alexpott, so assigning to him.

alexpott’s picture

Status: Needs review » Needs work
+++ b/core/modules/user/src/AccountForm.php
@@ -248,7 +248,7 @@ public function form(array $form, FormStateInterface $form_state) {
+    $user_preferred_admin_langcode = $register ? $language_interface->id : $account->getPreferredAdminLangcode('');

+++ b/core/modules/user/src/Plugin/LanguageNegotiation/LanguageNegotiationUserAdmin.php
@@ -92,17 +92,12 @@ public static function create(ContainerInterface $container, array $configuratio
+    if ($this->currentUser->hasPermission('access administration pages') && ($preferred_admin_langcode = $this->currentUser->getPreferredAdminLangcode('')) && $this->isAdminPath($request)) {

This change means we never call it with a NULL so I think we should remove the functionality from getPreferredAdminLangcode() that returns the default passed in or the default language. We should just return an empty string in this case. No need for the default argument at all :)

alexpott’s picture

Assigned: alexpott » Unassigned
gábor hojtsy’s picture

Status: Needs work » Needs review

@alexpott: well, this mirrors getPreferredLangcode(), see:

  /**
   * Returns the preferred language code of the account.
   *
   * @param string $default
   *   (optional) Default language code to return if the account
   *   has no valid language, defaults to the site default language.
   *
   * @return string
   *   The language code that is preferred by the account.
   */
  public function getPreferredLangcode($default = NULL);

  /**
   * Returns the preferred administrative language code of the account.
   *
   * Defines which language is used on administrative pages.
   *
   * @param string $default
   *   (optional) Default language code to return if the account
   *   has no valid language, defaults to the site default language.
   *
   * @return string
   *   The language code that is preferred by the account.
   */
  public function getPreferredAdminLangcode($default = NULL);

If we are to remove the default option from getPreferredLangcode() as well, then at places like the following, we'll need to add a conditional wrapper on empty return values and a fallback on site default language from the language manager:

core/modules/action/src/Plugin/Action/EmailAction.php:      $langcode = $recipient_account->getPreferredLangcode();
core/modules/contact/src/MessageForm.php:      $recipient_langcode = $recipient->getPreferredLangcode();
core/modules/language/language.module:      $language_code = $user->getPreferredLangcode();
core/modules/update/update.fetch.inc:          $target_langcode = $target_user->getPreferredLangcode();
core/modules/user/src/AccountForm.php:    $user_preferred_langcode = $register ? $language_interface->id : $account->getPrefer...
core/modules/user/user.module:  $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcode();
core/modules/user/user.module:  $langcode = isset($options['langcode']) ? $options['langcode'] : $account->getPreferredLangcod();
core/modules/user/user.module:  $language = \Drupal::languageManager()->getLanguage($params['account']->getPreferredLangcode();
core/modules/user/user.module:    $langcode = $langcode ? $langcode : $account->getPreferredLangcode();

(Most of these are used in sending email).

I think removing the argument only from the admin function would be inconsistent and may end up as a WTF.

gábor hojtsy’s picture

StatusFileSize
new10.82 KB
new7.52 KB

In further discussion with @alexpott, he suggested we use a boolean to decide if we fall back or not instead of letting external injection of a default. Hopefully this will look good enough now :)

alexpott’s picture

Status: Needs review » Reviewed & tested by the community

This is great - now we can satisfy both use cases of getPreferred* - to return what the user actually has set and useful runtime valid langcode.

catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 8.x, thanks!

  • catch committed f3b3f26 on 8.0.x
    Issue #2313157 by Gábor Hojtsy: Fixed Optimize admin language detection...
gábor hojtsy’s picture

Issue tags: -sprint

Thanks!

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.