Skip to content

[Manager] Manager ignores the per-user manager_language #2497

Closed

Description

Environment: Evolution CMS 3.5.8 (settings_version = 3.5.8), PHP 8.4, MySQL 8.0

Summary

A manager user who has a personal manager_language in evo_user_settings
still gets the system-wide manager language after logging in. On the
reproducing installation admin has manager_language = 'en' while the site
default is manager_language = 'uk', and the panel renders in Ukrainian.

The personal setting is loaded — Core::getUserSettings() merges it into the
config correctly. It is simply applied too late: the ManagerTheme singleton,
which owns the loaded lexicon and locale, is constructed before that merge
and never rebuilt, so the UI keeps the system language for the rest of the
request.

Reproduction

  1. Set the site default manager language (System Settings → Interface → Language)
    to uk.
  2. Give a manager user manager_language = 'en' in evo_user_settings.
  3. Log in as that user.
  4. Observed: the panel is Ukrainian — <html lang="uk">, menu labels Головна,
    Елементи, Користувачі, Скасувати, … .
  5. Expected: English, per the user's own setting.

Root cause

The chain runs during Core::__construct() → initialize() → getSettings(),
i.e. before manager/index.php reaches
$_lang = ManagerTheme::getLexicon(); (manager/index.php:163).

# Location What happens
1 core/src/Core.php:6349 Core::getSettings() entry point during boot
2 core/src/Core.php:6370 $this->loadConfig() aliased to Traits\Settings::getSettings()
3 core/src/Traits/Settings.php:121 getFactorySettings() the very first statement of loadConfig()
4 core/src/Traits/Settings.php:162 include core/factory/settings.php
5 core/factory/settings.php:14 ManagerTheme::getLexicon(...) the facade resolves the ManagerTheme singleton → __construct
6 core/src/ManagerTheme.php:194-196 loadLang($this->getCore()->getConfig('manager_language')) the config holds only factory + system settings here ⇒ 'uk'
7 core/src/Core.php:6381 $this->getUserSettings() merges evo_user_settings ⇒ config is now 'en' — 11 lines too late
8 manager/index.php:163 ManagerTheme::getLexicon() returns the already-built Ukrainian lexicon

The trigger is easy to miss: step 5. core/factory/settings.php calls
ManagerTheme::getLexicon(...) at include time (lines 14, 15, 35, 40, 41, 42,
43, 80) to fill default mail/system strings. That single call resolves the
singleton during loadConfig().

Because ManagerTheme is a singleton
(core/src/Providers/ManagerThemeServiceProvider.php:25-36) and loadLang()
runs exactly once in the constructor, the Ukrainian lexicon is never rebuilt for
the request, even though evo()->getConfig('manager_language') now returns
'en'.

Evidence

Booting the core directly and printing the values:

config manager_language (final, after user settings merged): 'uk'   <-- wrong, should be 'en'
ManagerTheme::getLangName() (what the UI actually uses)    : 'uk'
lexicon[cancel]                                            : Скасувати
lexicon[login_message]                                     : Введіть ваше ім'я користувача та пароль...

Fetching the live manager as admin (GET /manager/index.php with that user's
session) returns:

<html dir="ltr" lang="uk" xml:lang="uk" class="manager-frame dark">

with Ukrainian menu labels, although the database shows
evo_user_settings.manager_language = 'en' for admin (user id 1).

A backtrace of the singleton construction pins the trigger exactly:

#0  core/src/Providers/ManagerThemeServiceProvider.php(35)
#9  core/factory/settings.php(14)                       Illuminate\Support\Facades\Facade::__callStatic()
#10 core/src/Traits/Settings.php(162)                   include('...factory/settings.php')
#11 core/src/Traits/Settings.php(121)                   EvolutionCMS\Core->getFactorySettings()
#12 core/src/Core.php(6370)                             EvolutionCMS\Core->loadConfig()
#13 core/src/Core.php(253)                              EvolutionCMS\Core->getSettings()
#14 core/src/Core.php(234)                              EvolutionCMS\Core->initialize()
#15 core/src/Core.php(268)                              EvolutionCMS\Core->__construct()

Scope

The merge itself is fine — a boot-time comparison of every stored key shows the
merged value is correct for all of them. Only state captured inside the
ManagerTheme constructor
is stale, because the constructor is the one
consumer that runs before the merge.

ManagerTheme::__construct() reads exactly these keys:

Key Read at Per-user capable? Status
manager_language ManagerTheme.php:194-196 (loadLang) yes ignored — this issue
manager_theme ManagerThemeServiceProvider.php:26 (closure) yes, in principle also ignored, latent — the theme is frozen into $this->theme at the same moment. No site sets it per-user, so it has not been observed in the wild.
mgr_jquery_path, mgr_date_picker_path ManagerTheme.php:200-204 no (system paths) unaffected

Everything else is read at request time and does honour the per-user value.
Verified empirically: admin's manager_theme_mode was temporarily set to 1
while the system default stayed 3, and the manager rendered
class="manager-frame lightness" — the per-user value won. It was restored to
3 afterwards.

Notes

  • The login page is rendered before a user is authenticated, so it
    legitimately uses the system default. That part is not a bug.
  • The value kept in the session
    ($_SESSION['mgrUsrConfigSet']['manager_language'] === 'en') never reaches the
    UI either: the lexicon is frozen before the manager reads the session.
  • In this build the per-user language control only exists for web users
    (manager/actions/mutate_web_user.dynamic.php:734); there is no manager-user
    equivalent, so the row is effectively only reachable from the database.
  • which_browser = 'default' resolving to the system value is not related —
    Core::getUserSettings() (line 6421) handles that substitution deliberately.

Suggested fix

  1. Preferred — resolve the language lazily. Do not call loadLang() in the
    ManagerTheme constructor; resolve the locale on first actual use (the first
    getLexicon() / getLangName() call, i.e. from manager/index.php:163,
    which runs after getUserSettings()), reading
    evo()->getConfig('manager_language') at that moment. Apply the same
    treatment to manager_theme, which is frozen in the container closure at
    ManagerThemeServiceProvider.php:26 and would otherwise stay latent.
  2. Alternative — reorder Core::getSettings() so getUserSettings() runs
    before loadConfig(), and therefore before anything that can resolve the
    ManagerTheme singleton. This is the minimal change, but it couples the user
    settings to the factory-defaults load order.
  3. Make the override explicit: after getUserSettings() merges the rows, if the
    effective manager_language differs from the one the singleton loaded, re-run
    loadLang() (rebuild the lexicon) for the current request.

A regression test would need to assert that a request carrying a per-user
manager_language yields a lexicon from that language folder — see
core/tests/Unit/Install/MigrationLedgerTableTest.php for the project's
existing style of pinning core behaviour in a unit test.

Workaround until fixed

Set the language system-wide (System Settings → Interface → Language). In this
version the per-user setting has no effect on the manager UI.

Activity

  1. MiddleSokilAI commented on Oct 11, 2026

    @MiddleSokilAI
    Collaborator

    Root cause and existing fix

    The early ManagerTheme initialization described here is addressed by commit 71eeda6e339a63767e203ef6acd8984f0a462647, merged into 3.5.x via #2441.

    That commit is not included in the 3.5.8 tag, which explains why the reported installation can still exhibit this behavior. Core::getSettings() now calls syncManagerTheme() after merging user settings and running OnLoadSettings. A changed manager_language reloads the lexicon; a changed manager_theme invalidates both the container singleton and the facade instance so the next access resolves the effective theme.

    Validation

    On the current 3.5.x code, isolated runtime checks for uk → en → uk passed: the lexicon, HTML language returned by ManagerTheme, application locale and manager_language configuration all followed the selected language. The synchronization call also runs after the personal settings merge.

    This was not a full login test on the reporter's installation.

    Next step

    @another-brick-in-the-wall, could you retest with the current 3.5.x branch, or a build containing the linked fix? If it still reproduces, please provide the exact core commit and whether the final manager_language configuration differs from ManagerTheme::getLangName().

    — MiddleSokil

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions