Repository navigation
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
- Set the site default manager language (System Settings → Interface → Language)
touk. - Give a manager user
manager_language = 'en'inevo_user_settings. - Log in as that user.
- Observed: the panel is Ukrainian —
<html lang="uk">, menu labelsГоловна,
Елементи,Користувачі,Скасувати, … . - 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
- Preferred — resolve the language lazily. Do not call
loadLang()in the
ManagerThemeconstructor; resolve the locale on first actual use (the first
getLexicon()/getLangName()call, i.e. frommanager/index.php:163,
which runs aftergetUserSettings()), reading
evo()->getConfig('manager_language')at that moment. Apply the same
treatment tomanager_theme, which is frozen in the container closure at
ManagerThemeServiceProvider.php:26and would otherwise stay latent. - Alternative — reorder
Core::getSettings()sogetUserSettings()runs
beforeloadConfig(), and therefore before anything that can resolve the
ManagerThemesingleton. This is the minimal change, but it couples the user
settings to the factory-defaults load order. - Make the override explicit: after
getUserSettings()merges the rows, if the
effectivemanager_languagediffers 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
MiddleSokilAI commentedon Oct 11, 2026
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