mirror of
https://github.com/getgrav/grav.git
synced 2026-05-06 06:27:51 +02:00
@@ -253,6 +253,15 @@ switch ($command) {
|
||||
echo " File: {$file}\n";
|
||||
echo " Line: {$line}\n";
|
||||
echo " Created: {$created}\n";
|
||||
|
||||
$window = $manager->getUpgradeWindow();
|
||||
if ($window) {
|
||||
$expires = isset($window['expires_at']) ? date('c', (int)$window['expires_at']) : 'unknown';
|
||||
$reason = $window['reason'] ?? '(unknown)';
|
||||
echo " Window: active ({$reason}, expires {$expires})\n";
|
||||
} else {
|
||||
echo " Window: inactive\n";
|
||||
}
|
||||
exit(0);
|
||||
|
||||
default:
|
||||
|
||||
@@ -20,6 +20,7 @@ use function is_array;
|
||||
use function is_file;
|
||||
use function json_decode;
|
||||
use function json_encode;
|
||||
use function max;
|
||||
use function md5;
|
||||
use function preg_match;
|
||||
use function random_bytes;
|
||||
@@ -100,6 +101,8 @@ class RecoveryManager
|
||||
if (is_file($flag)) {
|
||||
@unlink($flag);
|
||||
}
|
||||
|
||||
$this->closeUpgradeWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -121,6 +124,10 @@ class RecoveryManager
|
||||
|
||||
$file = $error['file'] ?? '';
|
||||
$plugin = $this->detectPluginFromPath($file);
|
||||
if (!$plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
$context = [
|
||||
'created_at' => time(),
|
||||
'message' => $error['message'] ?? '',
|
||||
@@ -130,6 +137,10 @@ class RecoveryManager
|
||||
'plugin' => $plugin,
|
||||
];
|
||||
|
||||
if (!$this->shouldEnterRecovery($context)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->activate($context);
|
||||
if ($plugin) {
|
||||
$this->quarantinePlugin($plugin, $context);
|
||||
@@ -286,6 +297,63 @@ class RecoveryManager
|
||||
return $this->rootPath . '/system/recovery.flag';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function windowPath(): string
|
||||
{
|
||||
return $this->rootPath . '/system/recovery.window';
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
private function resolveUpgradeWindow(): ?array
|
||||
{
|
||||
$path = $this->windowPath();
|
||||
if (!is_file($path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$decoded = json_decode(file_get_contents($path), true);
|
||||
if (!is_array($decoded)) {
|
||||
@unlink($path);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
$expiresAt = (int)($decoded['expires_at'] ?? 0);
|
||||
if ($expiresAt > 0 && $expiresAt < time()) {
|
||||
@unlink($path);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $decoded;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $context
|
||||
* @return bool
|
||||
*/
|
||||
private function shouldEnterRecovery(array $context): bool
|
||||
{
|
||||
$window = $this->resolveUpgradeWindow();
|
||||
if (null === $window) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$scope = $window['scope'] ?? null;
|
||||
if ($scope === 'plugin') {
|
||||
$expected = $window['plugin'] ?? null;
|
||||
if ($expected && ($context['plugin'] ?? null) !== $expected) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
@@ -314,4 +382,54 @@ class RecoveryManager
|
||||
{
|
||||
return error_get_last();
|
||||
}
|
||||
|
||||
/**
|
||||
* Begin an upgrade window; during this window fatal plugin errors may trigger recovery mode.
|
||||
*
|
||||
* @param string $reason
|
||||
* @param array $metadata
|
||||
* @param int $ttlSeconds
|
||||
* @return void
|
||||
*/
|
||||
public function markUpgradeWindow(string $reason, array $metadata = [], int $ttlSeconds = 604800): void
|
||||
{
|
||||
$ttl = max(60, $ttlSeconds);
|
||||
$createdAt = time();
|
||||
|
||||
$payload = $metadata + [
|
||||
'reason' => $reason,
|
||||
'created_at' => $createdAt,
|
||||
'expires_at' => $createdAt + $ttl,
|
||||
];
|
||||
|
||||
file_put_contents($this->windowPath(), json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isUpgradeWindowActive(): bool
|
||||
{
|
||||
return $this->resolveUpgradeWindow() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getUpgradeWindow(): ?array
|
||||
{
|
||||
return $this->resolveUpgradeWindow();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*/
|
||||
public function closeUpgradeWindow(): void
|
||||
{
|
||||
$window = $this->windowPath();
|
||||
if (is_file($window)) {
|
||||
@unlink($window);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -222,6 +222,13 @@ class SelfupgradeCommand extends GpmCommand
|
||||
$io->newLine();
|
||||
$io->writeln("Preparing to upgrade to v<cyan>{$remote}</cyan>..");
|
||||
|
||||
/** @var \Grav\Common\Recovery\RecoveryManager $recovery */
|
||||
$recovery = Grav::instance()['recovery'];
|
||||
$recovery->markUpgradeWindow('core-upgrade', [
|
||||
'scope' => 'core',
|
||||
'target_version' => $remote,
|
||||
]);
|
||||
|
||||
$io->write(" |- Downloading upgrade [{$this->formatBytes($update['size'])}]... 0%");
|
||||
$this->file = $this->download($update);
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace Grav\Console\Gpm;
|
||||
use Grav\Common\GPM\GPM;
|
||||
use Grav\Common\GPM\Installer;
|
||||
use Grav\Common\GPM\Upgrader;
|
||||
use Grav\Common\Grav;
|
||||
use Grav\Console\GpmCommand;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
@@ -212,6 +213,10 @@ class UpdateCommand extends GpmCommand
|
||||
}
|
||||
}
|
||||
|
||||
/** @var \Grav\Common\Recovery\RecoveryManager $recovery */
|
||||
$recovery = Grav::instance()['recovery'];
|
||||
$recovery->markUpgradeWindow('package-update', ['scope' => 'core']);
|
||||
|
||||
// finally update
|
||||
$install_command = $this->getApplication()->find('install');
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ class RecoveryManagerTest extends \Codeception\TestCase\Test
|
||||
}
|
||||
};
|
||||
|
||||
$manager->markUpgradeWindow('core-upgrade', ['scope' => 'core']);
|
||||
$manager->handleShutdown();
|
||||
|
||||
$flag = $this->tmpDir . '/system/recovery.flag';
|
||||
|
||||
Reference in New Issue
Block a user