diff --git a/PROJECT_STATUS.md b/PROJECT_STATUS.md index 69d11ff..ecd7ba4 100644 --- a/PROJECT_STATUS.md +++ b/PROJECT_STATUS.md @@ -1,6 +1,6 @@ # Aurora Admin v2 — Project Status -_Last updated: 2026-07-03 (latest session — universal content-area scroll cap, sidebar accordion, scroll-anchoring fix)_ +_Last updated: 2026-07-03 (latest session — free/paid feature gating (licensing))_ ## What this project is @@ -438,6 +438,75 @@ page navigation via a taskbar. block editor (`body.block-editor-page` is present on both), so the existing block-editor handling covers it once `#site-editor` is framed. +## Licensing (free vs. paid feature gating) + +Added this session. The split, as decided with the user: the whole shell/ +theming product is free (it's the adoption hook); Role Editor, Activity Log, +Database Explorer, Analytics, White Label, and the remote site switcher are +paid (the "operational" tools an agency/serious site owner pays for). Menu +Creator, Admin Notices, Comments, and the Plugins page stay free. + +- `admin/src/Licensing/License.php` is the single gate every paid feature + checks — `PAID_FEATURES` maps a feature id to a human label, + `is_active()` and `is_feature_available($feature)` are the two calls + everything else uses. **`is_active()` is a placeholder today**: any + non-empty `license_key` setting counts as licensed, no remote validation + at all. There's no license server yet — when one exists, only + `is_active()`'s internals need to change (ideally caching the remote + check in a transient so it isn't a network call on every admin page + load); every gated feature already funnels through this class rather than + reading the setting directly, so nothing else needs to change. +- **Sub-apps** (`SubApps.php`): `PAGES` now has a `"feature"` key per page + (`null` for the 4 free ones). Locked pages still fully register + (`add_submenu_page`, real capability, real URL — same as before) so nav/ + bookmarks/capability checks all keep working; `render()` and + `maybe_enqueue()` just check `License::is_feature_available()` and swap in + a shared upgrade prompt (below) instead of the real feature's mount div/JS + bundle/REST data when unlicensed. This also means a locked page's + sensitive data (e.g. Database Explorer's schema access) never gets + fetched or serialized to the page in the first place. +- **Shared upgrade prompt**: `app/src/pages/_shared/UpgradeNotice.vue` (lock + icon, feature name, description, a CTA linking to + `admin.php?page=aurora-admin#license`) + a dedicated + `app/src/entries/locked-feature.js` Vite entry that mounts it onto + `[data-aurora-locked-feature]`. `SettingsField.vue` gives the license key + field `id="license"` so that CTA link scrolls straight to it (General is + the default-active settings category, so the anchor resolves on a fresh + page load too). Reused directly by the Dashboard's Analytics tab (below) + rather than building a second locked-state component. +- **Analytics** (paid): `Analytics.php`'s constructor returns immediately + (no hooks registered, no pageview tracking) when unlicensed. + `AnalyticsData.php`'s REST `permission_callback` also checks + `License::is_feature_available("analytics")`, so the endpoint 403s even + if someone hits it directly. The Dashboard's Analytics tab + (`Dashboard.vue`) renders `UpgradeNotice` in place of the real charts when + `useAnalyticsData`'s fetch comes back non-OK (the 403, in practice). +- **White Label** (paid): `WhiteLabel.php`'s constructor returns immediately + when unlicensed — favicon/plugin-rename/menu-rename/text-replacement + filters simply never register. Its settings fields stay visible either + way (so an admin can pre-configure before upgrading) — enforcement is + entirely server-side, nothing in the UI needs to disable inputs. +- **Remote site switcher** (paid): `Shell.php`'s `canSwitchSites` flag now + additionally requires `License::is_feature_available("remote-site-switcher")`, + alongside its existing `remote_site_switcher_capability` check. +- **Sidebar lock badges**: `Shell::aurora_nav()` adds a `"locked"` bool per + nav item (true for Role Editor/Activity Log/Database Explorer when + unlicensed); `Sidebar.vue` renders a small lock dashicon next to the label + when set. The link still navigates normally — `SubApps::render()` is what + actually shows the upgrade prompt there. +- **Settings page**: White Label/Analytics category subtitles and the + Remote Site Switcher Capability field's description now say "Pro feature + — requires a license" (`settingsConfig.js`) — a plain-text note only, no + functional gating in the settings UI itself (the features' own code is + the actual boundary). +- **Bug fix surfaced while testing this**: `RolesData.php`'s REST callback + called `get_editable_roles()` unqualified, which 500'd every time — + that function lives in `wp-admin/includes/user.php`, which WP only + auto-loads for real wp-admin page requests, not REST API requests. Fixed + with a `function_exists` guard + `require_once` before the call. Unrelated + to licensing, just happened to surface while verifying Role Editor's + unlock path. + ## Key bugs fixed (root causes, in case they resurface) - **`.card` global class collision**: original Dashboard cards used @@ -536,15 +605,35 @@ over-constraint bug (`top`+`height`+`bottom` all set on a fixed-position box silently ignoring `bottom`) that was making its frame run past the viewport (pushed as commit `8153d43`). -This session: capped every native/Aurora page's content area to the -viewport with internal scrolling instead of letting tall pages grow past it -and scroll the whole document (see "Universal content-area height cap" -above), which surfaced and fixed two more bugs along the way — a Chrome -scroll-anchoring jump on Add Plugins, and a phantom document-level +An earlier pass this session capped every native/Aurora page's content area +to the viewport with internal scrolling instead of letting tall pages grow +past it and scroll the whole document (see "Universal content-area height +cap" above), which surfaced and fixed two more bugs along the way — a +Chrome scroll-anchoring jump on Add Plugins, and a phantom document-level scrollbar with a large empty gap below the frame. Also made the sidebar's native-menu sections a single-open accordion instead of letting multiple -stay expanded at once. All of it was live-verified in Chrome against the -LocalWP site (build hot-patched into +stay expanded at once (pushed as commit `73591e0`). + +Most recently: added the free/paid feature-gating system end-to-end (see +"Licensing" above) — Role Editor, Activity Log, Database Explorer, +Analytics, White Label, and the remote site switcher are now paid; Menu +Creator, Admin Notices, Comments, Plugins, and the whole shell/theming +product stay free. Verified both directions live (entering/clearing a +license key immediately locks/unlocks the sidebar badges, the sub-app +pages, the Dashboard's Analytics tab, and the underlying REST endpoints), +and fixed an unrelated pre-existing 500 in Role Editor's REST endpoint +along the way (pushed as commit `6d50260`). The distributable zip +(`dist/aurora-admin-v2.zip`) was rebuilt fresh from source and should be +re-verified/re-zipped again after this doc update if anything changed since. + +All of it was live-verified in Chrome against the LocalWP site (build +hot-patched into `/Users/kris/Local Sites/aurora/.../aurora-admin/app/dist` for iteration — the repo's source files are the actual edits; that copy is just the compiled build artifact, not a separate edit surface). + +**Not yet built**: an actual license-purchase/checkout flow or remote +validation server — today entering *any* non-empty text in the License key +field unlocks every paid feature (see "Licensing" above). This is fine for +internal testing but must be replaced with real validation before this +ships to real users, or the paid tier isn't actually gated.