diff --git a/system/src/Grav/Common/Upgrade/SafeUpgradeService.php b/system/src/Grav/Common/Upgrade/SafeUpgradeService.php index 5944dab33..b3b768dab 100644 --- a/system/src/Grav/Common/Upgrade/SafeUpgradeService.php +++ b/system/src/Grav/Common/Upgrade/SafeUpgradeService.php @@ -146,11 +146,28 @@ class SafeUpgradeService /** * Run preflight validations before attempting an upgrade. * - * @return array{plugins_pending: array, psr_log_conflicts: array, warnings: string[]} + * @param string|null $targetVersion The target Grav version being upgraded to (e.g., '1.8.0') + * @return array{plugins_pending: array, psr_log_conflicts: array, warnings: string[], is_major_minor_upgrade: bool} */ - public function preflight(): array + public function preflight(?string $targetVersion = null): array { $warnings = []; + $isMajorMinorUpgrade = false; + + // Determine if this is a major/minor version upgrade (e.g., 1.7.x -> 1.8.y) + if ($targetVersion !== null) { + $currentVersion = GRAV_VERSION; + $currentParts = explode('.', $currentVersion); + $targetParts = explode('.', $targetVersion); + + $currentMajor = (int)($currentParts[0] ?? 0); + $currentMinor = (int)($currentParts[1] ?? 0); + $targetMajor = (int)($targetParts[0] ?? 0); + $targetMinor = (int)($targetParts[1] ?? 0); + + $isMajorMinorUpgrade = ($currentMajor !== $targetMajor) || ($currentMinor !== $targetMinor); + } + try { $pending = $this->detectPendingPluginUpdates(); } catch (RuntimeException $e) { @@ -160,8 +177,15 @@ class SafeUpgradeService $psrLogConflicts = $this->detectPsrLogConflicts(); $monologConflicts = $this->detectMonologConflicts(); + + // Only enforce plugin updates for major/minor upgrades + // For patch upgrades, just warn but don't block if ($pending) { - $warnings[] = 'One or more plugins/themes are not up to date.'; + if ($isMajorMinorUpgrade) { + $warnings[] = 'One or more plugins/themes are not up to date and must be updated for major version upgrades.'; + } else { + $warnings[] = 'One or more plugins/themes are not up to date.'; + } } if ($psrLogConflicts) { $warnings[] = 'Potential psr/log signature conflicts detected.'; @@ -175,6 +199,7 @@ class SafeUpgradeService 'psr_log_conflicts' => $psrLogConflicts, 'monolog_conflicts' => $monologConflicts, 'warnings' => $warnings, + 'is_major_minor_upgrade' => $isMajorMinorUpgrade, ]; } diff --git a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php index 7c7a5b63b..e53c62836 100644 --- a/system/src/Grav/Console/Gpm/SelfupgradeCommand.php +++ b/system/src/Grav/Console/Gpm/SelfupgradeCommand.php @@ -232,29 +232,6 @@ class SelfupgradeCommand extends GpmCommand $io->writeln("Grav v{$remote} is now available [release date: {$release}]."); $io->writeln('You are currently using v' . GRAV_VERSION . '.'); - // Determine if this is a major/minor version upgrade - $localParts = explode('.', $local); - $remoteParts = explode('.', $remote); - - $localMajor = (int)($localParts[0] ?? 0); - $localMinor = (int)($localParts[1] ?? 0); - $remoteMajor = (int)($remoteParts[0] ?? 0); - $remoteMinor = (int)($remoteParts[1] ?? 0); - - // Check if this is a major/minor version change (e.g., 1.7.x -> 1.8.y) - $isMajorMinorUpgrade = ($localMajor !== $remoteMajor) || ($localMinor !== $remoteMinor); - - if ($isMajorMinorUpgrade) { - $io->newLine(); - $io->writeln('NOTE: This is a major version upgrade.'); - $io->writeln('It is recommended to run `bin/gpm update` first to update all plugins and themes'); - $io->writeln('to their latest compatible versions before upgrading Grav core.'); - } else { - $io->newLine(); - $io->writeln('NOTE: This is a patch version upgrade.'); - $io->writeln('You can safely proceed. Grav will check for any plugin compatibility issues during the upgrade.'); - } - if (!$this->all_yes) { $question = new ConfirmationQuestion( 'Would you like to read the changelog before proceeding? [y|N] ', @@ -423,6 +400,27 @@ class SelfupgradeCommand extends GpmCommand } if ($pending) { + // Use the is_major_minor_upgrade flag from preflight result if available + $isMajorMinorUpgrade = $preflight['is_major_minor_upgrade'] ?? false; + + // Fall back to calculating it if not provided (for backwards compatibility) + if (!isset($preflight['is_major_minor_upgrade'])) { + $local = $this->upgrader->getLocalVersion(); + $remote = $this->upgrader->getRemoteVersion(); + $localParts = explode('.', $local); + $remoteParts = explode('.', $remote); + + $localMajor = (int)($localParts[0] ?? 0); + $localMinor = (int)($localParts[1] ?? 0); + $remoteMajor = (int)($remoteParts[0] ?? 0); + $remoteMinor = (int)($remoteParts[1] ?? 0); + + $isMajorMinorUpgrade = ($localMajor !== $remoteMajor) || ($localMinor !== $remoteMinor); + } + + $local = $this->upgrader->getLocalVersion(); + $remote = $this->upgrader->getRemoteVersion(); + $io->newLine(); $io->writeln('The following packages need updating before Grav upgrade:'); foreach ($pending as $slug => $info) { @@ -432,7 +430,16 @@ class SelfupgradeCommand extends GpmCommand $io->writeln(sprintf(' - %s (%s) %s → %s', $slug, $type, $current, $available)); } - $io->writeln(' › Please run `bin/gpm update` to bring these packages current before upgrading Grav.'); + if ($isMajorMinorUpgrade) { + // For major/minor upgrades, this is EXPECTED behavior - updating plugins first is REQUIRED + $io->writeln(' › For major version upgrades (v' . $local . ' → v' . $remote . '), plugins must be updated to their latest'); + $io->writeln(' compatible versions BEFORE upgrading Grav core to ensure compatibility.'); + $io->writeln(' Please run `bin/gpm update` to update these packages, then retry self-upgrade.'); + } else { + // For patch upgrades, this shouldn't normally happen but plugins still need updating + $io->writeln(' › Please run `bin/gpm update` to bring these packages current before upgrading Grav.'); + } + $io->writeln('Aborting self-upgrade. Run `bin/gpm update` first.'); return false; diff --git a/system/src/Grav/Installer/Install.php b/system/src/Grav/Installer/Install.php index efd710c9a..3ce4382c8 100644 --- a/system/src/Grav/Installer/Install.php +++ b/system/src/Grav/Installer/Install.php @@ -346,7 +346,9 @@ ERR; // 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(); + $targetVersion = $this->getVersion(); + $preflight = $service->preflight($targetVersion); + $isMajorMinorUpgrade = $preflight['is_major_minor_upgrade'] ?? false; // Check for pending plugin/theme updates if (!empty($preflight['plugins_pending'])) { @@ -358,11 +360,31 @@ ERR; $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.' - ); + if ($isMajorMinorUpgrade) { + // For major/minor upgrades, block until plugins are updated + // This allows the NEW package to define and enforce the upgrade policy + $currentVersion = GRAV_VERSION; + $targetVersion = $this->getVersion(); + throw new RuntimeException( + sprintf( + "Major version upgrade detected (v%s → v%s).\n\n" . + "The following plugins/themes have updates available:\n - %s\n\n" . + "For major version upgrades, plugins and themes must be updated FIRST.\n" . + "Please run 'bin/gpm update' to update these packages, then retry the upgrade.\n" . + "This ensures plugins have necessary compatibility fixes for the new Grav version.", + $currentVersion, + $targetVersion, + implode("\n - ", $list) + ) + ); + } else { + // For patch upgrades, just log a warning but allow upgrade + error_log( + 'WARNING: The following plugins/themes have updates available: ' . + implode(', ', $list) . '. ' . + 'Consider updating them after upgrading Grav.' + ); + } } // PSR log conflicts - log warning but don't block for now