Files
aurora-wordpress-admin/CHANGELOG.md
NixReaperandClaude Opus 4.8 e2db2ad4b5 Snapshot synced v3.6.1 working state as baseline
Machine-sync snapshot: the working tree carried the full 3.0.0-3.6.1 arc
(licensing/FileManager/DatabaseExplorer removal, all new replacement screens
and REST controllers) that had not been committed on this machine. Captured
here as a safety baseline before starting the Adminify-parity revamp.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-12 08:41:18 -05:00

1294 lines
75 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Changelog
All notable changes to Aurora Admin are documented here, one entry per
version bump. Starts at 2.1.0 — earlier history (the theming rebuild,
window chrome/taskbar, Site Editor support, etc.) lives in `git log` and
`PROJECT_STATUS.md`, not reconstructed here.
## [3.6.1] - 2026-07-11
### Fixed
- **Second round of misplaced `phpcs:ignore` fixes**, found via another fresh Plugin Check report after 3.5.1's fix. Turns out `WordPress.DB.DirectDatabaseQuery.DirectQuery`/`NoCaching` and `WordPress.DB.PreparedSQL.NotPrepared`/`InterpolatedNotPrepared` are attributed by PHPCS to *different* lines within the same multi-line `$wpdb->get_results()`/`get_row()` call — `DirectQuery`/`NoCaching` to the outer call's opening line, `NotPrepared` to whichever inner line the interpolated table name physically appears on. 3.5.1's fix moved every ignore comment down to cover the inner line (fixing `NotPrepared`) but broke coverage of the outer line (`DirectQuery`/`NoCaching` came back) for four calls in `MenuCreatorData.php` and `Notices.php`/`NoticesData.php` that hadn't already been collapsed to a single line. Fixed by collapsing those four remaining multi-line calls too — consistent with the single-line-call pattern used everywhere else in the 3.5.1 fix, which a single ignore comment reliably covers regardless of which sniff PHPCS attributes to that line. Re-verified with the automated scan plus live REST-dispatch functional round-trips.
## [3.6.0] - 2026-07-11
### Changed
- **Site Backup extracted into a standalone companion plugin,** at the
user's explicit request, following the same pattern as File Manager
(3.4.0) and Database Explorer (3.5.0) — proactive risk reduction, not a
WP.org rejection: restoring a backup means writing arbitrary files back
into the site, the same class of thing that's kept file-management
functionality out of Aurora Admin proper.
- The entire `admin/src/Backup/` tree (job/schedule storage, DB dumper,
file archiver, the state-machine engine, wp_cron safety net, and all
four storage providers — local, S3-compatible, Google Drive, plus the
provider interface/factory) and all four Backup REST controllers
moved, unmodified in behavior, into a brand-new sibling plugin:
**Aurora Site Backup** (`aurora-site-backup.php`, own bootstrap, own
REST namespace `aurora-site-backup/v1`, own Vite build). Since this
plugin needs its own settings storage independent of Aurora Admin
(S3 credentials, Google Drive OAuth tokens), a small standalone
`Options\Settings` class was added, backed by its own
`aurora_site_backup_settings` option — same minimal shape as Aurora
Admin's own Settings class, just a separate store.
- `CompanionPluginData.php`'s `PLUGINS` config array (already
generalized in 3.5.0) gained a `site-backup` entry — no new PHP
needed beyond the config line. The Settings → Add-ons screen now
lists all three companion plugins.
- Verified live via a full `rest_do_request()` dispatch: created a real
backup job, stepped it through the DB-dump phase, and canceled it —
end-to-end confirmation the extracted code still works, not just that
it loads without fatals.
- **Deployment gap, same as the other two companion plugins**: the
`aurora-site-backup.zip` is not yet hosted at
`https://aurora.auroradragon.studio/aurora-site-backup.zip` — the
Add-ons "Install & Activate" button for it will fail with a fetch
error until that's deployed (needs explicit go-ahead).
## [3.5.1] - 2026-07-11
### Fixed
- **Misplaced `phpcs:ignore` comments from the 3.3.2 compliance pass, found via a fresh Plugin Check report.** WPCS's `phpcs:ignore` suppresses violations reported on the exact next physical line — but several annotations added in 3.3.2 sat above the *opening* line of a multi-line `$wpdb->get_results()`/`get_row()`/`get_var()` call (e.g. `$rows = $wpdb->get_results(`), while PHPCS actually attributes the violation to whichever line further down inside that call contains the interpolated table-name/column tokens. Those suppressions were silently never taking effect. Fixed across `MenuCreatorData.php`, `NoticesData.php`, `Notices.php`, `BackupSchedules.php`, `BackupJobs.php`, `MediaFolders.php` (the largest cluster — collapsed most multi-line query calls to a single physical line so one ignore comment reliably covers every sniff PHPCS reports on that line, added a `MediaFolders::name_taken()` helper to replace an inline `while()` condition that couldn't otherwise be annotated), `DbDumper.php`, `AnalyticsData.php`, and `MediaData.php`'s `post__not_in` VIP-advisory comment. Also added a genuinely missing annotation on `Analytics::record()`'s `$wpdb->insert()` call. Verified with an automated scan confirming every `$wpdb->` call with dynamic table/query construction is now immediately preceded by its ignore comment.
- **Real bug found during verification, unrelated to Plugin Check**: `MenuCreatorData::create()` built its internal follow-up "fetch the row I just inserted" request as `new \WP_REST_Request("GET", "", ["id" => $wpdb->insert_id])` — but `WP_REST_Request`'s third constructor argument doesn't populate what `get_param()` reads, so `get_one()` always received a null id and returned "Menu not found" instead of the newly created menu. Confirmed via a full `rest_do_request()` dispatch (not just a direct function call) that this reproduced through the real REST route. Fixed by building the request with `set_url_params()`, WP core's own documented pattern for this.
- Confirmed the `compressed_files`/`hidden_files`/`unexpected_markdown_file` findings in the Plugin Check report (build zips, `.gitignore`, `.claude/`, `PROJECT_STATUS.md`) are scanner noise from scanning the whole development repository, not the actual submission zip — verified via `unzip -l` that none of those paths exist in `wporg-submission/aurora-admin.zip`.
## [3.5.0] - 2026-07-11
### Changed
- **Database Explorer extracted into a standalone companion plugin,** for
the same reason and following the exact same pattern as File Manager
in 3.4.0: direct database-query tooling (browsing tables, running
free-form SELECT/SHOW/DESCRIBE/EXPLAIN queries from wp-admin) is
treated by WordPress.org's Plugin Directory review the same way as
file-management functionality — outside what it accepts in a
directory-listed plugin. Rather than wait for a rejection to confirm
it or delete the feature outright:
- `admin/src/DatabaseExplorer/DatabaseExplorerData.php` and its Vue
UI (plus the shared `AppListShell`/`AppListItem` list-detail layout
it's built on) moved, unmodified in behavior, into a brand-new
sibling plugin: **Aurora Database Explorer**
(`aurora-database-explorer.php`, own bootstrap, own REST namespace
`aurora-database-explorer/v1`, own Vite build). Works completely
standalone, verified live via direct HTTP request with a
server-generated auth cookie — the admin page renders its mount
div and enqueued script correctly, and the REST namespace is
registered and reachable.
- `CompanionPluginData.php` was generalized from a single
file-manager-only class into a small `PLUGINS` config array keyed
by slug, so both File Manager and Database Explorer (and any future
companion plugin) share the same install/activate/status REST
routes and `Plugin_Upgrader`-based installer logic — adding a new
companion plugin going forward is a config entry, not new PHP.
- The **Settings → Add-ons** screen now lists both companion plugins.
- **Deployment gap, same as File Manager**: the Database Explorer zip
is not yet hosted at
`https://aurora.auroradragon.studio/aurora-database-explorer.zip` —
the Add-ons "Install & Activate" button for it will fail with a
fetch error until that's deployed (needs explicit go-ahead, same as
any other production deploy).
## [3.4.0] - 2026-07-11
### Changed
- **File Manager extracted into a standalone companion plugin.** WordPress.org's
second review response was unambiguous: the entire category of
file-management/code-editing functionality isn't accepted into the
Plugin Directory, regardless of the restrictions added in v3.2.1 —
the feature had to come out of Aurora Admin entirely for the
submission to be reconsidered. Rather than delete the functionality:
- `admin/src/FileManager/FileManagerData.php` and its three Vue
components moved, unmodified in behavior, into a brand-new sibling
plugin: **Aurora File Manager** (`aurora-file-manager.php`, own
bootstrap, own REST namespace `aurora-file-manager/v1`, own Vite
build). It works completely standalone — verified live with Aurora
Admin fully deactivated.
- The Theme Editor → File Manager redirect (`AppearancePages.php`) is
gone; visits to `theme-editor.php` now fall through to WordPress
core's own Theme Editor unmodified, which already respects
`DISALLOW_FILE_EDIT` itself.
- A new **Settings → Add-ons** screen offers one-click installation of
Aurora File Manager, wrapping `Plugin_Upgrader::install()` — the
same core API the native Plugins → Add New "Install Now" button
uses — pointed at a fixed, non-client-supplied HTTPS zip URL. No
settings/data are shared between the two plugins; Aurora Admin has
zero remaining code that touches the filesystem beyond its own
Site Backup archives.
## [3.3.2] - 2026-07-11
### Fixed
- **WordPress.org Plugin Check compliance pass** — a full re-run of the
official Plugin Check tool against the whole codebase (279 findings
across 26 files) surfaced several real gaps beyond the earlier v3.0.1/
v3.0.2/v3.2.1 passes, all now fixed:
- `readme.txt`'s `Stable tag` had drifted to 3.2.1 while the plugin
itself moved through 3.3.0/3.3.1 — a real mismatch that can prevent
WordPress.org from serving the correct download to users. Now kept
in lockstep with every version bump.
- `Analytics::country()` read `$_SERVER[$h]` directly for an
`empty()`/`strlen()` check before the existing sanitized copy was
computed — fixed by sanitizing once up front and using that
everywhere, rather than touching the raw superglobal at all.
- `AuroraAdmin.php`'s `defined("ABSPATH") || exit()` guard sat after
49 `use` import statements — functionally fine, but far enough into
the file that at least one automated file-access-protection scanner
didn't detect it. Moved to immediately after the `namespace`
declaration, this file's one deliberate departure from the rest of
the codebase's "guard after imports" convention.
- Roughly 230 `WordPress.DB.DirectDatabaseQuery`/`PreparedSQL`/
`UnescapedDBParameter` findings across every custom-table feature
(Site Backup, Media Folders, Activity Log, Analytics, Menu Creator,
Notices, Database Explorer) — verified each one is safe (internal
`$wpdb->prefix`-based table names or already-validated/escaped
identifiers, never raw user input) and annotated with the justified
`phpcs:ignore` the review process expects, matching the standard
already established for Database Explorer's query runner.
- A handful of `fread()`/`fwrite()`/`fclose()` calls adjacent to an
already-annotated `fopen()` were still individually flagged — WPCS
suppression is strictly per-line, so each sibling call in Site
Backup's file/S3/Google Drive streaming code now has its own
annotation too.
- Several `RuntimeException` messages interpolating internal (never
user-supplied) values were flagged as unescaped output — annotated
as exception messages, not HTML/browser output, the same
false-positive class WPCS is known to flag on exception
constructors.
## [3.3.1] - 2026-07-11
### Fixed
- **Settings page: nav and content now scroll independently.** Previously
the left category list and the right content panel scrolled together
as one long page — scrolling down the nav to reach a category near the
bottom (e.g. "Modern Image Formats") and clicking it left the content
panel's top scrolled out of view above the fold, requiring an extra
scroll back up to actually see it. Both panels now have their own
fixed-height scroll region, and switching categories resets the
content panel's scroll position to the top regardless of the nav's own
scroll position.
## [3.3.0] - 2026-07-11
### Added
- **Modern Image Formats** — a new Settings section that automatically
generates a WebP or AVIF sibling for every size WordPress produces from
a newly uploaded JPEG/PNG, and (optionally) rewrites `<img>` markup
into a `<picture>` element offering the modern format with the original
as a fallback source. "Automatic" output format prefers AVIF, falling
back to WebP, based on what the server's image editor actually
supports (`wp_image_editor_supports()`) — gracefully generates nothing
on a host that supports neither, rather than erroring. Purely additive:
original JPEG/PNG files are always kept, nothing about the attachment's
own metadata or file is changed, and generated files are cleaned up
automatically when the attachment is deleted. Only affects new
uploads — existing library images need a regenerate-thumbnails-style
tool to pick up the feature.
- Built entirely on documented WordPress core hooks
(`wp_generate_attachment_metadata`, `delete_attachment`,
`wp_editor_set_quality`, `wp_content_img_tag`,
`wp_get_attachment_image_html`) and `WP_Image_Editor::save()`'s
documented mime-type-conversion parameter — no third-party plugin
code, no Composer dependency.
- Verified live end-to-end: uploaded a real JPEG, confirmed a smaller
AVIF sibling was generated and recorded in attachment metadata,
confirmed a post embedding the image rendered a correct
`<picture><source type="image/avif" srcset="...">` wrapper, and
confirmed both the original and generated files were removed with no
orphans left behind after deleting the attachment.
## [3.2.1] - 2026-07-11
### Changed
- **File Manager: PHP/executable editing removed.** Rejected from the
WordPress.org Plugin Directory because File Manager let a
`manage_options` user open and overwrite *existing* `.php` files
in-place with immediate execution effect — no different in risk from
WordPress core's own Theme/Plugin editor, which core lets hosts disable
via `DISALLOW_FILE_EDIT` specifically because of that risk. File Manager
previously blocked creating/uploading/renaming-to executable file
types, but that same list wasn't enforced on *editing an existing* file
of that type — closed now: opening or saving any file matching
`BLOCKED_EXTENSIONS` (php, phtml, cgi, py, sh, and others) is refused.
File Manager also now honors `DISALLOW_FILE_EDIT` (blocks all editing)
and `DISALLOW_FILE_MODS` (blocks all create/upload/rename/delete/copy/
move/write operations), the same host-level lockdown constants
WordPress core's own file-modification flows already respect. Browsing,
downloading, and viewing file info are unaffected.
## [3.2.0] - 2026-07-11
### Added
- **Google Drive remote storage** for Site Backup — a second remote
storage option alongside S3-compatible, via OAuth 2.0. Each site
connects its own Google Cloud OAuth client (Client ID + Secret, pasted
into Settings) since a free, publicly-distributed plugin can't ship a
shared client secret; scope is the narrow `drive.file` (only
files/folders Aurora itself creates). Resumable upload and
`Range`-header chunked download against Drive API v3 mirror the
bounded-per-step design S3's multipart upload already established — no
changes to the job engine itself were needed, confirming the
storage-provider abstraction's original design goal. Shipped
code-complete and protocol-verified (request shapes checked against
Google's published API reference, and live-tested against Google's real
OAuth endpoints with placeholder credentials to confirm genuine API
errors come back); a full live "Connect" round-trip is left for the
user to verify once they have a real Google Cloud OAuth client and
HTTPS on the target site.
## [3.1.0] - 2026-07-11
### Added
- **Site Backup** — a new free sub-app: full site backup (database +
wp-content) and restore-in-place, on-demand or on a recurring daily/
weekly/monthly schedule, storing archives locally or on any
S3-compatible remote (AWS S3, Backblaze B2, Cloudflare R2, DigitalOcean
Spaces — one implementation via a configurable endpoint, no vendor
SDK, SigV4 signing implemented directly and verified against AWS's own
published test vectors).
- Built on a new resumable job-processing engine (`BackupEngine` +
`BackupJobs`) — the first background-job infrastructure in this
codebase. Every backup/restore step is bounded (row/file/byte count
and a wall-clock budget) so a multi-GB site never risks a single
request's timeout or memory limit. Two trigger paths converge on the
same step function: foreground polling from the Vue UI (fast
feedback while a tab is open) and a `wp_cron` safety net
(`BackupCron`) that keeps a job moving with no browser involved —
closing the tab mid-backup, or a scheduled backup with nobody
watching, both still reach completion.
- Database export/import (`DbDumper`) — paginated dump with schema
captured per-table (`DROP TABLE IF EXISTS` + `CREATE TABLE`, so
restore is deterministic and re-runnable from scratch), chunked
restore via a byte-cursor into the dump file. Aurora's own
backup-tracking tables are excluded from what gets backed up — a real
bug caught during testing, where restoring a snapshot of those tables
was overwriting the very job row driving the restore mid-flight.
- File archiving (`FileArchiver`) — a manifest of every file under
`wp-content/` (excluding the backup storage directory itself and,
structurally, `wp-config.php`, which lives outside `wp-content/`
entirely), zipped/extracted in bounded batches via `ZipArchive`,
reopened and appended to across isolated requests. Every extracted
entry is containment-checked (zip-slip protection) before anything is
written — verified live with a deliberately crafted malicious archive
entry, confirmed rejected.
- Local storage hardening — `wp-content/uploads/aurora-backups/` gets
WordPress core's own `index.php`/`.htaccess` stub pair; the real
protection is that every archive read goes through a
`manage_options`-gated REST route streaming the file through PHP,
never a guessable direct URL.
- Recurring schedules with count-based retention — a scheduled backup
prunes older ones (both the job row and its stored archive, local or
remote) down to the schedule's configured retention count.
- v1 deliberately does not include cross-domain migration (Duplicator's
own headline feature — generating an `installer.php` for a totally
different host) or restoring from an arbitrary uploaded archive
(restore only ever sources from Aurora's own backup history) — both
are real scope/risk decisions, not oversights.
- Live-verified end-to-end on the dev site, including catching and fixing
two real bugs along the way: a job's `phase` column wasn't updated to
a clean terminal value on completion (stale "Archiving files…" label
after a backup had actually finished), and a data-corruption bug where
manually pre-escaping newlines before `$wpdb->_real_escape()` (which
already does this) caused restored text fields to contain literal
backslash-n characters instead of real line breaks.
## [3.0.2] - 2026-07-11
### Fixed
- Plugin header validation error from the WordPress.org submission checker:
`Plugin URI` and `Author URI` were both `https://auroradragon.studio`,
and WP.org requires them to be distinct (one identifies the plugin, the
other the author). `Plugin URI` now points to
`https://aurora.auroradragon.studio` (the plugin's own marketing site);
`Author URI` stays on the studio's root site.
## [3.0.1] - 2026-07-11
### Fixed
- **WordPress.org Plugin Check compliance pass.** Ran the official Plugin
Check tool ahead of submission and fixed every real issue it surfaced
(257 flagged items, several dozen real):
- `WidgetsData.php`: replaced the forbidden `wp_get_sidebars_widgets()`
(internal-only core function) with a local helper reading the
`sidebars_widgets` option directly.
- `FileManagerData.php`: converted every raw filesystem call
(`move_uploaded_file()`, `mkdir()`, `rename()`, `unlink()`, `rmdir()`,
`is_writable()`, `readfile()`) to the `WP_Filesystem` API, which works
across hosting setups that don't allow direct filesystem access.
`move_uploaded_file()`'s upload-tampering check is preserved via an
explicit `is_uploaded_file()` call before handing off to
`WP_Filesystem::move()`.
- `BugReportData.php`: `parse_url()` → `wp_parse_url()`.
- `SubApps.php`: fixed a translation-function rule violation (page
titles now wrapped in `__()` at definition instead of being passed as
a variable to `__()` at the call site) and added justified
nonce-check suppressions on two read-only routing checks.
- Fixed missing `wp_unslash()`/sanitization on a handful of superglobal
reads (`Analytics.php`, `Shell.php`, `AnalyticsData.php`) and added
missing translator comments for two placeholder strings.
- Annotated dynamic-table-name SQL queries across `MediaFolders.php`,
`AnalyticsData.php`, `DatabaseExplorerData.php`, `NoticesData.php`,
`MenuCreatorData.php`, and `ActivityLogData.php` with justified
`phpcs:ignore` comments — every flagged table name comes from an
internal `$wpdb->prefix`-based helper, never user input, and table
identifiers can't go through `$wpdb->prepare()`'s value placeholders
(WordPress core has no API for arbitrary custom tables).
- Removed the stale `Domain Path: /languages/` plugin header (pointed
at a folder that's never existed — see 2.x-era removal of the fake
"13 languages" marketing claim).
- All changes live-verified against the LocalWP install: File Manager's
full create/rename/delete cycle exercised directly against the REST
routes (folder create, file create, rename, soft-delete, empty
trash), Widgets' sidebar-read endpoint, Database Explorer's table
browsing, and Media Folders' filtered listing all confirmed working
with zero console/PHP errors.
## [3.0.0] - 2026-07-06
### Removed
- **The entire licensing/monetization system.** Aurora Admin is being given
away free on the WordPress.org plugin repository — every feature
previously gated behind a paid license (Role Editor, Activity Log,
Database Explorer, File Manager, Analytics, White Label, Full Admin
Takeover, Media Folders) is now simply available to everyone, no key
required.
- Deleted `admin/src/Licensing/` entirely (`License.php`, `Integrity.php`,
the file-tamper-detection system that only existed to protect license
gating from bypass) and `bin/build-integrity-manifest.php`.
- Deleted the "Pro feature" upgrade-prompt UI (`UpgradeNotice.vue`,
`locked-feature.js`) and every lock badge in the sidebar.
- Removed the License key field and the dev-only "Simulate active
license" toggle from Settings — there's nothing left to license or
simulate.
- `license_key` and `dev_license_override` are actively purged from any
site's stored settings on upgrade (same cleanup pattern used for the
earlier Remote Site Switcher removal), not just hidden from the UI.
- Report a Bug still works, now via a direct call to the feedback
endpoint instead of going through the license-status layer.
- This is a clean split from the commercial `auroraplugins.com` service
(beta licenses, Pro purchases) — that server-side system is untouched
and unaffected by this change, it's simply no longer wired into this
codebase.
## [2.27.0] - 2026-07-06
### Added
- Restored the dev-only license override (`AURORA_ADMIN_DEV_MODE` wp-config.php
constant + Settings > Developer > "Simulate active license" toggle),
reversing the 2.25.0 removal. Requested explicitly for local testing on
admin.local — lets Pro features be exercised without a real license key.
**This must be stripped again before the next release/beta build** — same
standing rule as before, now re-documented in `PROJECT_STATUS.md`. Only
has any effect when `AURORA_ADMIN_DEV_MODE` is defined truthy in a given
install's own `wp-config.php`, never set in the repo or any built zip.
## [2.26.0] - 2026-07-05
### Added
- "Report a Bug" page (Aurora sidebar section), a real WordPress admin page
(`aurora-admin-bug-report`, registered via `SubApps.php` like Menu
Creator/Admin Notices) with a single free-text field plus auto-filled,
read-only diagnostics (plugin version, WordPress version, PHP version,
active theme, last 4 characters of the license key — not the full key).
Submits to a new `POST aurora-admin/v1/bug-report` REST route
(`admin/src/Rest/BugReportData.php`), which forwards to a new
`License::submit_bug_report()` method that calls the license server.
- **License server** (`aurora-licensing` plugin, not this repo): added a
`wp_aurora_bug_reports` table (DB version 3 → 4, same version-gated
migration idiom) and a new public `POST /report-bug` REST route,
storing the description plus whatever license key/domain/diagnostics
fields were sent. Deliberately a plain `wpdb->insert` with no admin UI
yet — reports are reviewable via `wp db query` for now; a dashboard can
come later if report volume justifies it.
- Verified live end-to-end (not mocked): submitted a real report through
the actual Report a Bug page, confirmed it landed correctly in
`wp_aurora_bug_reports` on the production server, then deleted the two
test rows (one from an earlier curl smoke test) by exact ID.
## [2.25.0] - 2026-07-05
### Removed
- Dev-mode license override (`AURORA_ADMIN_DEV_MODE` wp-config.php constant,
Settings > Developer category's toggle, and every call-site in
`License::is_active()`/`is_feature_available()`) — this was a
build-time-only escape hatch for testing paid features without a live
license, marked in `PROJECT_STATUS.md` as "MUST BE REMOVED BEFORE A
RELEASE BUILD" since it shipped in the plugin zip. Real beta testers are
about to install real builds, so it had to go before that zip goes out.
Paid features now gate purely on `License::is_active()` for everyone,
no exceptions.
## [2.24.0] - 2026-07-05
### Added
- Beta tester license support, end to end. User asked for 10 new 30-day
beta licenses plus a way to flag/display beta status in the plugin.
- **License server** (`aurora-licensing` plugin on the live
`auroraplugins.com` box, not this repo): added an `is_beta` column to
`wp_aurora_licenses` (migrated in place via a `plugins_loaded`-hooked
check, since the file was updated without re-activation — same
version-gated migration idiom this plugin's own `ActivityLog.php`
etc. use), a `wp aurora-lic generate-beta <count> [--days=<n>]
[--max-sites=<n>]` WP-CLI command (not a public REST route —
generating free licenses has to stay admin/shell-only), and `is_beta`
added to `/validate` and `/activate`'s response payloads. Generated
the 10 requested keys (30-day expiry, 1 site each) — see the
conversation for the actual key list; not duplicated here since this
file ships to customers.
- **Client** (this plugin): `License::is_beta_tester()` reads the
server's `is_beta` flag (already flowing through the existing
`status()` cache — no new caching logic needed). Settings > General's
license status line now shows "Beta Tester license active — expires
in N days (date)" instead of the normal "License active (plan)" text
when a beta key is active. A beta license is a real, fully-functional
`is_active()` license like any other — this is a display flag only,
not a separate gating path.
- Verified against the real production license server, not a mock:
generated a real key via the new CLI command, activated it from the
dev site's actual Settings page, confirmed the badge and expiry
countdown render correctly, then cleared the key (deactivating it
server-side via the existing `on_settings_updated` flow).
## [2.23.0] - 2026-07-05
### Added
- Full field-set parity for Writing/Reading/Discussion native Settings
screens, closing the gap flagged in 2.22.0's audit.
- **Writing**: `Default Post Category` and `Default Post Format` are now
real dropdowns (populated from `get_categories()`/
`get_post_format_strings()`) instead of raw numeric IDs. Added
`use_smilies` — registered in core's own REST settings schema but
never actually added to this screen.
- **Reading**: `Homepage`/`Posts Page` are now page-title dropdowns
(via `get_pages()`) instead of raw page IDs, and only appear once
"Homepage Displays" is set to "A static page" — matching native
WordPress's own conditional reveal. New generic `showIf` field
property in `NativeSettings.vue` (`{key, equals}`) drives this,
reusable by any future field.
- **Discussion**: went from 2 fields to full native parity (~24 fields:
pingback/comment defaults, moderation rules, threaded-comment
settings, pagination, three notification-email toggles, comment
moderation/disallowed-keys blocklists, and avatar settings). Core's
REST settings schema (`register_initial_settings()`) only ever
exposed 2 of Discussion's real options — everything else needed a new
dedicated controller, `DiscussionData.php`, doing raw `get_option()`/
`update_option()` reads/writes against an explicit key allowlist (same
pattern as `PermalinksData.php`, generalized to many keys instead of
one).
- New `textarea` field type in `SettingsField.vue` for the two
moderation-keys blocklist fields (multi-line plain text — nothing
else on any settings screen needed one until now).
- Verified live: dropdowns populate with real data, conditional fields
show/hide correctly, and a real save-then-reload round trip on
Discussion (toggled a setting off, saved, reloaded fresh, confirmed
the change persisted) — not just an optimistic-UI check.
## [2.22.0] - 2026-07-05
Full plugin audit, requested after the plugin-delete crash raised the
question of what else might be broken or incomplete. Checked every
sidebar screen live (console + PHP error log at each stop), re-verified
the license-activation claim from last session against the actual code,
and closed the one remaining documented security gap.
### Fixed
- **REST-bypass gap closed for Role Editor, Activity Log, and Database
Explorer** — their REST controllers only checked
`current_user_can('manage_options')`, not the license, so a direct REST
call could reach paid functionality (including Database Explorer's raw
SQL runner) with no license at all. Same fix already applied to File
Manager/Media Folders earlier this session, just never circled back for
these three. Verified every other paid-feature REST controller (Themes,
Nav Menus, Widgets, Permalinks, Analytics, White Label) already had the
check — this was the last of them.
- **Permalinks screen was missing the standard structure presets** (Plain,
Day and name, Month and name, Numeric, Post name) — only the raw
"Custom Structure" text field existed, which is the one part of that
screen almost nobody actually uses by hand. Added the five presets as
radio options with live example URLs, auto-detecting which preset (or
"Custom") matches the currently-saved structure.
### Verified (no changes needed)
- Clicked through Dashboard, Posts, Pages, Media, Users, Comments,
Plugins, all Aurora sub-apps (Menu Creator, Admin Notices, Role Editor,
Activity Log, Database Explorer, File Manager), Media Upload, Add New
User, Profile, all 5 native Settings screens, and several Aurora
Settings categories (General, White Label, Analytics) — no fatals, no
new console errors anywhere in the pass.
- Confirmed License.php's activation/validation flow is real (calls
`auroraplugins.com`'s REST API, caches with TTL, tamper-checks) —
corrected a stale "still a placeholder" claim from earlier this session
and removed the outdated doc line that caused it.
### Known gaps (not fixed — flagged for a decision, not silently patched)
- Writing/Reading/Discussion native Settings screens expose only a
handful of fields each, several as raw text/numeric-ID inputs where
native WordPress has dropdowns (post category, post format, homepage
page picker) — functional but poor UX, and Discussion in particular is
missing most of its native moderation options. Larger effort than the
Permalinks fix; needs new endpoints (categories list, post formats) and
new field types, not just a values tweak.
- Comments list's pagination footer shows "0 to 0 of 0" despite the "All"
tab correctly showing a count — cosmetic, not investigated further.
## [2.21.2] - 2026-07-05
### Fixed
- Deleting a plugin from Aurora's Plugins page ("major issue" per user
report) fataled the whole site with WordPress's generic "There has been
a critical error" page instead of actually deleting anything. Root
cause, found in the PHP error log: `PluginsData::delete_plugin()` called
core's `delete_plugins()`, which internally calls
`request_filesystem_credentials()` (WP_Filesystem setup) — that function
lives in `wp-admin/includes/file.php`, which a REST API request doesn't
load the way a normal wp-admin page load does, and `delete_plugin()`
only required `plugin.php`. `update_plugin()` right below it already
required `file.php` for the same underlying reason; `delete_plugin()`
just never got the same fix. Verified live: deleted a real plugin
(WP File Manager) end to end with no fatal, "Plugin deleted." message
shown correctly, confirmed no new error logged.
## [2.21.1] - 2026-07-05
### Fixed
- Sidebar section accordion closed the wrong section on "hidden child"
native screens (theme-install.php, media-new.php, user-new.php, ...).
User: expand Appearance, click "Add New Theme", and the Appearance
section collapsed while the "AURORA" section at the bottom opened
instead — this was a different bug than the overlay-covers-the-sidebar
issue fixed in 2.21.0, this was specifically about which section stays
expanded. Root cause: `Sidebar.vue`'s accordion decides the open section
by matching the current URL against each menu item's own submenu array
(`MenuSerializer::serialize()`, scraped from WP's `$submenu` global) —
but these screens are deliberately absent from `$submenu` (WP's own
native left menu doesn't list "Add New Theme" as a link either, only
"Themes"/"Customize"/"Editor"), so no section ever matched and the
accordion fell through to its Aurora-expanded default.
- WP core itself solves this exact problem for its own native menu
highlighting via a `$parent_file` global, set directly in each of
these pages' own PHP (`theme-install.php` sets
`$parent_file = 'themes.php'`, `media-new.php` sets `'upload.php'`,
`user-new.php` sets `'users.php'`) — reused that instead of
hardcoding a list of "hidden child screen" mappings ourselves.
`Shell::print_mount()` now reads it and passes it through as
`activeParentFile`; `Sidebar.vue`'s `isSectionActive()` treats a menu
item as active when its id matches, alongside the existing exact-URL
checks. General fix — covers any current or future WP core (or
third-party plugin) screen using this same convention, not just the
three confirmed here.
## [2.21.0] - 2026-07-05
### Changed
- The theme-install.php "Preview & Install" overlay (clicking a theme card
under Appearance > Themes > Add New Theme) is now framed like the block
editor window instead of covering Aurora's sidebar/toolbar entirely.
User reported "it closes the menu" — confirmed this is WP core's own
native behavior (the fullscreen overlay covers the native admin menu
too, on a totally vanilla install), not a regression, but agreed a
framed treatment was worth the risk for visual consistency with how the
block editor is already handled.
- `.wp-full-overlay` (WP core's overlay component, shared with the
Customizer — only `.theme-install-overlay` specifically is touched,
not `.wp-full-overlay` generally) is reframed to Aurora's standard
inset box: the overlay itself plus its two independently
`position: fixed` children (`.wp-full-overlay-sidebar`,
`.wp-full-overlay-main`) each need the same left/top/bottom math
applied directly, since fixed descendants don't inherit an ancestor's
padding-box positioning the way absolutely-positioned ones do.
`margin-left` on `.expanded`/`.collapsed` mirrors WP core's own three
responsive breakpoints with Aurora's frame offset added on top, so the
sidebar collapse/expand slide animation keeps working unmodified.
- Root-caused and fixed two real bugs found while building this: (1)
opening the overlay adds `body.full-overlay-active`, and WP core's own
CSS sets `body.full-overlay-active { visibility: hidden }` with only
the overlay itself set back to visible — Aurora's entire shell
inherited that hidden visibility too (nested in body), which is what
silently erased the sidebar/toolbar instead of just having them
covered. Fixed with one override re-enabling visibility for Aurora's
own root specifically. (2) The overlay's own `height: 100%` (WP core)
combined with our `top`+`bottom` insets was the exact same
over-constrained-box bug already known from the block editor's
`.interface-interface-skeleton` — same `height: auto !important` fix.
- The traffic-light winbar needed a permanently-running `MutationObserver`
rather than a one-shot check: the overlay doesn't exist in the initial
DOM (WP core creates it lazily on first click), and every subsequent
theme selection fully re-renders the overlay's contents, wiping out
anything inserted into it — confirmed live (bar present after the
first click, gone after picking a second theme) before switching to an
idempotent "ensure inserted" observer callback.
- Verified live end-to-end: opening via natural clicks (not just direct
URL navigation), switching between themes with the prev/next arrows
(triggers the content-destroying re-render), closing and confirming
the sidebar/toolbar return correctly, no console errors at any step.
## [2.20.2] - 2026-07-05
### Fixed
- Appearance > Themes was missing two capabilities the native Themes
screen has: browsing/installing themes from the WordPress.org directory,
and uploading a theme zip. `AppearancePages.php`'s `load-themes.php`
hijack never passed a link to `theme-install.php` (only `customizeUrl`),
so there was no way to reach either from Aurora's Themes page.
`Themes.vue` gains an "Add New Theme" button (next to Customize) linking
to native `theme-install.php`, gated on `current_user_can('install_themes')`
— that native screen already has both the WordPress.org browser (Popular/
Latest/Block Themes/Favorites/Feature Filter tabs, search) and an
"Upload Theme" button built in, so neither needed rebuilding. Same
reuse-native-flow-for-add pattern already used for Plugins' `addUrl`
(`plugin-install.php`) and Media's `newMediaUrl` (`media-new.php`).
## [2.20.1] - 2026-07-05
### Fixed
- Font Library (`font-library.php`) rendered with a jarring mix of Aurora's
dark frame around a completely white, near-unreadable content area (light
text on a white card). WP 6.5+ built this screen on `@wordpress/
components`' newer "boot-layout" primitives instead of classic postbox
markup, so `native-dark.css`'s existing postbox/card rules never matched
its DOM. Added targeted dark-theme coverage for the screen's stable,
hand-authored class names (`.boot-layout-container`, `.boot-layout__stage`,
`.admin-ui-page`, `.admin-ui-navigable-region`, `.admin-ui-page__header`,
`.font-library-page__tab-panel`, `.font-library__tabpanel-layout`,
`.font-library__footer`, `.font-library__fonts-list-item`,
`.font-library__font-card`) — deliberately not touching this screen's
Emotion-generated hash classes (e.g. `css-qi2azi`), which aren't stable
across WP builds.
- Regenerated `admin/src/Licensing/integrity-manifest.json` and rebuilt
`dist/aurora-admin-v2.zip` from a fresh staging copy — the protected
files' checksums (`Integrity::is_tampered()`'s manifest) had gone stale
after this session's edits to `aurora-admin.php`, `Shell.php`, and others.
Also excluded `.claude/` from the staged copy — the internal tooling
config had been getting bundled into the distributable zip.
## [2.20.0] - 2026-07-05
### Added
- Media Folders (paid — `License::PAID_FEATURES["media-folders"]`): a
folder tree for the Media Library, duplicating the organizing capability
of the FileBird Pro plugin at the user's request, with its own
independent schema (`wp_aurora_media_folders` /
`wp_aurora_media_folder_items`, distinct from FileBird's own `wp_fbv*`
tables so both can coexist) rather than importing FileBird's data.
- New `MediaFolders.php` model (create/rename/delete/reparent, one folder
per attachment) + `MediaFolderData.php` REST routes
(`/media-folders`, `/media-folders/assign`), every route double-gated
(`current_user_can` + `License::is_feature_available`) per this
project's established REST-bypass-hardening pattern.
- `Media.vue` gains a folder sidebar (`MediaFolderTree.vue`): create,
rename, delete, right-click context menu, drag-and-drop reparenting,
and drag-and-drop (or a "Move to folder" fallback) to file media into
folders. "All Media" and "Uncategorized" are pinned pseudo-folders.
- The Media Library page itself stays free/ungated either way — only the
folder capability is paid. Unlicensed users keep today's flat grid
unchanged; the sidebar shows an inline `UpgradeNotice` instead of
disappearing or locking the whole page — a new gating shape for this
codebase (previously: whole sub-app free or whole sub-app paid).
### Fixed
- `Shell::is_own_page()` didn't recognize `ScreenHijack`-based pages
(Posts, Pages, Media, Users, Comments — they keep their native URL, no
`?page=aurora-admin` to key off), so it misclassified them as native
pages and loaded `native-dark.css`'s own-page exclusion incorrectly,
letting that stylesheet's generic `a` color rule leak into the real
Aurora UI. Invisible until now because no earlier hijacked page rendered
a link relying on accent/contrast coloring — Media Folders' new
"Enter a license key" CTA in the unlicensed sidebar was the first.
`ScreenHijack::register()` now flags `is_hijack_active()` the moment any
registered hijack activates for the current request, and
`is_own_page()` checks it.
## [2.19.0] - 2026-07-05
### Removed
- Remote Site Switcher: the Settings > General "Remote Sites" repeater and
"Remote Site Switcher Capability" field, and the toolbar dropdown they
fed. User asked what it did; investigation found it was inert scaffolding
— it collected and stored a remote site's URL/username/application
password, but nothing anywhere in the codebase ever used the credentials
(no REST calls, no proxying, no SSO). The toolbar "switcher" was just a
plain link opening the remote site's own `/wp-admin/` in a new tab. User's
call: "doesn't really do much for the software" — removed rather than
built out, since a real cross-site integration wasn't the ask.
- `License::PAID_FEATURES["remote-site-switcher"]` — the feature's paid gate,
now dead code since the feature itself is gone.
### Added
- `Settings::remove_retired_keys()` — actively deletes now-unused settings
keys from the database on `init`, rather than leaving them inertly
stored. Added specifically because `remote_sites` held a real secret
(WordPress application passwords); once the field stopped being read or
written, the safer move was to purge it, not just stop touching it.
## [2.18.0] - 2026-07-05
### Added
- Role Editor: an info icon next to every capability checkbox, with a
hover tooltip explaining what it actually does — user asked directly,
pointing at `level_0`/`level_1` as an example of capabilities with no
explanation anywhere in the UI. New `capabilityDescriptions.js` covers
all ~55 WordPress core capabilities (`edit_posts`, `manage_options`,
`unfiltered_html`, etc.) plus the legacy `level_0`–`level_10` "user
level" system specifically — a pre-2.0 permissions scheme WordPress
still assigns to every default role purely for backward compatibility
with old themes/plugins that check `current_user_can('level_N')`
directly instead of a named capability; core itself hasn't checked a
level in years. Any capability not in the dictionary (e.g. one added by
another plugin) gets a generic "custom capability" fallback rather than
showing nothing. Verified live: hovering `level_0`'s info icon on the
Subscriber role shows the explanation described above.
## [2.17.2] - 2026-07-05
### Fixed
- `AppListShell.vue` (the shared master/detail layout used by Database
Explorer, Role Editor, Activity Log, Admin Notices, and Menu Creator):
the list pane and detail pane shared one page-level scroll instead of
scrolling independently. Selecting an item near the bottom of a long
list (e.g. Database Explorer's 40 tables) left the detail panel's
newly-loaded content rendered off-screen above the current scroll
position — looked like nothing happened until you scrolled all the way
back up. Root cause: the shell only set a `min-height` on the outer
flex container, so it just grew with content instead of giving each
pane a capped height + its own scrollbar. Fixed by giving the container
a real `height` and both panes `height: 100%; overflow-y: auto`, with a
separate mobile-breakpoint treatment (stacked panes get natural height
with their own `max-height` instead of squashing to 50/50). Verified
live: scrolled Database Explorer's table list to the bottom, selected
`wp_users`, confirmed its data appeared immediately without scrolling.
## [2.17.1] - 2026-07-05
### Fixed
- File Manager's right-click context menu never appeared for real users
(it worked in earlier automated testing, which turned out to be
triggering the bug's absence rather than confirming its fix). Root
cause: right-clicking — especially via a trackpad's two-finger tap or
Ctrl-click on macOS — can fire a trailing synthetic `click` event
immediately after `contextmenu` on the same target. The menu's
"click anywhere else closes it" listener was attached synchronously the
moment the menu opened, so that trailing click closed it in the same
tick it appeared — a real race condition, not a rendering bug. Fixed by
deferring the outside-click listener's registration by one tick
(`setTimeout(..., 0)`) so it only starts listening after the opening
event has fully finished. Verified by simulating the exact race
(`contextmenu` immediately followed by `click` on the same element,
same tick) and confirming the menu stays open; also confirmed a genuine
later click still closes it.
## [2.17.0] - 2026-07-05
### Added
- File Manager: **right-click context menu**, mirroring the toolbar exactly
(same functions, just a different trigger) so the two can't drift out of
sync. Right-clicking an item outside the current selection selects just
that item first (standard file-manager behavior); right-clicking within
an existing multi-selection leaves it intact so bulk actions still work
from the menu. Right-clicking empty space shows folder-level actions
(New Folder/File, Upload, Paste) instead. Closes on Escape or a click
anywhere else.
- File Manager: the plain `<textarea>` editor is now a real code editor
(CodeMirror 6 — `codemirror` + per-language packages for JS/TS, CSS,
HTML, JSON, PHP, Markdown, XML, SQL), with syntax highlighting, line
numbers, code folding, and active-line highlighting. Language is chosen
by file extension; unrecognized extensions fall back to plain text
rather than guessing wrong. Themed directly off Aurora's own CSS
variables (not a hardcoded palette) so it matches whatever theme preset
is active instead of looking like a foreign widget. Adds real bundle
weight (~250KB gzipped) — only loaded when the File Manager page itself
is opened, not part of the main admin bundle.
- Verified live: context menu appears correctly for both selection and
empty-space cases with the right actions enabled/disabled, Escape closes
it; PHP syntax highlighting confirmed rendering correctly on a real file
(index.php — comments, strings, keywords all colored, doc-block folding
visible).
## [2.16.0] - 2026-07-05
### Changed
- File Manager's actions consolidated into a single elFinder-style toolbar
(back/forward/up navigation history, new folder/file, upload,
copy/cut/paste, rename, trash, download/edit/info, list/grid view toggle,
fullscreen, search) matching the layout of the "WP File Manager" reference
plugin, replacing the previous split of header buttons + a per-row Actions
column. Selection is now click-to-select (checkbox) + toolbar action,
double-click to open a folder or editable file — the toolbar's buttons
enable/disable based on what's currently selected (e.g. Rename/Info only
with exactly one item selected, Download only for a single file).
- **Move/copy switched from a destination-picker modal to clipboard-style
copy/cut/paste** — select item(s), click Copy or Cut, navigate anywhere
(tree sidebar, breadcrumb, double-click into a folder), click Paste. A
copy clipboard survives repeated pastes; a cut clipboard clears itself
after the first successful paste. Same `/file-manager/bulk` endpoint
underneath, just a different frontend trigger.
- Added a **grid view** (thumbnails/icons in a card layout) as an
alternative to the list view, and a **fullscreen toggle** for the File
Manager panel itself.
- Verified live: copy → navigate into a folder → paste (file appeared in
the new location) → select → trash → confirmed removed; back-navigation
and grid view toggle both confirmed working.
## [2.15.0] - 2026-07-05
### Added
- File Manager closed most of the feature gap against the reference "WP
File Manager" plugin, adding everything that didn't carry the security
risk of the already-excluded zip/unzip and chmod features:
- **Move & copy** between folders (`copy_item`/`move_item`), with
same-containment checks as everything else plus a specific guard
against moving/copying a folder into its own descendant. Name
collisions at the destination get " (2)", " (3)", etc. appended rather
than silently overwriting.
- **Multi-select + bulk actions** — one `/file-manager/bulk` endpoint
drives trash/move/copy across any number of selected items, reusing
the exact same per-item logic as the single-item buttons so bulk
behavior can't drift from what those do. Partial failures don't abort
the rest — every path gets its own attempt, failures are reported back.
- **Trash instead of permanent delete** — deleting now moves the item
into a flat UUID-named folder under `wp-content/uploads/aurora-admin-trash/`
plus a JSON manifest (original path, name, type, deleted-at), rather
than encoding the original path into the filename (nesting/special
characters/length limits make that fragile). Trash view offers
restore (recreates parent folders as needed, blocks if something's
since taken the original spot), permanent delete, and empty-trash.
- **Folder tree sidebar** — lazy-loaded (only fetches a folder's children
when expanded) via the existing list endpoint filtered to directories,
no new backend route needed. Reused as the destination picker for
move/copy.
- **File info panel** — read-only permissions/owner/size/dates/mime type
via a new `/file-manager/info` endpoint; directory size is computed
on-demand for this one-off request rather than for every row in the
main list (would slow down browsing large folders for no benefit).
- **Image thumbnails** — a new `/file-manager/preview` endpoint serves
image files inline (not as an attachment) for use as an `<img>` src;
no server-side resizing, just the extension allowlist + size cap every
other endpoint here already uses.
- **Drag-and-drop upload** (multiple files at once) and **sortable
columns** (Name/Size/Modified, folders always first regardless of
sort) — both frontend-only, no new backend surface.
- Aurora's own trash folder is hidden from the normal browser view when
listing `wp-content/uploads` — it's an implementation detail, not a
real user-created folder.
- Verified live: multi-select, info panel, and the full create → trash →
restore round trip through the UI; copy/bulk-trash/empty-trash verified
directly against the REST endpoints.
- Still deliberately not built (unchanged from the original scope decision):
zip/unzip (zip-slip risk) and chmod/permissions editing (too easy to lock
yourself out).
## [2.14.0] - 2026-07-05
### Added
- **Developer-only** license override for local testing — lets Pro
features be tested without buying a license against the developer's own
software. Settings > Developer's "Simulate active license" toggle treats
every paid feature as fully licensed, bypassing both the real license
check and the file-integrity tamper check. Two gates keep this from ever
doing anything on a real install even if this code ships by accident:
(1) it only has any effect at all when `AURORA_ADMIN_DEV_MODE` is
defined truthy in `wp-config.php` — never set anywhere in this repo or
the release zip, only in a local dev environment's own config — and (2)
the Settings > Developer category itself only renders in the UI when
that constant is present. **This entire feature (the `DEV_MODE_CONSTANT`
checks in `License.php`, the `devModeAvailable` flag in
`SettingsPage.php`, and the "Developer" category in
`settingsConfig.js`/`Settings.vue`) must be removed before packaging a
release build** — every touch point is marked `DEV ONLY` in comments.
Verified live: toggling it on/off correctly unlocks/re-locks a Pro
feature (Role Editor) end-to-end, including the sidebar's lock badge.
## [2.13.1] - 2026-07-05
### Fixed
- Every `ScreenHijack`-mounted page (Posts, Pages, Media, Users, Comments,
the 5 native Settings screens, Media Upload, Add New User, Nav Menus,
Themes, Widgets, Profile) was missing the traffic-light title bar and
rendering outside the normal card frame — the DOM structure didn't match
native/other Aurora pages. Root cause: WordPress prints
`<div id="wpbody"><div id="wpbody-content">` *after* the
`in_admin_header` hook fires (confirmed directly in core's
`admin-header.php`), but `ScreenHijack.php` started its output buffer
*on* `in_admin_header` — so those wrapper divs' opening tags, and later
their closing tags (printed before `admin_footer` fires), both ended up
inside the buffer and got discarded along with the native content they
were meant to replace. The mount div ended up a level too high in the
DOM (a direct child of `#wpcontent`), which broke `windowChrome.js`'s
`#wpbody-content`-based traffic-light insertion and the shell's card
framing CSS. Fixed by having `ScreenHijack.php` re-print the `#wpbody`/
`#wpbody-content` wrapper divs itself around the mount point, so the DOM
matches what WordPress would have produced. Verified live on Posts,
Media, Users, and Profile.
## [2.13.0] - 2026-07-05
### Added
- Sixth and final phase of the "full admin takeover" initiative: the block
editor (`post.php`/`post-new.php`). Deliberately a reskin, not a rebuild
— Gutenberg stays completely native, extending the framing
`windowChrome.js`/`shell-frame.css` already had working (traffic-light
dots overlaid on the editor). Added a "← Posts"/"← Pages" breadcrumb next
to the dots, resolved from the `post-type-{slug}` body class WP core
already adds to every post edit screen (needed since Posts/Pages moved
off a fixed `admin.php?page=...` URL onto their native URLs earlier this
phase, so there's no single URL to hardcode).
- Caught and fixed a real bug while testing: the Site Editor
(`site-editor.php`) shares the same `block-editor-page` body class the
breadcrumb logic keyed off, but has no `post-type-*` class (it edits
templates, not a post) — the original version defaulted to guessing
"Posts" there, which was wrong. Now returns no breadcrumb at all when
no `post-type-*` class is present, verified live on both the post
editor (crumb shows, correct link) and the Site Editor (no crumb,
nested-skeleton framing unaffected).
- This is the last screen in the "full admin takeover" initiative's
original 9-item build order. All 9 phases are now shipped.
## [2.12.0] - 2026-07-05
### Added
- Fifth phase of the "full admin takeover" initiative: Profile
(`profile.php`), reskinning WordPress's own fields (name, contact info,
bio) via core's `/wp/v2/users/me` REST route, plus a small new
`ProfileData.php` route for password changes (core deliberately excludes
password from its own REST schema). Free, gated by `modern_users`.
- Deliberately does not attempt to host third-party plugin fields (2FA
setup, WooCommerce customer fields, etc., added via the
`show_user_profile`/`personal_options` hooks) — a Vue reskin has no
safe way to render another plugin's raw HTML/JS. Instead, the hijack
checks `has_action('show_user_profile')`/`has_action('personal_options')`
at hijack time and falls through to the real native screen whenever
either has a callback registered, rather than silently hiding those
plugins' fields. Verified live both ways: installed a test mu-plugin
hooking `show_user_profile` and confirmed the native screen renders
instead of the reskin; removed it and confirmed the reskin returns.
- Password changes re-issue the auth cookie immediately after
`wp_set_password()` (which invalidates the current session, since it's
derived in part from the password hash) — same as core's own
user-edit.php does when you change your own password.
## [2.11.0] - 2026-07-05
### Added
- Fourth phase of the "full admin takeover" initiative: the Appearance
batch. Nav Menus (`nav-menus.php`) and Widgets (`widgets.php`) are real,
purpose-built replacements — not wired into the existing Menu Creator,
which manages Aurora's own admin sidebar and turned out to be an unrelated
feature despite the similar name. Themes (`themes.php`) is a real
list/activate/delete replacement (`ThemesData.php` + `Themes.vue`). Theme
Editor (`theme-editor.php`) redirects to the existing File Manager instead
of getting a second in-browser file editor — building one would duplicate
File Manager's exact RCE risk surface for no benefit. Tools, Import/Export,
and Site Health are deliberately left un-hijacked; Shell's existing
native-page framing already covers them and a full rebuild has no real
value-add over core's own screens.
- Widgets is deliberately simplified to reordering/moving/deactivating
existing widget instances between sidebars — adding a brand-new
instance of an arbitrary widget type still uses WordPress's own
block-based widgets screen (each widget type owns its own default
settings and form; genericizing that is a full editor's worth of scope
on its own, same reasoning as not rebuilding the block editor).
- Nav Menus supports the common paths: create/delete menus, add
pages/posts/categories/custom links, reorder and indent/outdent
(sub-menu) items, assign menus to theme locations.
- All three (plus Native Settings, previously shipped) are gated by the
`full_admin_takeover` master toggle and the `"admin-takeover"` paid
feature; every new REST controller checks the license directly in its
own `permission_callback`, not just at the page level.
- Fixed a real frontend bug caught while testing these against a 403 (no
license): `Themes.vue`, `Widgets.vue`, and `NavMenus.vue`'s fetch calls
didn't check `res.ok` before treating the response body as the expected
data shape — a REST error object (`{code, message, data}`) got rendered
as if it were the actual list, producing bogus placeholder rows instead
of a clear error message. All three now surface the real error.
- Verified live: Themes' activate/list/screenshot rendering confirmed
working end-to-end (switched between all 3 installed themes); Nav Menus
and Widgets' REST layer verified directly (create/add-item/list/delete
round trip, real sidebar data) — the pages themselves couldn't be
rendered in this dev environment since none of the 3 installed themes
register classic nav-menu/widget support (all are block/FSE themes,
which core itself refuses these screens for too — not a defect in the
hijack).
## [2.10.0] - 2026-07-05
### Added
- Third phase of the "full admin takeover" initiative: Media Upload
(`media-new.php`) and Add New User (`user-new.php`) are now hijacked in
place. Both are Vue-only — no new PHP REST controllers — talking directly
to core's own `/wp/v2/media` and `/wp/v2/users` REST routes, which already
cover file upload and user creation. Free, gated by the same
`modern_media`/`modern_users` toggles as their already-free list-view
companions (no reason to paywall mid-workflow between "browse" and "add").
Verified live end-to-end: created a real user and confirmed it appears in
the Users list.
## [2.9.0] - 2026-07-05
### Added
- Second phase of the "full admin takeover" initiative: the 5 native
Settings screens (General, Writing, Reading, Discussion, Permalinks) are
now hijacked in place (`NativeSettingsPages.php`), sharing one generic
Vue page (`NativeSettings.vue`) driven by a per-screen field schema.
Most fields read/write through core's own `/wp/v2/settings` REST route;
Permalinks has no core REST route for rewrite rules, so it gets its own
small `PermalinksData.php` controller instead (calls
`$wp_rewrite->set_permalink_structure()` + `flush_rules()`, same as core's
own settings page does).
- New master toggle, Settings > Admin Takeover > "Full admin takeover"
(default **off** — these new areas cover more ground and carry more risk
than the already-migrated Posts/Pages/Media/Users/Comments, so opt-in
rather than on by default), plus a new `"admin-takeover"` paid feature key.
Every new REST controller in this phase checks
`License::is_feature_available("admin-takeover")` directly in its own
`permission_callback`, not just at the page level — the same defense the
File Manager REST controller already has, verified live by confirming a
direct REST call still 403s with the setting on but no license.
- `SettingsField.vue` (shared by the main Settings page and the new native
Settings screens) gained two small additions: `readonly` support on text
fields (Site Address shouldn't be editable inline) and a `select` field
type (a plain dropdown, distinct from the existing multiselect checkbox
list) — both minimal, generically useful additions, not one-offs.
## [2.8.0] - 2026-07-05
### Added
- First phase of the "full admin takeover" initiative: Posts, Pages, Media
Library, Users, and Comments no longer redirect to a separate
`admin.php?page=...` URL — they now hijack their native screens in place
(`ScreenHijack.php`, registered via `HijackedNativePages.php`), the same
approach the original uixpress reference plugin used. WordPress's
`load-{page}.php` hook fires before the native screen prints anything, so
Aurora buffers and discards the native output and mounts its own Vue page
in its place — the URL never changes, so there's no separate slug that can
fall out of sync with a plugin's own redirect, a bookmark, or any other
direct visit. This is exactly the bug class already hit and fixed once for
Plugins; the other five pages are now structurally immune to it. Plugins
itself stays on the redirect mechanism — `plugins.php` prints output in a
way that can't be cleanly buffered, confirmed by the reference plugin's own
abandoned attempt at hijacking it.
- Existing REST controllers and Vue pages for all five are unchanged — only
the mounting mechanism moved.
## [2.7.2] - 2026-07-05
### Fixed
- Installing/activating a plugin dropped back to the native WordPress
Plugins page instead of staying in Aurora's UI, until the sidebar's
"Plugins" link was clicked again. Unlike Posts/Pages/Media/Users/
Comments, Plugins only ever had its sidebar *link* rewritten
(`MenuSerializer.php`) — there was no `redirect_native_pages()` case
for direct visits to `plugins.php`, which is exactly what WordPress's
own post-activate/post-install redirect does
(`wp_redirect(self_admin_url('plugins.php?activate=true'))`, no
`?page=` param). Added the missing case, matching the pattern already
used for the other four pages.
## [2.7.1] - 2026-07-05
### Fixed
- Native form fields (text inputs, textareas, selects) on several
classic/non-block-editor screens — most visibly the title field on the
Edit Media screen, but the same fix applies wherever it occurred — were
showing stark white, breaking the dark theme. Root cause: some native
WP screens style specific fields via a 2-ID selector (e.g. core's
`#titlediv #title { background: #fff }`), which beats our existing
rule's single `#wpbody-content` id no matter how many classes are
stacked on it — the exact same CSS-specificity trap as an earlier
block-editor background bug. Added `!important` to the form-field rule
block, since there's no legitimate case where a native admin form field
should stay white.
## [2.7.0] - 2026-07-05
### Added
- New File Manager (`aurora-admin-file-manager`), browsing/editing the
WordPress install's own files — built fresh (not embedding elFinder),
in the spirit of the "WP File Manager" plugin already on this site
(same root, same `manage_options`-only gate). Create/upload/download/
rename/delete files and folders, plus a plain-text editor for
code/config files. A **Pro feature** — the riskiest surface in the
whole plugin (arbitrary read/write access to the site's own PHP files),
so it's gated the same way as Database Explorer.
- Every path a client sends is resolved to a real, `ABSPATH`-contained
path via `realpath()` before any filesystem operation — containment
is checked against the resolved path, not string-matched against the
raw input, so symlink tricks and encoded traversal can't slip past.
- Uploads/new files/renames all reject a blocklist of server-executable
extensions (`.php`, `.phtml`, `.cgi`, `.sh`, etc.) — the single most
important control here, since uploading a webshell is the whole ballgame.
- Deliberately not built: zip/unzip (zip-slip extraction vulnerability),
chmod/permissions editing, and cross-directory move/copy — each is a
meaningfully larger, riskier scope than a first pass warrants.
### Fixed
- File Manager's REST endpoints now also check
`License::is_feature_available('file-manager')`, not just
`manage_options` — found while wiring it up that the page-level lock
alone doesn't stop a direct REST call from bypassing it entirely.
**The same gap exists in Database Explorer, Role Editor, and Activity
Log's REST controllers and is not yet fixed** — flagged as a follow-up.
## [2.6.0] - 2026-07-05
### Added
- Real replacement pages for Pages, Media Library, and Users — the
remaining native admin screens that were only dark-themed skinning,
now built the same way as Posts/Comments/Plugins (own REST controller,
own Vue page, native screen redirected away):
- **Pages** (`PagesData.php` + `Pages.vue`): same as Posts, but shows
the parent page instead of categories. `edit.php?post_type=page`
redirects; other post types are untouched.
- **Media Library** (`MediaData.php` + `Media.vue`): a thumbnail grid
(not a rebuilt drag-drop uploader) with type filters (Images/Audio/
Video/Documents/Unattached), search, and delete. `upload.php`
(both grid and list mode) redirects; `media-new.php` (the actual
upload screen) is untouched, same reasoning as leaving the block
editor alone for Posts/Pages.
- **Users** (`UsersData.php` + `Users.vue`): filterable by role, with
edit/delete actions. Deleting a user always reassigns their content
to whoever performs the deletion, rather than exposing WP core's
reassign-or-delete-everything choice as a one-click REST action —
deliberately simplified to avoid a surprising, highly destructive
default. `users.php` redirects; `user-new.php` (account creation)
is untouched.
- All four gated by their own Settings toggle (`modern_pages`,
`modern_media`, `modern_users` — `modern_posts` already existed),
each defaulting to on.
## [2.5.0] - 2026-07-05
### Added
- Real replacement Posts management page (`aurora-admin-posts`), the same
kind of full custom page as Comments/Plugins rather than dark-themed
skinning over the native `edit.php` list table. New `PostsData.php` REST
controller (list with status/search filters, trash, restore, permanent
delete) and a new Vue page matching the Comments layout (status filter
pills, search, table with title/author/categories/comments/date/status,
inline row actions, pagination). The native Posts screen (`edit.php`,
default post type only — Pages and other post types are untouched)
redirects to it, gated by Settings > Posts' restored "Modern posts
table" toggle (removed a few commits ago when it had no implementation
behind it — now it does).
### Fixed
- The `modern_plugins`/`modern_comments` settings toggles now default to
visually "on" in the Settings UI to match their actual PHP default
(previously they defaulted true server-side but showed off in the UI
until explicitly saved) — same fix applied to the new `modern_posts`.
## [2.4.2] - 2026-07-04
### Fixed
- Admin Notices only ever showed on the Aurora Dashboard — everywhere
else, `admin_notices` fired normally, but on the Dashboard specifically
the notice was invisible: `DashboardTakeover` hides `#wpbody` entirely
there (to hide the *native* dashboard, not our own), and that's exactly
where WordPress prints `admin_notices` output. Notices now render
directly inside the Aurora dashboard frame instead on that one page.
Along the way, also worked around a separate WordPress core behavior
(`common.js` unconditionally relocates every `.notice` element to right
after the page's `<h1>`) using core's own documented `below-h2` opt-out
class, since without it the notice would've just been silently moved
back into the hidden `#wpbody` by that script regardless of where it
was placed server-side.
## [2.4.1] - 2026-07-04
### Fixed
- Dashboard scrolling was broken — content below the first ~1500px
(Server Health, Media Analytics, etc.) was completely unreachable.
`.aurora-dashboard-frame` never got the height cap + internal scroll
treatment `#wpbody-content` got in an earlier fix; combined with
`html`/`body` being locked to `overflow: hidden` (to fix a phantom
scrollbar on a different page), there was no scroll mechanism left at
all for the dashboard specifically. Mirrored the same
max-height/overflow-y treatment onto `.aurora-dashboard-frame`.
## [2.4.0] - 2026-07-04
### Added
- Settings > Menu's "Menu search" toggle now actually does something —
previously had no implementation at all. Shows a real search box at the
top of the sidebar that filters both the WP-native menu list and the
Aurora nav section by title, auto-expanding every section with a match.
### Fixed
- Settings > Dashboard's "Custom dashboard" toggle now actually gates the
dashboard takeover — previously the Aurora dashboard always replaced the
native one regardless of this setting, with no way to turn it back off.
- Settings > Plugins' "Modern plugins page" and Settings > Comments'
"Modern comments page" toggles now actually gate their respective
takeovers/redirects the same way — previously both ran unconditionally
regardless of these settings.
### Removed
- Settings > Posts' "Modern posts table" and Settings > Users' "Modern
users page" toggles — both described a custom management interface that
was never built. Shipping a checkbox with nothing behind it is worse
than not having the checkbox; removed rather than left as dead UI. Their
categories are removed too, since they had no other fields.
## [2.3.1] - 2026-07-04
### Fixed
- Sidebar accordion now treats the "Aurora" branded nav section as part of
the same single-open accordion as the WP-native menu list, instead of
being the one exception that stayed open regardless of what else was
expanded. Expanding any native section now collapses Aurora too, and
vice versa.
## [2.3.0] - 2026-07-04
### Added
- Settings > Media's "Allow SVG uploads" toggle now actually does
something — previously existed in the UI with no implementation behind
it, so every SVG upload was rejected. Fixes both of WordPress's default
blocks (the `upload_mimes` allowlist and the real-mime sniff that nulls
out the file type for SVGs specifically), plus a PHP warning
(`wp_get_missing_image_subsizes()` reading undefined `width`/`height`
metadata keys that SVGs never had) that could otherwise corrupt the
upload response on environments with warnings displayed inline. Also
does basic sanitization on upload (strips `<script>` tags, inline event
handler attributes, `javascript:` URIs) since SVG can carry executable
content.
## [2.2.0] - 2026-07-04
### Added
- Settings > Login's "Style login page" and "Login logo" fields now
actually do something — previously they existed in the UI with no
implementation behind them at all. Styles `wp-login.php` with a dark
theme (background, card, inputs, button) and swaps in the configured
logo image in place of the WordPress logo, linked to the site's home
URL instead of wordpress.org.
## [2.1.1] - 2026-07-04
### Fixed
- Image fields (Logo, Dark mode logo, Login logo, Admin favicon) now open
the real WordPress media library picker. `wp_enqueue_media()` was never
called, so `wp.media` was undefined client-side and every image field
silently fell back to a plain URL prompt instead.
## [2.1.0] - 2026-07-04
### Added
- Free/paid feature gating: Role Editor, Activity Log, Database Explorer,
Analytics, White Label, and the remote site switcher are now Pro
features. Menu Creator, Admin Notices, Comments, Plugins, and the core
shell/theming stay free. Locked pages show an upgrade prompt instead of
the real feature; the sidebar shows a lock badge on locked items.
- Real license validation against the `auroraplugins.com` license server
(validate/activate/deactivate), replacing the earlier placeholder that
treated any non-empty key as active. Status is cached for 12 hours and
re-checked immediately on a license-key change. The Settings page shows
a live status line (active/invalid/unreachable) under the License key
field.
- File-integrity tamper protection: if the code implementing the license
gate has been modified, every paid feature re-locks regardless of an
otherwise-valid license — makes stripping the check out of a
redistributed copy not actually unlock anything.
### Fixed
- Role Editor's REST endpoint 500'd when hit via the REST API — it called
`get_editable_roles()`, which WordPress only auto-loads for normal
wp-admin page requests, not REST requests.