feat(demo): add story 1 — Sorano: Rock and Time
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
# Migrate to Grav 2.0
|
||||
|
||||
Stages a fresh Grav 2.0 install alongside your existing Grav 1.7 or 1.8 site
|
||||
and hands off to a standalone migration wizard. The plugin itself does **not**
|
||||
perform the migration — it exists solely to download Grav 2.0, drop a
|
||||
self-contained `migrate.php` at your webroot, and get out of the way so the
|
||||
wizard can run in a fresh PHP process with no 1.x code loaded.
|
||||
|
||||
## Why a standalone handoff?
|
||||
|
||||
In-place upgrades from 1.x → 2.0 are not safe: the vendor stacks differ, file
|
||||
locks and opcache pinning can corrupt mid-upgrade state, and any failure
|
||||
leaves an unbootable site. This plugin's job is to make the *handoff*
|
||||
boring: download, drop, redirect. Everything risky happens later, in a
|
||||
process that has no relationship to your running 1.x install.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Grav 1.7.50+ or 1.8.x
|
||||
- Write access to your webroot and `tmp/` directory
|
||||
- PHP 7.3.6+ (for the kickoff itself; the 2.0 wizard requires PHP 8.3+)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
bin/gpm install migrate-grav
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### From the admin
|
||||
|
||||
Click **Migrate to Grav 2.0** in the sidebar. Press the staging button. Your
|
||||
browser will be redirected to `/migrate.php` and you'll be running the wizard
|
||||
outside of Grav 1.x.
|
||||
|
||||
### From the CLI
|
||||
|
||||
```bash
|
||||
bin/plugin migrate-grav init
|
||||
```
|
||||
|
||||
Then follow the printed instructions to start the wizard in a fresh PHP
|
||||
process (either `php migrate.php` or by visiting the URL).
|
||||
|
||||
### Status
|
||||
|
||||
```bash
|
||||
bin/plugin migrate-grav status
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`user/config/plugins/migrate-grav.yaml`:
|
||||
|
||||
```yaml
|
||||
enabled: true
|
||||
source_url: 'https://getgrav.org/download/core/grav-update/2.0.0-beta.1?testing'
|
||||
source_local_zip: '' # absolute path to a local 2.0 zip (dev only)
|
||||
stage_dir: 'grav-2'
|
||||
require_super_admin: true
|
||||
```
|
||||
|
||||
## Twig in content
|
||||
|
||||
Grav 2.0 changed how editor-authored Twig (Twig inside page content) is secured: the
|
||||
`security.twig_content` gate is off by default, a sandbox restricts what content Twig can
|
||||
do, and the blanket `undefined_functions` escape hatch was removed — an unlisted Twig
|
||||
function or filter is now a hard error. The `safe_functions` / `safe_filters` allow-lists
|
||||
are retained (and hardened: command/code-execution functions can never be enabled). The
|
||||
migration tries to preserve your 1.x behavior:
|
||||
|
||||
- It turns the `security.twig_content` gate back on when your source site used Twig in
|
||||
content (per-page `process: twig: true` or the site-wide `system.yaml` opt-ins).
|
||||
- It scans your Twig-enabled page content for the functions/filters it calls. **Raw PHP
|
||||
functions** (e.g. `strtoupper`) are added to `system.twig.safe_functions` /
|
||||
`safe_filters` (so they're callable at all) **and** to the
|
||||
`security.twig_sandbox.allowed_functions` / `allowed_filters` lists (so sandboxed content
|
||||
may call them). Your existing `safe_functions` entries are preserved and merged in.
|
||||
- **Plugin-provided Twig functions** (e.g. `unite_gallery`) are added to the sandbox
|
||||
allow-list, but the providing plugin must still register them — ideally via the
|
||||
`onBuildTwigSandboxPolicy` event. These are listed in the migration report.
|
||||
- Functions Grav 2.0 refuses — `Utils::isDangerousFunction()` (`system`, `exec`,
|
||||
`preg_replace`, …) and the sandbox's by-design exclusions (`constant`, `read_file`,
|
||||
`evaluate`, …) — are never added; the report lists them so you know those usages need
|
||||
reworking.
|
||||
|
||||
**What it can't detect automatically:** custom **object methods and properties** used in
|
||||
content Twig (for example a plugin object's `{{ thing.render() }}`) can't be found by a
|
||||
static scan, because the object's class isn't known until runtime. Grav 2.0 already
|
||||
allowlists the common page, media, config, and user classes, so most content keeps working.
|
||||
If something still renders as raw Twig (or shows a sandbox placeholder) after migration,
|
||||
check `logs/security.log`, then either add the class/method to
|
||||
`security.twig_sandbox.allowed_methods` by hand or — better — update the providing plugin to
|
||||
a 2.0 version that registers its safe Twig members via the `onBuildTwigSandboxPolicy` event.
|
||||
|
||||
The allowlists written to `user/config/security.yaml` are the **full** lists (core defaults
|
||||
plus your additions) on purpose: Grav merges these lists by index, so a partial override
|
||||
would corrupt the core defaults. If you prune an entry, leave the rest intact.
|
||||
|
||||
## URL-based image actions
|
||||
|
||||
Grav 1.7 applied image transforms straight from the query string —
|
||||
`image.jpg?cropResize=300,200` resized on the fly with no gate. Grav 2.0 moved that behind
|
||||
the new `system.images.url_actions` toggle (**off by default**), because those actions run
|
||||
with arguments an unauthenticated visitor controls. The normal, developer-driven path is
|
||||
unaffected: a Twig/Markdown media call like `page.media['x'].cropResize(300,200)`, or a
|
||||
Markdown image whose file is the page's own media (``), is
|
||||
resolved through the media object at render time into a hashed cache URL with no query
|
||||
string — it never touches this toggle.
|
||||
|
||||
The migration scans your content for the query-string form that *does* bypass the media
|
||||
object — absolute or rooted paths, `theme://`/`image://` stream paths, references to files
|
||||
that aren't the page's media, and anything hand-written in a theme template — and turns
|
||||
`system.images.url_actions` on in the staged `user/config/system.yaml` when it finds any, so
|
||||
those images keep transforming after migration. Co-located Markdown media references (the
|
||||
common case) are recognised and left alone, so the toggle is not flipped on needlessly.
|
||||
External and protocol-relative URLs (`https://cdn.example.com/x.jpg?…`, `//host/x.jpg?…`)
|
||||
are skipped too — those are served by the remote host, so a CDN's own `?format=webp` query
|
||||
can't be mistaken for a Grav image action.
|
||||
|
||||
If a flagged transform requests an image larger than `system.images.max_pixels`
|
||||
(25,000,000px by default), Grav still refuses it even with the toggle on — the report calls
|
||||
those out so you can raise the ceiling or rework them.
|
||||
|
||||
## Aborting
|
||||
|
||||
If you want to start over before launching the wizard, remove:
|
||||
|
||||
- `.migrating` at your webroot
|
||||
- The staged subdirectory (default: `grav-2/`)
|
||||
- `tmp/grav-2.0-staged.zip`
|
||||
|
||||
Your existing Grav 1.x site is untouched.
|
||||
|
||||
## Recovering from a failed promote
|
||||
|
||||
The promote step is the only point where the wizard touches your live webroot. It runs in three phases:
|
||||
|
||||
1. **Phase 1 — backup zip.** Every file in your live 1.x install (except the staged `grav-2/`) is zipped to `grav-2/backup/migration-backup-<version>--<timestamp>.zip`. After promote this lands at `backup/<…>.zip` next to Grav's other backups.
|
||||
2. **Phase 2 — delete.** Top-level entries at the webroot are removed.
|
||||
3. **Phase 3 — promote.** Contents of `grav-2/` are renamed up to the webroot.
|
||||
|
||||
If Phase 2 or Phase 3 fails partway through, your live webroot may be partially destroyed. The backup zip from Phase 1 is your recovery artifact.
|
||||
|
||||
**Before you retry, identify and free the lock.** The most common failure (especially on Windows, where open files can't be deleted) is a code editor, git GUI, or terminal holding a file handle on something inside your webroot — `.git/index`, `.git/objects/pack/*.idx`, a `.log` being tailed. The wizard will now report the specific path that failed; close whatever has it open.
|
||||
|
||||
**To restore from the backup zip:**
|
||||
|
||||
- **Windows:** in File Explorer, right-click the zip → **Extract All…** and pick your webroot. The Extract All wizard reconstructs the directory tree correctly. 7-Zip and WinRAR also work fine.
|
||||
- **macOS:** double-click in Finder (Archive Utility extracts a proper tree), or `unzip migration-backup-*.zip -d /path/to/webroot` from Terminal.
|
||||
- **Linux:** `unzip migration-backup-*.zip -d /path/to/webroot`.
|
||||
|
||||
Once the webroot is restored, follow the **Aborting** steps above to clear the wizard state, then re-run the wizard from the admin.
|
||||
|
||||
### "The zip extracts as flat files with `·` or `\` in their names"
|
||||
|
||||
If you ran the wizard on **Windows** with a version **prior to 1.0.0-rc.3**, the backup zip it created has a separator bug — entry names use `\` (Windows path separator) instead of `/` (zip spec). Every standards-tolerant extractor (7-Zip, Archive Utility, Windows Explorer's in-place viewer) treats the backslashes as literal filename characters and dumps every file in the zip's root with names like `user\plugins\admin\file.php` (or, in some viewers, `user·plugins·admin·file.php`).
|
||||
|
||||
To repair such a zip, copy `user/plugins/migrate-grav/wizard/mg-repair-backup.php` from this plugin to any directory and run:
|
||||
|
||||
```
|
||||
php mg-repair-backup.php migration-backup-1.7.x--20260507111032.zip
|
||||
```
|
||||
|
||||
It writes `migration-backup-1.7.x--20260507111032.fixed.zip` next to the original with all entry names normalized to forward slashes. Extract the fixed zip with any tool and the directory tree will be correct.
|
||||
|
||||
The script is self-contained — no Grav, no Composer, no plugin context. It just needs PHP 8.1+ with the `zip` extension. Backup-zip writes from 1.0.0-rc.3 onward no longer have this bug regardless of OS.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
Reference in New Issue
Block a user