Advance full admin takeover from v1.1 to v1.12

Batches everything built since the 1.0 WP.org submission reset: Login
Redirect, the Tools/Export/Import screen takeovers, Modules rebuilt as
an addon marketplace, Menu Creator's reorder fix, Activity Log
redesigned into a filterable table with Admin/User tabs, dark-theme
fixes for Site Health/Personal Data/Updates, and an opt-in Disable
Gutenberg toggle. Continues the "nothing stays native" takeover goal
and closes gaps found via live testing and the WordPress Plugin Check
tool. See CHANGELOG.md for the full per-version breakdown (1.1-1.12).

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
NixReaper
2026-07-26 13:37:35 -05:00
co-authored by Claude Sonnet 5
parent faf9f35992
commit 24be114f9a
39 changed files with 3462 additions and 1653 deletions
+39
View File
@@ -0,0 +1,39 @@
<?php
namespace AuroraAdmin\Pages;
defined("ABSPATH") || exit();
/**
* Registers the Export screen (export.php) for the "full admin takeover"
* initiative — a real Aurora-built form, not a reframed native page.
*
* Safe to fully rebuild (unlike Tools/Site Health): export.php's actual file
* generation (the `?download=true` request) calls export_wp() and die()s
* before ever reaching in_admin_header/admin_footer — the two hooks
* ScreenHijack's buffering relies on — so that request is never touched by
* the hijack at all and runs completely native, unmodified. Only the HTML
* form (the plain GET view with no `download` param) is replaced; Aurora's
* form submits via a real browser navigation to the same
* export.php?download=true&... URL, so the download itself is byte-for-byte
* what core would have produced.
*/
class ExportPage
{
public function __construct()
{
ScreenHijack::register(
"load-export.php",
"export", // Matches core's own capability for this screen.
"full_admin_takeover",
"export",
"aurora-admin-export-root",
null,
null,
function () {
return [
"exportUrl" => esc_url_raw(admin_url("export.php")),
];
}
);
}
}
+137
View File
@@ -0,0 +1,137 @@
<?php
namespace AuroraAdmin\Pages;
defined("ABSPATH") || exit();
/**
* Registers the Import screen (import.php) for the "full admin takeover"
* initiative — a real Aurora-built listing, not a reframed native page.
*
* Only the LISTING page is rebuilt. Running an already-active importer
* (native "Run Importer" link) still goes to admin.php?import={id} — a
* screen registered and entirely rendered by whatever third-party importer
* plugin owns that id (register_importer()'s own callback). That page is
* arbitrary, unpredictable third-party UI outside this hijack's scope
* (a different load-{hook}), and stays native, same as e.g. the block
* editor stays native beyond its frame.
*
* Install/activate reuses existing, already-proven mechanisms instead of
* new logic: WordPress core's own admin-ajax "install-plugin" action (same
* one native's "Install Now" button triggers) for installing, and Aurora's
* own aurora-admin/v1/plugins/activate route (see PluginsData.php) for
* activation.
*/
class ImportPage
{
public function __construct()
{
ScreenHijack::register(
"load-import.php",
"import", // Matches core's own capability for this screen.
"full_admin_takeover",
"import",
"aurora-admin-import-root",
null,
null,
function () {
return self::build_data();
}
);
}
private static function build_data()
{
if (!function_exists("get_importers")) {
require_once ABSPATH . "wp-admin/includes/import.php";
}
// Real, already-active importers (registered via register_importer() by
// an active importer plugin) — global $wp_importers. get_importers()
// returns that global as-is, which is null (not an empty array) until
// something actually calls register_importer() — true whenever no
// importer plugin is active, e.g. a fresh install. Native import.php
// never hits this directly: it only ever reads $importers via isset(),
// and PHP auto-vivifies null into an array the moment popular_importers
// gets merged in via `$importers[$id] = [...]`, so by the time IT
// foreach()es, an assignment has always already happened. This code
// foreach()es the raw result directly, so it needs its own guard.
$active = get_importers();
if (!is_array($active)) {
$active = [];
}
$can_install = current_user_can("install_plugins");
$popular = $can_install ? wp_get_popular_importers() : [];
$merged = [];
foreach ($active as $id => $data) {
$merged[] = [
"id" => $id,
"name" => $data[0],
"description" => $data[1],
"status" => "active",
"runUrl" => esc_url_raw(self_admin_url("admin.php?import=" . $id)),
];
}
// Same de-dup + dummy-registration logic as native import.php: a
// popular importer already covered by an active one is skipped.
foreach ($popular as $pop_key => $pop) {
$importer_id = isset($pop["importer-id"]) ? $pop["importer-id"] : $pop_key;
if (isset($active[$pop_key]) || isset($active[$importer_id])) {
continue;
}
$plugin_slug = isset($pop["plugin-slug"]) ? $pop["plugin-slug"] : "";
$entry = [
"id" => $importer_id,
"name" => $pop["name"],
"description" => $pop["description"],
"slug" => $plugin_slug,
// Deterministic regardless of current status — provided for every
// entry so the front end never has to construct an admin URL
// itself (this respects any custom/network admin path).
"runUrl" => esc_url_raw(self_admin_url("admin.php?import=" . $importer_id)),
];
if ($plugin_slug !== "" && file_exists(WP_PLUGIN_DIR . "/" . $plugin_slug)) {
if (!function_exists("get_plugins")) {
require_once ABSPATH . "wp-admin/includes/plugin.php";
}
$plugins = get_plugins("/" . $plugin_slug);
if ($plugins) {
$keys = array_keys($plugins);
$entry["status"] = "installed_inactive";
$entry["pluginFile"] = $plugin_slug . "/" . $keys[0];
} else {
$entry["status"] = "not_installed";
}
} else {
$entry["status"] = "not_installed";
}
$merged[] = $entry;
}
usort($merged, function ($a, $b) {
return strcasecmp($a["name"], $b["name"]);
});
// Any third-party output hooked to import_filters (a genuine, if rarely
// used, extension point) — same capture-and-relay approach as Tools'
// tool_box. Empty on a site with nothing hooked to it.
ob_start();
do_action("import_filters");
$native_filters_html = trim((string) ob_get_clean());
return [
"importers" => $merged,
"canInstallPlugins" => $can_install,
"installNonce" => wp_create_nonce("updates"),
"ajaxUrl" => esc_url_raw(admin_url("admin-ajax.php")),
"pluginSearchUrl" => esc_url_raw(network_admin_url("plugin-install.php?tab=search&type=tag&s=importer")),
"nativeFiltersHtml" => $native_filters_html,
];
}
}
+8
View File
@@ -35,6 +35,7 @@ class SubApps
public static function pages()
{
return [
"aurora-admin-modules" => ["title" => __("Modules", "aurora-admin"), "entry" => "modules"],
"aurora-admin-menu-creator" => ["title" => __("Menu Creator", "aurora-admin"), "entry" => "menu-creator"],
"aurora-admin-notices" => ["title" => __("Admin Notices", "aurora-admin"), "entry" => "admin-notices"],
"aurora-admin-roles" => ["title" => __("Role Editor", "aurora-admin"), "entry" => "role-editor"],
@@ -164,6 +165,13 @@ class SubApps
$data["theme"] = wp_get_theme()->get("Name");
}
// The Menu Creator editor needs the raw top-level menu, which can't be
// read over REST (the admin $menu isn't built there). Pass the snapshot
// captured during this admin page load instead.
if ($page === "aurora-admin-menu-creator") {
$data["nativeItems"] = \AuroraAdmin\MenuCreator\MenuCreator::native_snapshot();
}
$root_id = "aurora-admin-" . $pages[$page]["entry"] . "-root";
?>
<div
+72
View File
@@ -0,0 +1,72 @@
<?php
namespace AuroraAdmin\Pages;
defined("ABSPATH") || exit();
/**
* Registers the "Available Tools" screen (tools.php) for the "full admin
* takeover" initiative — a real hijacked replacement, not just a reframed
* native page.
*
* tools.php's whole purpose is being an extension point: other plugins add
* their own cards to it via the `tool_box` action (WooCommerce, migration
* tools, etc. have historically used it). A pure from-scratch rebuild would
* silently drop that content the moment Aurora takes over the screen, so
* this still captures and re-renders whatever `tool_box` produces — Aurora
* owns the page shell (header, card styling, layout) but not the content of
* hooks it doesn't control.
*
* Import, Export, Site Health, Export/Erase Personal Data, and the Theme/
* Plugin File Editors are separate screens (their own load-{file} hooks) and
* are not covered by this — Available Tools only.
*
* Native tools.php's "Categories and Tags Converter" card is NOT produced by
* the tool_box hook — it's hardcoded directly in core's own template, gated
* on the `import` capability plus category/tag manage_terms. Replicated here
* with the identical gate so visibility exactly matches what core itself
* would have shown.
*/
class ToolsPage
{
public function __construct()
{
ScreenHijack::register(
"load-tools.php",
"edit_posts", // Matches core's own capability for this screen.
"full_admin_takeover",
"tools",
"aurora-admin-tools-root",
null,
null,
function () {
// Same gate core's own tools.php applies before showing the
// Categories and Tags Converter card.
$show_converter = false;
if (current_user_can("import")) {
$cats = get_taxonomy("category");
$tags = get_taxonomy("post_tag");
$show_converter =
($cats && current_user_can($cats->cap->manage_terms)) ||
($tags && current_user_can($tags->cap->manage_terms));
}
// The native call to do_action('tool_box') already happened during
// this same request (inside the buffer ScreenHijack discards) — this
// is a second, independent invocation solely to capture its output
// for Aurora's own page, so any OTHER plugin's tool_box card still
// shows up here. Standard "buffer a hook to capture its markup"
// pattern; safe for the well-behaved, output-only callbacks this
// hook is meant for.
ob_start();
do_action("tool_box");
$native_tools_html = trim((string) ob_get_clean());
return [
"showCategoriesConverter" => $show_converter,
"importUrl" => esc_url_raw(admin_url("import.php")),
"nativeToolsHtml" => $native_tools_html,
];
}
);
}
}