diff --git a/classes/plugin/SafeUpgradeManager.php b/classes/plugin/SafeUpgradeManager.php index c29a310a..18833b1d 100644 --- a/classes/plugin/SafeUpgradeManager.php +++ b/classes/plugin/SafeUpgradeManager.php @@ -1025,11 +1025,11 @@ class SafeUpgradeManager $context = []; if ($this->jobManifestPath) { - $context['manifest'] = $this->jobManifestPath; + $context['manifest'] = $this->convertPathForContext($this->jobManifestPath); } if ($this->progressPath) { - $context['progress'] = $this->progressPath; + $context['progress'] = $this->convertPathForContext($this->progressPath); } if (!$context) { @@ -1041,6 +1041,20 @@ class SafeUpgradeManager return $encoded === false ? null : base64_encode($encoded); } + private function convertPathForContext(string $path): string + { + $normalized = str_replace('\\', '/', $path); + $root = str_replace('\\', '/', GRAV_ROOT); + + if (strpos($normalized, $root) === 0) { + $relative = substr($normalized, strlen($root)); + + return ltrim($relative, '/'); + } + + return $normalized; + } + protected function ensureExecutablePermissions(): void { $executables = [ diff --git a/safe-upgrade-status.php b/safe-upgrade-status.php index 02a15f89..92ce0a07 100644 --- a/safe-upgrade-status.php +++ b/safe-upgrade-status.php @@ -2,10 +2,14 @@ declare(strict_types=1); -$root = dirname(__DIR__, 3); +$root = \defined('GRAV_ROOT') ? GRAV_ROOT : dirname(__DIR__, 3); $jobsDir = $root . '/user/data/upgrades/jobs'; $fallbackProgress = $root . '/user/data/upgrades/safe-upgrade-progress.json'; +if (!\defined('GRAV_ROOT')) { + \define('GRAV_ROOT', $root); +} + header('Content-Type: application/json; charset=utf-8'); header('Cache-Control: no-store, no-cache, must-revalidate, max-age=0'); header('Pragma: no-cache'); @@ -44,6 +48,18 @@ $normalizeDir = static function (string $path): string { $jobsDirNormalized = $normalizeDir($jobsDir); $userDataDirNormalized = $normalizeDir(dirname($jobsDir)); +$toRelative = static function (string $path): string { + $normalized = str_replace('\\', '/', $path); + $root = str_replace('\\', '/', GRAV_ROOT); + + if (strpos($normalized, $root) === 0) { + $relative = substr($normalized, strlen($root)); + + return ltrim($relative, '/'); + } + + return $normalized; +}; $contextParam = $_GET['context'] ?? ''; if ($contextParam !== '') { @@ -52,7 +68,17 @@ if ($contextParam !== '') { $decoded = json_decode($decodedRaw, true); if (is_array($decoded)) { $validatePath = static function (string $candidate) use ($normalizeDir, $jobsDirNormalized, $userDataDirNormalized) { + if ($candidate === '') { + return null; + } + $candidate = str_replace('\\', '/', $candidate); + + if ($candidate[0] !== '/' && !preg_match('/^[A-Za-z]:[\\\\\/]/', $candidate)) { + $candidate = GRAV_ROOT . '/' . ltrim($candidate, '/'); + $candidate = str_replace('\\', '/', $candidate); + } + $directory = dirname($candidate); $real = realpath($directory); if ($real === false) { @@ -133,10 +159,10 @@ if ($jobId !== '' && is_array($progress) && !isset($progress['job_id'])) { $contextPayload = []; if ($manifestPath) { - $contextPayload['manifest'] = $manifestPath; + $contextPayload['manifest'] = $toRelative($manifestPath); } if ($progressPath) { - $contextPayload['progress'] = $progressPath; + $contextPayload['progress'] = $toRelative($progressPath); } $contextToken = $contextPayload ? base64_encode(json_encode($contextPayload)) : null; diff --git a/themes/grav/app/updates/safe-upgrade.js b/themes/grav/app/updates/safe-upgrade.js index 908e2b73..5d463d02 100644 --- a/themes/grav/app/updates/safe-upgrade.js +++ b/themes/grav/app/updates/safe-upgrade.js @@ -55,6 +55,7 @@ export default class SafeUpgrade { this.jobId = null; this.statusFailures = 0; this.statusContext = null; + this.statusIdleCount = 0; this.directStatusUrl = this.resolveDirectStatusUrl(); this.preferDirectStatus = !!this.directStatusUrl; @@ -122,6 +123,7 @@ export default class SafeUpgrade { this.statusFailures = 0; this.preferDirectStatus = !!this.directStatusUrl; this.statusContext = null; + this.statusIdleCount = 0; this.renderLoading(); this.modal.open(); this.fetchPreflight(); @@ -512,6 +514,7 @@ export default class SafeUpgrade { let jobFailed = false; let shouldReload = false; let handled = false; + let lastPayload = null; console.debug('[SafeUpgrade] poll status'); @@ -545,6 +548,7 @@ export default class SafeUpgrade { } const payload = response.data || {}; + lastPayload = payload; if (Object.prototype.hasOwnProperty.call(payload, 'context')) { this.statusContext = payload.context || null; } @@ -552,6 +556,12 @@ export default class SafeUpgrade { const data = payload.progress || payload; nextStage = data.stage || null; + if (!job || !Object.keys(job).length) { + this.statusIdleCount += 1; + } else { + this.statusIdleCount = 0; + } + this.renderProgress(data, job); if (job.status === 'error') { @@ -600,6 +610,10 @@ export default class SafeUpgrade { const delay = Math.min(5000, 1200 * Math.max(1, this.statusFailures)); this.schedulePoll(delay); } + } else if ((!lastPayload || !lastPayload.job || !Object.keys(lastPayload.job).length) && usingDirect && this.statusIdleCount >= 5) { + this.preferDirectStatus = false; + this.statusFailures = 0; + this.schedulePoll(); } else if (jobFailed) { this.stopPolling(); this.jobId = null; diff --git a/themes/grav/js/admin.min.js b/themes/grav/js/admin.min.js index f215847a..8d50ea52 100644 --- a/themes/grav/js/admin.min.js +++ b/themes/grav/js/admin.min.js @@ -4611,6 +4611,7 @@ var SafeUpgrade = /*#__PURE__*/function () { this.jobId = null; this.statusFailures = 0; this.statusContext = null; + this.statusIdleCount = 0; this.directStatusUrl = this.resolveDirectStatusUrl(); this.preferDirectStatus = !!this.directStatusUrl; this.registerEvents(); @@ -4678,6 +4679,7 @@ var SafeUpgrade = /*#__PURE__*/function () { this.statusFailures = 0; this.preferDirectStatus = !!this.directStatusUrl; this.statusContext = null; + this.statusIdleCount = 0; this.renderLoading(); this.modal.open(); this.fetchPreflight(); @@ -4984,6 +4986,7 @@ var SafeUpgrade = /*#__PURE__*/function () { var jobFailed = false; var shouldReload = false; var handled = false; + var lastPayload = null; console.debug('[SafeUpgrade] poll status'); var endpoint = this.resolveStatusEndpoint(); var statusUrl = endpoint.url; @@ -5016,8 +5019,14 @@ var SafeUpgrade = /*#__PURE__*/function () { _this6.statusContext = payload.context || null; } var job = payload.job || {}; + lastPayload = payload; var data = payload.progress || payload; nextStage = data.stage || null; + if (!job || !Object.keys(job).length) { + _this6.statusIdleCount += 1; + } else { + _this6.statusIdleCount = 0; + } _this6.renderProgress(data, job); if (job.status === 'error') { nextStage = 'error'; @@ -5061,6 +5070,10 @@ var SafeUpgrade = /*#__PURE__*/function () { var delay = Math.min(5000, 1200 * Math.max(1, _this6.statusFailures)); _this6.schedulePoll(delay); } + } else if ((!lastPayload || !lastPayload.job || !Object.keys(lastPayload.job).length) && usingDirect && _this6.statusIdleCount >= 5) { + _this6.preferDirectStatus = false; + _this6.statusFailures = 0; + _this6.schedulePoll(); } else if (jobFailed) { _this6.stopPolling(); _this6.jobId = null;