Add free/paid feature gating (Role Editor, Activity Log, DB Explorer,

Analytics, White Label, remote site switcher are paid)

- New License class is the single gate every paid feature checks —
  today it's a placeholder (any non-empty license_key counts as active)
  since there's no license server yet, but every gated feature already
  funnels through it so swapping in real remote validation later won't
  touch anything else.
- Role Editor, Activity Log, and Database Explorer stay registered/
  capability-checked as before, but render an UpgradeNotice screen
  instead of the real feature when unlicensed (and skip loading the
  real JS bundle/REST data). Menu Creator, Admin Notices, Comments,
  and Plugins stay free.
- Analytics' tracking hooks and REST endpoint, and White Label's
  favicon/rename/text-replace hooks, simply don't register without a
  license. Settings fields for both stay visible/editable either way.
- Remote site switcher gated alongside its existing capability check.
- Sidebar shows a small lock badge on locked Aurora nav items; the
  Dashboard's Analytics tab shows the same upgrade prompt when locked.
- Fixed an unrelated bug surfaced while testing Role Editor: RolesData's
  REST callback called get_editable_roles() without loading
  wp-admin/includes/user.php first, which REST requests don't get for
  free (only real wp-admin page loads do) — 500'd every time.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
nixpanel
2026-07-03 19:59:00 -05:00
co-authored by Claude Sonnet 5
parent 73591e0ca4
commit 6d502604d7
15 changed files with 244 additions and 22 deletions
+56
View File
@@ -0,0 +1,56 @@
<?php
namespace AuroraAdmin\Licensing;
use AuroraAdmin\Options\Settings;
defined("ABSPATH") || exit();
/**
* Single gate every paid feature funnels through, so the free/paid split
* lives in one place instead of being re-decided in each feature's code.
*
* Today's is_active() is a placeholder: any non-empty license_key counts as
* active, with no remote validation. There's no license server yet — when
* one exists, swap is_active()'s internals for a real check (cached in a
* transient so it isn't a remote call on every admin page load); nothing
* else in the plugin needs to change, since every gated feature already
* calls into this class rather than reading the setting directly.
*/
class License
{
/**
* Paid sub-apps/features, keyed by the id used to gate them, mapped to a
* human label used in upgrade prompts.
*/
const PAID_FEATURES = [
"role-editor" => "Role Editor",
"activity-log" => "Activity Log",
"database-explorer" => "Database Explorer",
"analytics" => "Analytics",
"white-label" => "White Label",
"remote-site-switcher" => "Remote Site Switcher",
];
public static function is_active()
{
$key = trim((string) Settings::get("license_key", ""));
return $key !== "";
}
/**
* True for any feature not in PAID_FEATURES (nothing to gate) or when a
* license is active; false only when the feature is paid and unlicensed.
*/
public static function is_feature_available($feature)
{
if (!isset(self::PAID_FEATURES[$feature])) {
return true;
}
return self::is_active();
}
public static function feature_label($feature)
{
return isset(self::PAID_FEATURES[$feature]) ? self::PAID_FEATURES[$feature] : "";
}
}