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
13 changes: 8 additions & 5 deletions assets/modules/store/js/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -1555,7 +1555,7 @@ store = {
}
});
},
refreshManagerUiAfterPermissionSync: function(){
refreshManagerUiAfterPermissionSync: function(delay){
try {
if (window.top && window.top.mainMenu && typeof window.top.mainMenu.reloadtree === 'function') {
window.top.mainMenu.reloadtree();
Expand All @@ -1566,7 +1566,7 @@ store = {
if (window.top && window.top.location) {
setTimeout(function(){
window.top.location.reload();
}, 700);
}, 700 + (parseInt(delay || 0, 10) || 0));
}
} catch (e) {}
},
Expand Down Expand Up @@ -1916,13 +1916,16 @@ store = {
if (
task
&& task.status === 'succeeded'
&& $.inArray(task.type, ['console_install', 'console_uninstall']) >= 0
&& $.inArray(task.type, ['console_install', 'console_uninstall', 'site_update']) >= 0
&& store.systemTaskRefreshPermissionsTaskId !== parseInt(task.id || 0, 10)
) {
store.systemTaskRefreshPermissionsTaskId = parseInt(task.id || 0, 10);
var reloadAlways = task.type === 'site_update';
store.refreshManagerPermissions(function(response){
if (response && response.ok) {
store.refreshManagerUiAfterPermissionSync();
// After a core update the whole manager must reload so the
// session and menu pick up the new version.
if (reloadAlways || (response && response.ok)) {
store.refreshManagerUiAfterPermissionSync(reloadAlways ? 2000 : 0);
}
});
}
Expand Down
18 changes: 16 additions & 2 deletions assets/plugins/updater/plugin.updater.php
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ function updaterHandleSystemTaskRequest()
]);
}

$requiresToken = in_array($action, ['create', 'cancel'], true);
$requiresToken = in_array($action, ['create', 'cancel', 'refresh_session'], true);
if ($requiresToken) {
$token = isset($_REQUEST['updater_task_token']) ? (string)$_REQUEST['updater_task_token'] : '';
if ($token === '' || !hash_equals(updaterEnsureSystemTaskToken(), $token)) {
Expand Down Expand Up @@ -394,6 +394,9 @@ function updaterHandleSystemTaskRequest()
$isSuperAdmin
));

case 'refresh_session':
updaterJsonResponse($context->refreshCurrentManagerPermissions());

case 'cancel':
updaterJsonResponse($taskService->cancelQueuedTaskPayload(
isset($_REQUEST['task_id']) ? (int)$_REQUEST['task_id'] : 0,
Expand Down Expand Up @@ -534,7 +537,15 @@ function close() {
}

if (reloadOnClose) {
window.location.reload();
var target = window;
try {
if (window.top && window.top.location && window.top !== window) {
target = window.top;
}
} catch (error) {
target = window;
}
target.location.reload();
}
}

Expand Down Expand Up @@ -640,6 +651,9 @@ function renderTask(task, result) {
}

if (isSucceeded) {
if (!reloadOnClose) {
request('refresh_session').catch(function () {});
}
reloadOnClose = true;
}

Expand Down
65 changes: 65 additions & 0 deletions core/src/Console/SiteUpdateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use EvolutionCMS\Models\Category;
use EvolutionCMS\Models\SiteModule;
use EvolutionCMS\Models\SystemSetting;
use EvolutionCMS\Services\ComposerVersionSynchronizer;
use EvolutionCMS\Services\Store\RemoteTransportService;
use Illuminate\Console\Command;
Expand Down Expand Up @@ -229,6 +230,7 @@ public function startUpdate()
$this->runCoreMigrations();
$this->runUpdateSeeders();
$this->updateBundledExtrasModule();
$this->syncSettingsVersion();

$this->line('<fg=green>Remove Install Directory</>');
self::rmdirs(EVO_BASE_PATH . 'install');
Expand Down Expand Up @@ -306,6 +308,69 @@ protected function runUpdateSeeders(): void
}
}

/**
* Persist the installed version and drop the settings cache.
*
* The manager menu and plugins read settings_version, which only a manual
* settings save used to refresh, so the old version stayed visible after an update.
*
* @since 3.5.8
* @return void
*/
protected function syncSettingsVersion(): void
{
$version = $this->readInstalledVersion();
if ($version === '') {
return;
}

SystemSetting::query()->updateOrCreate(
['setting_name' => 'settings_version'],
['setting_value' => $version]
);

foreach (['siteCache.idx.php', 'sitePublishing.idx.php'] as $file) {
$path = $this->bootstrapCachePath() . $file;
if (is_file($path)) {
unlink($path);
}
}

$this->line('<fg=green>Settings version set to ' . $version . '</>');
}

/**
* Read the version from the freshly overlaid factory/version.php.
*
* @since 3.5.8
* @return string
*/
protected function readInstalledVersion(): string
{
$file = EVO_CORE_PATH . 'factory/version.php';
if (!is_file($file)) {
return '';
}
if (function_exists('opcache_invalidate')) {
@opcache_invalidate($file, true);
}

$version = include $file;

return is_array($version) ? trim((string) ($version['version'] ?? '')) : '';
}

/**
* Directory holding the compiled settings cache.
*
* @since 3.5.8
* @return string
*/
protected function bootstrapCachePath(): string
{
return rtrim(evo()->bootstrapPath(), '/\\') . '/';
}

/**
* Remove placeholder files and root distribution artifacts from the update archive.
*
Expand Down
51 changes: 47 additions & 4 deletions core/tests/Feature/SiteUpdateE2ETest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
| - the core-only migration that creates the system task tables
| - SiteUpdateCommand::runUpdateSeeders() (the install update seeders)
| - SiteUpdateCommand::updateBundledExtrasModule()
| - SiteUpdateCommand::syncSettingsVersion() (settings_version + settings cache)
|
| and asserts the resulting schema/data deltas. The file-replacement mechanics
| (download/extract/move) are covered separately via the static helpers, since
Expand Down Expand Up @@ -107,6 +108,15 @@ function seedVersionNDatabase(Capsule $capsule): void
$table->string('category')->nullable();
$table->integer('rank')->default(0);
});
// settings_version is what the manager menu shows; version N left it stale.
$schema->create('system_settings', function (Blueprint $table) {
$table->string('setting_name')->primary();
$table->text('setting_value')->nullable();
});
$capsule->getConnection()->table('system_settings')->insert([
'setting_name' => 'settings_version',
'setting_value' => '0.0.0',
]);
$schema->create('site_modules', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->nullable();
Expand Down Expand Up @@ -175,13 +185,19 @@ function seedVersionNDatabase(Capsule $capsule): void
/**
* Run the database-affecting steps of an update, exactly as the update command does.
*/
function runSiteUpdateDatabaseSteps(): void
function runSiteUpdateDatabaseSteps(string $bootstrapDir): void
{
// 1. Core-only migration that ships the system task tables and permissions.
(new \CreateSystemCliTasksTables())->up();

// 2 + 3. The real update command steps, with console output suppressed.
$command = new class extends \EvolutionCMS\Console\SiteUpdateCommand {
// 2-4. The real update command steps, with console output suppressed and the
// settings cache pointed at a scratch directory instead of the live storage path.
$command = new class($bootstrapDir) extends \EvolutionCMS\Console\SiteUpdateCommand {
public function __construct(private string $bootstrapDir)
{
parent::__construct();
}

public function line($string, $style = null, $verbosity = null)
{
// Suppress: there is no console output bound in the test harness.
Expand All @@ -196,10 +212,21 @@ public function applyExtrasModule(): void
{
$this->updateBundledExtrasModule();
}

public function applySettingsVersion(): void
{
$this->syncSettingsVersion();
}

protected function bootstrapCachePath(): string
{
return $this->bootstrapDir;
}
};

$command->applyUpdateSeeders();
$command->applyExtrasModule();
$command->applySettingsVersion();
}

test('update from version N to N+1 applies migrations, update seeders and refreshes Extras', function () {
Expand All @@ -211,7 +238,13 @@ public function applyExtrasModule(): void
expect($db->getSchemaBuilder()->hasTable('system_cli_tasks'))->toBeFalse()
->and($db->table('permissions')->where('key', 'logout')->exists())->toBeTrue();

runSiteUpdateDatabaseSteps();
// A stale settings cache from version N that must be dropped so the DB value is read.
$bootstrapDir = sys_get_temp_dir() . '/evo_update_cache_' . uniqid() . '/';
mkdir($bootstrapDir, 0777, true);
file_put_contents($bootstrapDir . 'siteCache.idx.php', '<?php // stale');
file_put_contents($bootstrapDir . 'sitePublishing.idx.php', '<?php // stale');

runSiteUpdateDatabaseSteps($bootstrapDir);

// Migration created the system task tables.
expect($db->getSchemaBuilder()->hasTable('system_cli_tasks'))->toBeTrue()
Expand All @@ -238,6 +271,16 @@ public function applyExtrasModule(): void
expect($extras->description)->toContain('<strong>0.2.0</strong>')
->and($extras->modulecode)->toContain('store/core.php')
->and($extras->modulecode)->not->toBe('OUTDATED MODULE CODE');

// settings_version now matches the installed core and the stale settings cache is gone,
// so the manager menu shows the new version without a settings save or re-login.
$installed = include EVO_CORE_PATH . 'factory/version.php';
expect($db->table('system_settings')->where('setting_name', 'settings_version')->value('setting_value'))
->toBe($installed['version'])
->and(is_file($bootstrapDir . 'siteCache.idx.php'))->toBeFalse()
->and(is_file($bootstrapDir . 'sitePublishing.idx.php'))->toBeFalse();

rmdir($bootstrapDir);
});

test('moveFiles replaces files into the destination tree', function () {
Expand Down
8 changes: 7 additions & 1 deletion core/tests/Unit/UpdaterManagerUiTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@

expect($source)
->toContain('reloadOnClose')
->toContain('window.location.reload();')
// Reload the whole manager so the top menu shows the new version, and
// rebuild session permissions before that reload.
->toContain('target = window.top;')
->toContain('target.location.reload();')
->toContain("request('refresh_session')")
->toContain("case 'refresh_session':")
->toContain("['create', 'cancel', 'refresh_session']")
->toContain('updater_live_update_close_reload')
->toContain('updater_live_update_response_changed')
->toContain('normalized.substring(firstJsonChar, lastJsonChar + 1)')
Expand Down
10 changes: 10 additions & 0 deletions install/cli-install.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,10 +174,20 @@ public function update()
bootstrapInstallMigrationHistory($installMigrationsPath);
Console::call('migrate', ['--path' => $installMigrationsPath, '--realpath' => true, '--force' => true]);
seed('update');
// The manager menu reads settings_version; keep it on the installed version.
\EvolutionCMS\Models\SystemSetting::query()->updateOrCreate(
['setting_name' => 'settings_version'],
['setting_value' => (string) evo()->getVersionData('version')]
);
// Apply core-only migrations (core/database/migrations) not present in the
// install/stubs chain, e.g. the system task tables. Runs after seeding so the
// guarded ACL repairs inside those migrations detect a healthy baseline.
Console::call('migrate', ['--force' => true]);
foreach ([evo()->getSiteCacheFilePath(), evo()->getSitePublishingFilePath()] as $cacheFile) {
if (is_file($cacheFile)) {
unlink($cacheFile);
}
}
echo 'Evolution CMS updated!' . "\n";
$this->checkRemoveInstall();
$this->removeInstall();
Expand Down
5 changes: 5 additions & 0 deletions install/src/controllers/install.php
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,11 @@
\EvolutionCMS\Models\SystemSetting::insert($systemSettings);
} else {
seed('update');
// The manager menu reads settings_version; keep it on the installed version.
\EvolutionCMS\Models\SystemSetting::query()->updateOrCreate(
['setting_name' => 'settings_version'],
['setting_value' => (string) evo()->getVersionData('version')]
);
}

// Apply core-only migrations (core/database/migrations) that are not part
Expand Down