Document the free/paid feature-gating architecture
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
6d502604d7
commit
eb03d3dd6c
+97
-8
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user