'grav-recovery', 'cookie_httponly' => true, 'cookie_secure' => !empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off', 'cookie_samesite' => 'Lax', ]); $manager = new RecoveryManager(); $context = $manager->getContext() ?? []; $token = $context['token'] ?? null; $authenticated = $token && isset($_SESSION['grav_recovery_authenticated']) && hash_equals($_SESSION['grav_recovery_authenticated'], $token); $errorMessage = null; $notice = null; if ($_SERVER['REQUEST_METHOD'] === 'POST') { $action = $_POST['action'] ?? ''; if ($action === 'authenticate') { $provided = trim($_POST['token'] ?? ''); if ($token && hash_equals($token, $provided)) { $_SESSION['grav_recovery_authenticated'] = $token; header('Location: ' . $_SERVER['REQUEST_URI']); exit; } $errorMessage = 'Invalid recovery token.'; } elseif ($action === 'clear-flag') { // Clear recovery flag - allowed without authentication $manager->clear(); $_SESSION['grav_recovery_authenticated'] = null; $notice = 'Recovery flag cleared. Reload the page to continue.'; } elseif ($action === 'disable-recovery') { // Disable recovery mode in config (updates.recovery_mode) - allowed without authentication $configDir = GRAV_ROOT . '/user/config'; $configFile = $configDir . '/system.yaml'; Folder::create($configDir); $config = []; if (is_file($configFile)) { $content = file_get_contents($configFile); if ($content !== false) { // Simple YAML parsing for this specific case $config = \Symfony\Component\Yaml\Yaml::parse($content) ?? []; } } if (!isset($config['updates'])) { $config['updates'] = []; } $config['updates']['recovery_mode'] = false; $yaml = \Symfony\Component\Yaml\Yaml::dump($config, 4, 2); file_put_contents($configFile, $yaml); // Also clear the recovery flag $manager->clear(); $_SESSION['grav_recovery_authenticated'] = null; $notice = 'Recovery mode has been disabled. Reload the page to continue.'; } elseif ($authenticated) { $service = new SafeUpgradeService(); try { if ($action === 'rollback' && !empty($_POST['manifest'])) { $service->rollback(trim($_POST['manifest'])); $manager->clear(); $_SESSION['grav_recovery_authenticated'] = null; $notice = 'Rollback complete. Please reload Grav.'; } } catch (\Throwable $e) { $errorMessage = $e->getMessage(); } } else { $errorMessage = 'Authentication required for this action.'; } } $quarantineFile = GRAV_ROOT . '/user/data/upgrades/quarantine.json'; $quarantine = []; if (is_file($quarantineFile)) { $decoded = json_decode(file_get_contents($quarantineFile), true); if (is_array($decoded)) { $quarantine = $decoded; } } $manifestDir = GRAV_ROOT . '/user/data/upgrades'; $snapshots = []; if (is_dir($manifestDir)) { $files = glob($manifestDir . '/*.json'); if ($files) { foreach ($files as $file) { $decoded = json_decode(file_get_contents($file), true); if (!is_array($decoded)) { continue; } $id = $decoded['id'] ?? pathinfo($file, PATHINFO_FILENAME); if (!is_string($id) || $id === '' || strncmp($id, 'snapshot-', 9) !== 0) { continue; } $decoded['id'] = $id; $decoded['file'] = basename($file); $decoded['created_at'] = (int)($decoded['created_at'] ?? filemtime($file) ?: 0); $snapshots[] = $decoded; } if ($snapshots) { usort($snapshots, static function (array $a, array $b): int { return ($b['created_at'] ?? 0) <=> ($a['created_at'] ?? 0); }); } } } $latestSnapshot = $snapshots[0] ?? null; // Determine base URL for assets $scriptName = $_SERVER['SCRIPT_NAME'] ?? '/index.php'; $baseUrl = rtrim(dirname($scriptName), '/\\'); if ($baseUrl === '.' || $baseUrl === '') { $baseUrl = ''; } header('Content-Type: text/html; charset=utf-8'); ?> Grav Recovery Mode
Grav

Recovery Mode

Grav has encountered an error during a recent update

Action Failed
A Fatal Error Occurred
Grav detected a fatal error after a recent upgrade and has entered recovery mode to protect your site.

Error Details

:
View Stack Trace
Affected Plugin
  • Plugin (has been automatically disabled)

Quarantined Plugins

These plugins have been automatically disabled due to errors:

What would you like to do?

Choose an action to resolve this issue:

Clear Recovery & Continue: Clears the recovery flag and attempts to load your site normally.
Disable Recovery Mode: Sets updates.recovery_mode: false in your configuration so recovery mode won't trigger again.

Rollback to Previous Version

If the error persists, you can rollback to a previous Grav version.

Grav — Created

To rollback, enter the recovery token found in user/data/recovery.flag