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
+29 -8
View File
@@ -2,6 +2,7 @@
namespace AuroraAdmin\Pages;
use AuroraAdmin\Utility\Assets;
use AuroraAdmin\Licensing\License;
defined("ABSPATH") || exit();
@@ -31,14 +32,19 @@ defined("ABSPATH") || exit();
*/
class SubApps
{
// "feature" ties a page to a License::PAID_FEATURES key — null means the
// page is free. Locked pages still register normally (real capability,
// real URL) but render() swaps in an upgrade prompt instead of the real
// feature when unlicensed, and maybe_enqueue() loads that prompt's tiny
// JS bundle instead of the real (much heavier) feature bundle.
const PAGES = [
"aurora-admin-menu-creator" => ["title" => "Menu Creator", "entry" => "menu-creator"],
"aurora-admin-notices" => ["title" => "Admin Notices", "entry" => "admin-notices"],
"aurora-admin-roles" => ["title" => "Role Editor", "entry" => "role-editor"],
"aurora-admin-activity-log" => ["title" => "Activity Log", "entry" => "activity-log"],
"aurora-admin-database" => ["title" => "Database Explorer", "entry" => "database-explorer"],
"aurora-admin-plugins" => ["title" => "Installed Plugins", "entry" => "plugins"],
"aurora-admin-comments" => ["title" => "Comments", "entry" => "comments"],
"aurora-admin-menu-creator" => ["title" => "Menu Creator", "entry" => "menu-creator", "feature" => null],
"aurora-admin-notices" => ["title" => "Admin Notices", "entry" => "admin-notices", "feature" => null],
"aurora-admin-roles" => ["title" => "Role Editor", "entry" => "role-editor", "feature" => "role-editor"],
"aurora-admin-activity-log" => ["title" => "Activity Log", "entry" => "activity-log", "feature" => "activity-log"],
"aurora-admin-database" => ["title" => "Database Explorer", "entry" => "database-explorer", "feature" => "database-explorer"],
"aurora-admin-plugins" => ["title" => "Installed Plugins", "entry" => "plugins", "feature" => null],
"aurora-admin-comments" => ["title" => "Comments", "entry" => "comments", "feature" => null],
];
private static $hook_suffixes = [];
@@ -92,7 +98,13 @@ class SubApps
return;
}
Assets::enqueue("aurora-admin-" . self::PAGES[$slug]["entry"], "entries/" . self::PAGES[$slug]["entry"] . ".js");
$page = self::PAGES[$slug];
if ($page["feature"] && !License::is_feature_available($page["feature"])) {
Assets::enqueue("aurora-admin-locked-feature", "entries/locked-feature.js");
return;
}
Assets::enqueue("aurora-admin-" . $page["entry"], "entries/" . $page["entry"] . ".js");
}
public static function redirect_native_pages()
@@ -118,6 +130,15 @@ class SubApps
return;
}
$feature = self::PAGES[$page]["feature"];
if ($feature && !License::is_feature_available($feature)) {
$data = ["feature" => License::feature_label($feature)];
?>
<div data-aurora-locked-feature="<?php echo esc_attr(wp_json_encode($data)); ?>"></div>
<?php
return;
}
$roles = [];
foreach (wp_roles()->roles as $slug => $info) {
$roles[] = ["value" => $slug, "label" => translate_user_role($info["name"])];