Skip to content

About

WordPress plugin updates that check key pages before and after and roll back an update that breaks them (structural comparison, verified rollback), plus a dashboard for other sites over application passwords.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Site Manager: Safe Updates for WordPress

CI WordPress 6.5 – 7.1 PHP 8.1 – 8.4 PHPStan level 8 WPCS 3

A WordPress plugin for people who look after websites. It updates plugins without breaking the site, and shows the other sites you manage in one dashboard.

Plugin updates are the most common way a working site breaks. When WordPress auto-updates a plugin, the check it runs afterwards looks for a fatal PHP error. Most breakage is quieter than that: a contact form that no longer renders, a shortcode printed as raw text, a missing stylesheet that leaves the page unstyled, an empty product grid, a PHP warning splashed across the top of the page. The site still answers "200 OK", and nobody notices until a customer does.

This plugin fetches your key pages as a visitor sees them before an update and again after it, compares their structure, and puts the old version back if a page broke:

snapshot pages ─► back up the plugin folder ─► update ─► snapshot again ─► compare
                                                          ├─ fine:   keep the update, delete the backup
                                                          └─ broken: restore the backup, re-check the pages, record why

Updates: two waiting, and what happened to five earlier ones: three rolled back with the reason on each page

Results

On a real WordPress site (tests/integration/run.sh, run in CI on WordPress 6.5 and the latest release). A fixture plugin is updated through five versions, and each update is judged from pages fetched over HTTP:

Update What it does Result
1.1 New wording Kept
1.2 Fatal error while rendering its shortcode Rolled back (an error page served with status 200)
1.3 Shortcode renamed, so pages show [opening_hours] Rolled back
1.4 Stops loading its stylesheet Rolled back
1.5 Stylesheet renamed to a minified bundle Kept

After each rollback, the page is fetched again and must look as it did before. Note the fatal error in 1.2: it happened after WordPress had sent the headers, so the broken page came back as 200 OK. A check on the HTTP status alone would have kept that update.

Across many kinds of breakage (php eval/run.php). Generated WordPress sites (three theme layouts, five pages each, with nonces, counters and rotating content that change on every load) are updated 30 times each. Rules and thresholds were set on 20 tuning sites. These are 50 held-out sites:

Check after an update (held-out) Breaking updates caught Harmless updates rolled back
Server error status only 26.1% 0%
Error status or error page (what WordPress checks after an auto-update) 34.0% 0%
Any change in the visible text (a naive visual diff) 100% 100%
Structural comparison (this plugin) 100% 0%

The held-out sites test new sites, not new kinds of breakage. For that, eight kinds of breakage and five kinds of harmless change were written after the rules were frozen (commit 147186c) and run once:

New kinds, written after freezing (30 sites) Caught by this plugin Caught by the error check
Maintenance page served as 200 35 of 35 0
Redirected to the login page 31 of 31 0
Content stuck on "Loading…" 34 of 34 0
Untranslated string keys (form.submit.label) 33 of 33 0
Images broken 30 of 42 0
Navigation menu emptied 0 of 29 0
Every price showing $0.00 0 of 34 0
Content rendered twice 0 of 38 0
All new breaking updates 59.1% 0%
New harmless changes (consent overlay, lazy images, sidebar moved, wording, seasonal header) 0 of 624 rolled back

What the numbers show:

  • The error check WordPress relies on misses most breakage. It caught a third of breaking updates on held-out sites, and none of the new kinds.
  • Comparing structure, with changing tokens masked, separates breakage from noise. A naive visual diff flags every update, because nonces and counters change on every load. This plugin rolled back no harmless update in any bank.
  • It has blind spots, and they are listed here rather than tuned away. A menu that renders empty but keeps its <nav>, prices that all drop to zero (digits are masked to ignore counters), and duplicated content all passed. Broken images were caught only on pages with four or more images. I set a 60% target for the new kinds before running them, and it reached 59.1%. The gate is now a regression floor at 55%, and fixing these is listed in the known limitations.

Sites: another WordPress site's version, waiting updates and last update result, read over its REST API

How it works

  • Snapshots (src/Core/PageSnapshot.php). Each page's status, error markers (critical error, database error, maintenance), landmarks (as HTML5 elements, ARIA roles or the usual class names), forms, images, links, stylesheets, unrendered shortcodes, visible PHP warnings, and the visible words with numbers and hashes masked.
  • Comparison (src/Core/Comparator.php). It flags anything newly wrong: an error status or page, most of the text gone, a landmark or every form gone, a shortcode as text, a stylesheet gone and not replaced, PHP warnings, images or links mostly gone, or text much less similar than a re-render. Each reason is stored and shown. See ADR 1.
  • Backup and rollback (src/Updates). The plugin folder is copied through WP_Filesystem before the update. On a regression, the copy is put back and the pages are fetched again to confirm they recovered. If they don't, the backup is kept and the history says where. See ADR 2.
  • Hub (src/Hub, src/Rest). Other sites expose their status (versions, waiting updates, recent results) to an administrator through /wp-json/site-manager/v1/status. The hub authenticates with a WordPress application password, stored encrypted (AES-256-GCM with a key derived from the site's salts). See ADR 3.
  • Safe auto-updates. A daily WP-Cron run updates waiting plugins one at a time through the same check. It is off by default.

Decisions: docs/adr. Gaps: docs/known-limitations.md.

Run it

composer install
vendor/bin/phpunit                 # unit tests (snapshots and comparison)
vendor/bin/phpcs                   # WordPress coding standards
vendor/bin/phpstan analyse         # level 8 with WordPress stubs
php eval/run.php --gate            # eval/reports/report.{md,json,html}

docker compose up -d && bash tests/integration/run.sh    # real updates and rollbacks in WordPress
# open http://127.0.0.1:8080/wp-admin/admin.php?page=site-manager (admin / admin)

Tests and CI

  • Unit: masking, error pages including 200-status critical errors, silent breakage (shortcodes, forms, navigation, warnings, stylesheets, blank pages), harmless changes (bundled stylesheets, ARIA landmarks, banners), and snapshot round-trips.
  • Integration (real WordPress): pages fetched over HTTP, five real updates with the outcomes above, the plugin staying active, the history, backups cleaned up, the hub reading a site with an application password (and reporting a wrong one), secrets stored encrypted, REST permissions, and uninstall.
  • CI: WPCS and PHPCompatibility, PHPStan level 8, PHPUnit on PHP 8.1 to 8.4, the evaluation gate, the integration suite on WordPress 6.5 and the latest release, and a gitleaks scan of the full history.

License

GPL-2.0-or-later

About

WordPress plugin updates that check key pages before and after and roll back an update that breaks them (structural comparison, verified rollback), plus a dashboard for other sites over application passwords.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages