Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
c82645a
wordCount Filter for Grav (#3957)
pmoreno-rodriguez Oct 13, 2025
75d8356
Fixed Twig Sandbox Bypass due to nested expression (#3939)
nakkouchtarek Oct 13, 2025
250568b
initial safeupgrade work
rhukster Oct 15, 2025
2b1a7d3
upgrade manager fix
rhukster Oct 15, 2025
b55e86a
force upgrades before updating
rhukster Oct 15, 2025
57212ec
better plugin checks
rhukster Oct 15, 2025
43ddf45
latest tweak
rhukster Oct 15, 2025
cf2ac28
bugfixes in safeupgradeservice
rhukster Oct 15, 2025
7dd5c8a
staging root config option
rhukster Oct 15, 2025
f88c09a
update GRAV_VERSION for testing
rhukster Oct 15, 2025
23da92d
honor staging_root
rhukster Oct 15, 2025
77114ec
grav/restore dedicated binary
rhukster Oct 15, 2025
c8227b3
standalone grav-restore fixes
rhukster Oct 16, 2025
a5c6f1d
Merge branch 'feature/installer-rewrite' into develop
rhukster Oct 16, 2025
7192cfe
synced restore changes
rhukster Oct 16, 2025
c56d24c
timelimt on recovery status
rhukster Oct 16, 2025
d6cbc26
source fix in restore bin + missing dot files after upgrade
rhukster Oct 16, 2025
2c4b69f
Merge branch 'develop' of github.com:getgrav/grav into develop
rhukster Oct 16, 2025
43126b0
fixes for 1.8 upgrades
rhukster Oct 16, 2025
b68872e
Monolog 3 compatible shim to handle upgrades
rhukster Oct 16, 2025
f10894f
fixes for permission retention
rhukster Oct 16, 2025
3f0b204
Add new SafeUpgradeRun CLI command
rhukster Oct 16, 2025
09aa2fb
ensureJobResult
rhukster Oct 17, 2025
b6a37cf
preserver root files
rhukster Oct 17, 2025
9dd507b
route safeupgrade status
rhukster Oct 17, 2025
70d6aec
another fix for safe upgrade
rhukster Oct 17, 2025
286b5a5
fix for binary permissions in CLI
rhukster Oct 17, 2025
9230a5a
ingore recovery window
rhukster Oct 17, 2025
5e7b482
test fix
rhukster Oct 17, 2025
d97b2d7
logic fixes
rhukster Oct 17, 2025
2999c06
change snapshot storage
rhukster Oct 17, 2025
9206424
move back to cp instead of mv for snapshots
rhukster Oct 17, 2025
44fd117
more granular install for self upgrade
rhukster Oct 18, 2025
4cab0a7
optimized staged package
rhukster Oct 18, 2025
4650bd0
filter out extra folders
rhukster Oct 18, 2025
a0b64b6
more refactoring of safe install
rhukster Oct 18, 2025
17706d5
stop cache clearing snapshots
rhukster Oct 18, 2025
f30cd26
bin/restore enhancement
rhukster Oct 18, 2025
7325eb2
run / restore feature
rhukster Oct 18, 2025
c9640d7
create adhoc snapshot
rhukster Oct 19, 2025
6a4ab16
more restore bin fixes
rhukster Oct 19, 2025
da0fbf9
better label handling for snapshots
rhukster Oct 19, 2025
997bdff
fix test
rhukster Oct 19, 2025
cd5f384
ignore unpublished plugins
rhukster Oct 19, 2025
269bf78
ignore unpublished plugins - part 2
rhukster Oct 19, 2025
0ac7727
updated changelog
rhukster Oct 19, 2025
5815c8c
move recover.flag
rhukster Oct 19, 2025
80b8389
prepare for release
rhukster Oct 19, 2025
f437235
Merge tag '1.7.50' into develop
rhukster Oct 19, 2025
ea5ba5d
Merge branch 'release/1.7.50'
rhukster Oct 19, 2025
3cf616e
more fixes for recovery.window and recovery.flag
rhukster Oct 19, 2025
38840ff
recovery/command fixes
rhukster Oct 19, 2025
e82a0ce
more recovery fixes
rhukster Oct 20, 2025
1982717
more recovery manage fixes
rhukster Oct 20, 2025
23d92e6
jump into recovery mode
rhukster Oct 20, 2025
6568910
fix recovery mode
rhukster Oct 20, 2025
71eb774
support labels in recovery mode
rhukster Oct 20, 2025
06471eb
sync with 1.8 changes
rhukster Oct 20, 2025
3fad2a8
fix for GRAV_ROOT
rhukster Oct 20, 2025
915991a
prepare for release
rhukster Oct 20, 2025
975a2a8
Merge tag '1.7.50.1' into develop
rhukster Oct 20, 2025
a620556
Merge branch 'release/1.7.50.1'
rhukster Oct 20, 2025
1b75df7
fix for #3966
rhukster Oct 21, 2025
20809f3
prepare for release
rhukster Oct 21, 2025
841259c
Merge branch 'release/1.7.50.2'
rhukster Oct 21, 2025
1c5c2ac
Merge tag '1.7.50.2' into develop
rhukster Oct 21, 2025
de26048
remove phpstan file
rhukster Oct 21, 2025
8c1e425
update ignore
rhukster Oct 21, 2025
f73df19
reset system.yaml to default
rhukster Oct 23, 2025
ef48476
prepare for release
rhukster Oct 23, 2025
16b0b56
Merge tag '1.7.50.3' into develop
rhukster Oct 23, 2025
2a18c07
Merge branch 'release/1.7.50.3'
rhukster Oct 23, 2025
ff0de91
more fixes for safe upgrade
rhukster Oct 31, 2025
0afbce5
prepare for release
rhukster Oct 31, 2025
ce817c1
Merge branch 'release/1.7.50.4'
rhukster Oct 31, 2025
918bfc6
Merge tag '1.7.50.4' into develop
rhukster Oct 31, 2025
dd89d7e
improved js assets pipline handling to support defer
rhukster Nov 3, 2025
4adc767
add preflight command and —safe and —legacy for self-upgrade
rhukster Nov 4, 2025
7e8c0e3
selfupgrade fix
rhukster Nov 4, 2025
7ba99f7
Update system.yaml
gigago Nov 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@ tests/cache/*
tests/error.log
system/templates/testing/*
/user/config/versions.yaml
/system/recovery.window
tmp/*
#needs_fixing.txt
/AGENTS.md
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
# v1.7.50.4
## 10/31/2025

1. [](#improved)
* More fixes and improvements for safe-uprade process

# v1.7.50.3
## 10/21/2025

1. [](#bugfix)
* Restored `user/config/system.yaml` to 1.7 branch version (testing mode off)

# v1.7.50.2
## 10/21/2025

1. [](#bugfix)
* Fix for `SafeUpgradeService::getLastManifest()` fatal error on upgrade [#3966](https://github.com/getgrav/grav/issues/3966)

# v1.7.50.1
## 10/20/2025

1. [](#bugfix)
* Fix for broken `GRAV_ROOT`

# v1.7.50
## 10/19/2025

1. [](#new)
* Added new **Safe Core Upgrade** process with snapshots for backup and restore, better preflight and postflight checks, as well as exception checking post-install for easy rollback.
* Introduced recovery mode with token-gated UI, plugin quarantine, and CLI rollback support.
* Added `bin/gpm preflight` compatibility scanner and `bin/gpm rollback` utility.
* Added `wordCount` Twig filter [#3957](https://github.com/getgrav/grav/pulls/3957)

# v1.7.49.5
## 09/10/2025

Expand Down
209 changes: 209 additions & 0 deletions bin/build-test-update.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

use Grav\Common\Filesystem\Folder;


require __DIR__ . '/../vendor/autoload.php';

if (!\defined('GRAV_ROOT')) {
\define('GRAV_ROOT', realpath(__DIR__ . '/..') ?: getcwd());
}

if (!\extension_loaded('zip')) {
fwrite(STDERR, "The PHP zip extension is required.\n");
exit(1);
}

$options = getopt('', [
'version:',
'output::',
'port::',
'base-url::',
'serve',
]);

if (!isset($options['version'])) {
fwrite(
STDERR,
"Usage: php bin/build-test-update.php --version=1.7.999 [--output=tmp/test-gpm] [--port=8043] [--base-url=http://127.0.0.1:8043] [--serve]\n"
);
exit(1);
}

$version = trim((string) $options['version']);
if ($version === '') {
fwrite(STDERR, "A non-empty --version value is required.\n");
exit(1);
}

$root = GRAV_ROOT;

$output = $options['output'] ?? $root . '/tmp/test-gpm';
if (!str_starts_with($output, DIRECTORY_SEPARATOR)) {
$output = $root . '/' . ltrim($output, '/');
}
$output = rtrim($output, DIRECTORY_SEPARATOR);

$defaultPort = isset($options['port']) ? (int) $options['port'] : 8043;
$baseUrl = $options['base-url'] ?? sprintf('http://127.0.0.1:%d', $defaultPort);
$serve = array_key_exists('serve', $options);

Folder::create($output);

$downloadName = sprintf('grav-update-%s.zip', $version);
$zipPath = $output . '/' . $downloadName;
$jsonPath = $output . '/grav.json';
$zipPrefix = 'grav-update/';

$excludeDirs = [
'.build',
'.crush',
'.ddev',
'.git',
'.github',
'.gitlab',
'.circleci',
'.idea',
'.vscode',
'.pytest_cache',
'backup',
'cache',
'images',
'logs',
'node_modules',
'tests',
'tmp',
'user',
];

$excludeFiles = [
'.htaccess',
'.DS_Store',
'robots.txt',
];

$directory = new RecursiveDirectoryIterator($root, RecursiveDirectoryIterator::SKIP_DOTS);
$filtered = new RecursiveCallbackFilterIterator(
$directory,
function (SplFileInfo $current) use ($root, $excludeDirs, $excludeFiles): bool {
$relative = ltrim(str_replace($root, '', $current->getPathname()), DIRECTORY_SEPARATOR);
$relative = str_replace('\\', '/', $relative);

if ($relative === '') {
return true;
}

if (str_contains($relative, '..')) {
return false;
}

foreach ($excludeDirs as $prefix) {
$prefix = trim($prefix, '/');
if ($prefix === '') {
continue;
}

if ($relative === $prefix || str_starts_with($relative, $prefix . '/')) {
return false;
}
}

if (in_array($current->getFilename(), $excludeFiles, true)) {
return false;
}

return true;
}
);

$zip = new ZipArchive();
if ($zip->open($zipPath, ZipArchive::CREATE | ZipArchive::OVERWRITE) !== true) {
throw new RuntimeException(sprintf('Unable to open archive at %s', $zipPath));
}

$zip->addEmptyDir($zipPrefix);

$iterator = new RecursiveIteratorIterator($filtered, RecursiveIteratorIterator::SELF_FIRST);
/** @var SplFileInfo $fileInfo */
foreach ($iterator as $fileInfo) {
$fullPath = $fileInfo->getPathname();
$relative = ltrim(str_replace($root, '', $fullPath), DIRECTORY_SEPARATOR);
$relative = str_replace('\\', '/', $relative);
$targetPath = $zipPrefix . $relative;

if ($fileInfo->isDir()) {
$zip->addEmptyDir(rtrim($targetPath, '/') . '/');
continue;
}

if ($fileInfo->isLink()) {
$target = readlink($fullPath);
$zip->addFromString($targetPath, $target === false ? '' : $target);
$zip->setExternalAttributesName($targetPath, ZipArchive::OPSYS_UNIX, 0120000 << 16);
continue;
}

$zip->addFile($fullPath, $targetPath);

$perms = @fileperms($fullPath);
if ($perms !== false) {
$zip->setExternalAttributesName($targetPath, ZipArchive::OPSYS_UNIX, ($perms & 0xFFFF) << 16);
}
}

$zip->close();

$size = filesize($zipPath);
$sha256 = hash_file('sha256', $zipPath);
$timestamp = date('c');
$downloadUrl = rtrim($baseUrl, '/') . '/' . rawurlencode($downloadName);

$manifest = [
'version' => $version,
'date' => $timestamp,
'min_php' => '8.3.0',
'assets' => [
'grav-update' => [
'name' => $downloadName,
'slug' => 'grav-update',
'version' => $version,
'date' => $timestamp,
'testing' => false,
'description' => 'Local test update package generated for safe-upgrade validation.',
'download' => $downloadUrl,
'size' => $size,
'checksum' => 'sha256:' . $sha256,
'sha256' => $sha256,
'host' => parse_url($downloadUrl, PHP_URL_HOST),
],
],
'changelog' => [
$version => [
'date' => $timestamp,
'content' => "- Local test update package generated by build-test-update.\n",
],
],
];

file_put_contents($jsonPath, json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL);

$manifestUrl = rtrim($baseUrl, '/') . '/grav.json';

echo "Update package created at: {$zipPath}\n";
echo "Manifest written to: {$jsonPath}\n";
echo "Manifest URL: {$manifestUrl}\n";
echo "Download URL: {$downloadUrl}\n";
echo "Archive size: {$size} bytes\n";
echo "SHA256: {$sha256}\n";

if ($serve) {
$host = parse_url($baseUrl, PHP_URL_HOST) ?: '127.0.0.1';
$port = parse_url($baseUrl, PHP_URL_PORT) ?: $defaultPort;
$command = sprintf('php -S %s:%d -t %s', $host, $port, escapeshellarg($output));
echo "\nServing files using PHP built-in server. Press Ctrl+C to stop.\n";
echo $command . "\n\n";
passthru($command);
}
Loading