Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions core/src/Core.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Facade;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Str;
Expand Down Expand Up @@ -6560,6 +6561,44 @@ public function getSettings()
$this->getUserSettings();
$this->setConfig('site_timezone', $siteTimezone);
$this->invokeEvent('OnLoadSettings', ['config' => &$this->config]);

// The factory defaults read the manager lexicon (core/factory/settings.php), so
// ManagerTheme is already built from the system theme and language by the time the
// user settings above are merged. Realign it so a per-user manager_theme /
// manager_language (and anything OnLoadSettings changed) reaches the manager UI.
$this->syncManagerTheme();
}

/**
* Realign the already resolved ManagerTheme with the current manager_theme and
* manager_language. A different theme needs a new instance (the theme name drives the
* view namespaces, the style and the theme snippets/chunks); a different language only
* needs the lexicon read again.
*/
protected function syncManagerTheme(): void
{
if (!$this->isBackend() || !$this->resolved('ManagerTheme')) {
return;
}

$managerTheme = $this['ManagerTheme'];
if (!$managerTheme instanceof ManagerTheme) {
return;
}

$theme = (string) $this->getConfig('manager_theme', 'default');
if ($theme !== '' && $theme !== $managerTheme->getTheme()) {
// Dropped, not rebuilt: the next call resolves it again from the merged config.
$this->forgetInstance('ManagerTheme');
Facade::clearResolvedInstance('ManagerTheme');

return;
}

$language = (string) $this->getConfig('manager_language');
if ($language !== '' && $language !== $managerTheme->getLangName()) {
$managerTheme->reloadLang($language);
}
}

/**
Expand Down
14 changes: 14 additions & 0 deletions core/src/ManagerTheme.php
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,20 @@ public function __construct(CoreInterface $core, string $theme)
}
}

/**
* Re-read the lexicon for another language.
*
* The theme is built while the configuration still only holds the system settings,
* so the core calls this once the per-user manager_language has been merged in.
*
* @param string $lang
* @return string
*/
public function reloadLang(string $lang): string
{
return $this->loadLang($lang);
}

protected function loadLang($lang = 'english')
{
$_lang = [];
Expand Down
69 changes: 69 additions & 0 deletions core/tests/Unit/ManagerPerUserLanguageTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace Tests\Unit;

use EvolutionCMS\ManagerTheme;
use PHPUnit\Framework\TestCase;
use ReflectionMethod;

/**
* core/factory/settings.php reads the manager lexicon, so the ManagerTheme singleton is
* already built - with the system theme and language - by the time Core::getSettings()
* merges the per-user settings. Without an explicit refresh a user-level manager_language
* or manager_theme never reaches the manager UI: the whole backend stays on the site-wide
* value.
*/
final class ManagerPerUserLanguageTest extends TestCase
{
private static string $coreSource = '';

public static function setUpBeforeClass(): void
{
$rootDir = dirname(__DIR__, 3);
require_once $rootDir . '/core/vendor/autoload.php';

self::$coreSource = (string) file_get_contents($rootDir . '/core/src/Core.php');
}

public function testFactorySettingsStillNeedTheManagerLexicon(): void
{
$factorySettings = (string) file_get_contents(dirname(__DIR__, 2) . '/factory/settings.php');

// This is what forces ManagerTheme to be resolved before the user settings are known.
self::assertStringContainsString('ManagerTheme::getLexicon(', $factorySettings);
}

public function testManagerThemeCanReloadItsLexicon(): void
{
$method = new ReflectionMethod(ManagerTheme::class, 'reloadLang');

self::assertTrue($method->isPublic());
self::assertSame(1, $method->getNumberOfRequiredParameters());
}

public function testCoreRefreshesTheManagerThemeAfterMergingUserSettings(): void
{
$mergePosition = mb_strpos(self::$coreSource, '$this->getUserSettings();');
$syncPosition = mb_strpos(self::$coreSource, '$this->syncManagerTheme();');

self::assertIsInt($mergePosition);
self::assertIsInt($syncPosition);
self::assertGreaterThan($mergePosition, $syncPosition);
}

public function testManagerThemeRefreshOnlyTouchesAnAlreadyBuiltBackendTheme(): void
{
$sync = mb_substr(
self::$coreSource,
(int) mb_strpos(self::$coreSource, 'protected function syncManagerTheme')
);
$sync = mb_substr($sync, 0, (int) mb_strpos($sync, "\n }"));

self::assertStringContainsString('$this->isBackend()', $sync);
self::assertStringContainsString("\$this->resolved('ManagerTheme')", $sync);
self::assertStringContainsString('reloadLang(', $sync);
// A different theme cannot be patched in place - the instance has to go.
self::assertStringContainsString("forgetInstance('ManagerTheme')", $sync);
self::assertStringContainsString("Facade::clearResolvedInstance('ManagerTheme')", $sync);
}
}
31 changes: 0 additions & 31 deletions manager/includes/user_settings.inc.php

This file was deleted.