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:
co-authored by
Claude Sonnet 5
parent
faf9f35992
commit
24be114f9a
@@ -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")),
|
||||
];
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user