0.9.10 — 30 March 2026
Features
- Standalone updater — a self-contained
update.phpwizard that walks you through upgrading between versions. Upload two files, visit the URL, log in with your admin credentials, and follow the steps: backup confirmation, dry-run pre-flight checks (PHP version, ZipArchive, DB privileges, disk space, writable directories, ZIP hash validation), automatic database backup, migration runner, file extraction with rollback capability. If something goes wrong, click one button and everything goes back the way it was. If everything's fine, click the other button and the old files disappear. Works on shared hosting with no shell access — because that's the whole point. - Activity log and security log pagination — both tabs now paginate at 50 rows per page with Newer/Older navigation. Subtitles show the total count. Previously it loaded everything at once, which was fine when "everything" was 30 entries and less fine when it was 3,000.
- Save & Exit — new option in the split save dropdown on page, asset, and partial editors. Saves and returns you to the Pages list. For when you've made your change and don't need to stare at the editor anymore.
- Sitemap editor save button — moved to the navbar as a split button with Save & Exit. The old inline buttons at the bottom of the page are gone. Consistency is a feature.
public/images/directory — a new home for developer template images (icons, backgrounds, design elements) that aren't meant to be CMS-managed. Created by the installer. Documented in Directory Structure and Image Attributes. Your hero background goes inuploads/; your decorative SVG arrows go inimages/.update.phpin security checklist — the dashboard now auto-detectsupdate.phpon disk, rated High risk, same as theinstall.phpcheck. The environment report includes it too. Because the updater can overwrite your entire CMS, and leaving it on the server after you're done is the kind of thing that keeps security auditors employed.
Bug Fixes
- Activity page returned a 500 error. The count queries called
Database::fetch(), which doesn't exist. The method is calledfetchOne(). It has always been calledfetchOne(). In our defence,fetch()is what every other database library calls it.
Legal & Compliance
- Privacy policy updated for GDPR compliance — legal basis for processing, international transfer disclosure, data portability rights, right to withdraw consent, right to complain to a supervisory authority, and a proper data controller section. Umami (analytics) and Deftform (contact forms) disclosed as third-party processors.