From cb7a3ccfdfb789cacd1c96a331f6c54ed2a0185d Mon Sep 17 00:00:00 2001 From: Andy Miller Date: Sat, 8 Nov 2025 11:03:50 +0000 Subject: [PATCH] mostly working Signed-off-by: Andy Miller --- .../src/Grav/Common/GPM/Remote/GravCore.php | 4 +- .../Common/Upgrade/SafeUpgradeService.php | 11 ++ .../Grav/Console/Gpm/SelfupgradeCommand.php | 31 +--- system/src/Grav/Installer/Install.php | 167 +++++++++++++++++- 4 files changed, 183 insertions(+), 30 deletions(-) diff --git a/system/src/Grav/Common/GPM/Remote/GravCore.php b/system/src/Grav/Common/GPM/Remote/GravCore.php index 4a98c1cdb..dbe40a8a4 100644 --- a/system/src/Grav/Common/GPM/Remote/GravCore.php +++ b/system/src/Grav/Common/GPM/Remote/GravCore.php @@ -20,8 +20,8 @@ use InvalidArgumentException; class GravCore extends AbstractPackageCollection { /** @var string */ - protected $repository = 'https://getgrav.org/downloads/grav.json'; - +// protected $repository = 'https://getgrav.org/downloads/grav.json'; + protected $repository = 'http://localhost:8043/grav.json'; /** @var array */ private $data; /** @var string */ diff --git a/system/src/Grav/Common/Upgrade/SafeUpgradeService.php b/system/src/Grav/Common/Upgrade/SafeUpgradeService.php index f52fb5cbd..b47de887b 100644 --- a/system/src/Grav/Common/Upgrade/SafeUpgradeService.php +++ b/system/src/Grav/Common/Upgrade/SafeUpgradeService.php @@ -65,6 +65,17 @@ use const JSON_PRETTY_PRINT; */ class SafeUpgradeService { + /** + * Version identifier for this SafeUpgradeService implementation. + * This is used to verify that the correct version is loaded during upgrades. + * + * IMPORTANT: Increment this with each release that changes SafeUpgradeService. + * Format: YYYYMMDD (release date) + * + * @var string + */ + public const IMPLEMENTATION_VERSION = '20251106'; // 2025-11-06 - Added preflight to Install.php + /** @var string */ private $rootPath; /** @var string */ diff --git a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php index afb303312..cae961b3a 100644 --- a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php +++ b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php @@ -15,8 +15,9 @@ use Grav\Common\HTTP\Response; use Grav\Common\GPM\Installer; use Grav\Common\GPM\Upgrader; use Grav\Common\Grav; -use Grav\Common\Upgrade\SafeUpgradeService; use Grav\Console\GpmCommand; +// NOTE: SafeUpgradeService removed - no longer used in this file +// Preflight is now handled in Install.php after downloading the package use Grav\Installer\Install; use RuntimeException; use Symfony\Component\Console\Input\ArrayInput; @@ -162,11 +163,9 @@ class SelfupgradeCommand extends GpmCommand $this->displayGPMRelease(); - $safeUpgrade = $this->createSafeUpgradeService(); - $preflight = $safeUpgrade->preflight(); - if (!$this->handlePreflightReport($preflight)) { - return 1; - } + // NOTE: Preflight checks are now run in Install.php AFTER downloading the package. + // This ensures we use the NEW SafeUpgradeService from the package, not the old one. + // Running preflight here would load the OLD class into memory and prevent the new one from loading. $update = $this->upgrader->getAssets()['grav-update']; @@ -333,7 +332,8 @@ class SelfupgradeCommand extends GpmCommand } $io->newLine(); - $safeUpgrade->clearRecoveryFlag(); + // Clear recovery flag - upgrade completed successfully + $recovery->clearUpgradeWindow('core-upgrade'); } if ($this->tmp && is_dir($this->tmp)) { @@ -375,23 +375,6 @@ class SelfupgradeCommand extends GpmCommand return $this->tmp . DS . $package['name']; } - /** - * @return SafeUpgradeService - */ - protected function createSafeUpgradeService(): SafeUpgradeService - { - $config = null; - try { - $config = Grav::instance()['config'] ?? null; - } catch (\Throwable $e) { - $config = null; - } - - return new SafeUpgradeService([ - 'config' => $config, - ]); - } - /** * @param array $preflight * @return bool diff --git a/system/src/Grav/Installer/Install.php b/system/src/Grav/Installer/Install.php index b17f9258f..68ede9660 100644 --- a/system/src/Grav/Installer/Install.php +++ b/system/src/Grav/Installer/Install.php @@ -15,8 +15,10 @@ use Grav\Common\Cache; use Grav\Common\GPM\Installer; use Grav\Common\Grav; use Grav\Common\Plugins; -use Grav\Common\Upgrade\SafeUpgradeService; use RuntimeException; +// NOTE: SafeUpgradeService is NOT imported via 'use' to avoid autoloader loading OLD class +// When Install.php is included during upgrade, the autoloader is from the OLD installation +// and would load the OLD SafeUpgradeService. We manually require the NEW one when needed. use function class_exists; use function dirname; use function function_exists; @@ -296,6 +298,40 @@ ERR; $this->updater->install(); if ($this->shouldUseSafeUpgrade()) { + // CRITICAL: Check if SafeUpgradeService is already loaded from old installation + // If it is, PHP will use the OLD version instead of the NEW one from this package + $expectedPath = $this->location . '/system/src/Grav/Common/Upgrade/SafeUpgradeService.php'; + + if (class_exists('Grav\\Common\\Upgrade\\SafeUpgradeService', false)) { + // Class is already loaded - check if it's from the correct location + $reflection = new \ReflectionClass('Grav\\Common\\Upgrade\\SafeUpgradeService'); + $loadedFrom = $reflection->getFileName(); + + $loadedFromReal = realpath($loadedFrom) ?: $loadedFrom; + $expectedReal = realpath($expectedPath) ?: $expectedPath; + + if ($loadedFromReal !== $expectedReal) { + // OLD SafeUpgradeService is already loaded - we cannot proceed safely + throw new RuntimeException( + sprintf( + "CRITICAL: SafeUpgradeService was already loaded from the old installation.\n" . + "This prevents the new version from being used and will cause bugs to persist.\n\n" . + "Loaded from: %s\n" . + "Expected: %s\n\n" . + "WORKAROUND: This is a known issue in Grav < 1.7.50.9.\n" . + "The old CLI/Admin code loads SafeUpgradeService before downloading the package.\n" . + "Once you upgrade to 1.7.50.9+, this issue will be fixed.\n\n" . + "For now, you have two options:\n" . + "1. Use manual upgrade: Download zip, extract, copy files\n" . + "2. Wait for automatic fix in next release", + $loadedFromReal, + $expectedReal + ) + ); + } + } + + // SafeUpgradeService was loaded in shouldUseSafeUpgrade() from the NEW package $options = []; try { $grav = Grav::instance(); @@ -306,7 +342,63 @@ ERR; // ignore } - $service = new SafeUpgradeService($options); + // Use fully qualified class name (no 'use' statement to avoid autoloader) + $service = new \Grav\Common\Upgrade\SafeUpgradeService($options); + + // CRITICAL: Verify we're using the NEW SafeUpgradeService from this package, not the old one + $this->verifySafeUpgradeServiceVersion($service); + + // Run preflight checks using the NEW SafeUpgradeService + // This ensures preflight logic is from the package being installed, not the old installation + try { + $preflight = $service->preflight(); + + // Check for pending plugin/theme updates + if (!empty($preflight['plugins_pending'])) { + $pending = $preflight['plugins_pending']; + $list = []; + foreach ($pending as $slug => $info) { + $current = $info['current'] ?? 'unknown'; + $available = $info['available'] ?? 'unknown'; + $list[] = sprintf('%s (v%s → v%s)', $slug, $current, $available); + } + + throw new RuntimeException( + 'Please update the following plugins/themes before upgrading Grav: ' . + implode(', ', $list) . '. ' . + 'Run "bin/gpm update" or update via Admin panel first.' + ); + } + + // PSR log conflicts - log warning but don't block for now + if (!empty($preflight['psr_log_conflicts'])) { + $conflicts = $preflight['psr_log_conflicts']; + error_log( + 'WARNING: PSR/log conflicts detected in plugins: ' . + implode(', ', array_keys($conflicts)) . + '. These may need updating after Grav upgrade.' + ); + } + + // Monolog conflicts - log warning but don't block for now + if (!empty($preflight['monolog_conflicts'])) { + $conflicts = $preflight['monolog_conflicts']; + error_log( + 'WARNING: Monolog API conflicts detected in plugins: ' . + implode(', ', array_keys($conflicts)) . + '. These may need updating after Grav upgrade.' + ); + } + } catch (RuntimeException $e) { + // Preflight failed - abort upgrade with clear message + throw new RuntimeException( + 'Upgrade preflight checks failed: ' . $e->getMessage(), + 0, + $e + ); + } + + // Preflight passed - proceed with upgrade if ($this->progressCallback) { $service->setProgressCallback(function (string $stage, string $message, ?int $percent = null, array $extra = []) { $this->relayProgress($stage, $message, $percent); @@ -345,8 +437,16 @@ ERR; */ private function shouldUseSafeUpgrade(): bool { - if (!class_exists(SafeUpgradeService::class)) { - return false; + // CRITICAL: Check if class exists WITHOUT triggering autoloader + // If not loaded yet, manually load the NEW one from this package + if (!class_exists('Grav\\Common\\Upgrade\\SafeUpgradeService', false)) { + // Class not loaded yet - try to load from NEW package + $serviceFile = $this->location . '/system/src/Grav/Common/Upgrade/SafeUpgradeService.php'; + if (!file_exists($serviceFile)) { + return false; // SafeUpgradeService not available in this package + } + // Load the NEW SafeUpgradeService from this package + require_once $serviceFile; } if (null !== self::$forceSafeUpgrade) { @@ -479,6 +579,65 @@ ERR; return $matches[1] ?? ''; } + /** + * Verify that we're using the NEW SafeUpgradeService from this package, + * not the OLD one from the current installation. + * + * This is CRITICAL to ensure bugfixes in SafeUpgradeService are actually used. + * + * @param object $service The SafeUpgradeService instance (no type hint to avoid early loading) + * @return void + * @throws RuntimeException if the wrong version is loaded + */ + protected function verifySafeUpgradeServiceVersion(object $service): void + { + // Get the file path where SafeUpgradeService was loaded from + $reflection = new \ReflectionClass($service); + $loadedFrom = $reflection->getFileName(); + + // Expected: should be from THIS package in $this->location + // e.g., /tmp/grav-update-abc123/grav-update/system/src/Grav/Common/Upgrade/SafeUpgradeService.php + $expectedPath = $this->location . '/system/src/Grav/Common/Upgrade/SafeUpgradeService.php'; + + // Normalize paths for comparison + $loadedFromReal = realpath($loadedFrom) ?: $loadedFrom; + $expectedReal = realpath($expectedPath) ?: $expectedPath; + + if ($loadedFromReal !== $expectedReal) { + // CRITICAL ERROR: We're using the OLD SafeUpgradeService! + // This means bugfixes in the new version won't be applied. + error_log(sprintf( + 'CRITICAL: SafeUpgradeService loaded from WRONG location!' . "\n" . + ' Expected (NEW): %s' . "\n" . + ' Actual (OLD): %s' . "\n" . + 'This indicates a class loading issue that will prevent bugfixes from being applied.', + $expectedReal, + $loadedFromReal + )); + + throw new RuntimeException( + 'CRITICAL: SafeUpgradeService was loaded from the old installation instead of the new package. ' . + 'This is a known issue that has been fixed. Please upgrade using CLI: bin/gpm self-upgrade -f' + ); + } + + // Additional check: verify IMPLEMENTATION_VERSION constant exists + // (Added in this fix - old versions won't have it) + if (!defined('Grav\\Common\\Upgrade\\SafeUpgradeService::IMPLEMENTATION_VERSION')) { + error_log( + 'WARNING: SafeUpgradeService::IMPLEMENTATION_VERSION not defined. ' . + 'This suggests an old version is loaded.' + ); + } else { + $version = constant('Grav\\Common\\Upgrade\\SafeUpgradeService::IMPLEMENTATION_VERSION'); + error_log(sprintf( + 'SafeUpgradeService verification PASSED. Using version %s from: %s', + $version, + $loadedFromReal + )); + } + } + protected function legacySupport(): void { // Support install for Grav 1.6.0 - 1.6.20 by loading the original class from the older version of Grav.