mostly working

Signed-off-by: Andy Miller <rhuk@mac.com>
This commit is contained in:
Andy Miller
2025-11-08 11:03:50 +00:00
parent 076c10d34b
commit cb7a3ccfdf
4 changed files with 183 additions and 30 deletions

View File

@@ -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 */

View File

@@ -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 */

View File

@@ -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

View File

@@ -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.