Add a real replacement Posts management page (v2.5.0)
Posts was previously just dark-themed skinning over the native edit.php list table, unlike Comments/Plugins which are genuine replacements. Built the same way: new PostsData.php REST controller (list with status/search filters matching native tabs, trash, restore, permanent delete) and a Vue page matching the Comments layout. - SubApps.php registers it as aurora-admin-posts, parented under edit.php (same pattern as Plugins under plugins.php). - redirect_native_pages() redirects direct edit.php visits (typed URL, not just the sidebar link) to the Aurora page — gated to the default post type only, so Pages and other custom post types are untouched. - MenuSerializer rewrites the sidebar's Posts/All Posts links the same way Plugins/Comments already were. - Gated by Settings > Posts' "Modern posts table" toggle, re-added now that it has a real implementation (it was removed a few commits ago for describing a feature that didn't exist). - Fixed modern_plugins/modern_comments/modern_posts all defaulting to visually "on" in the Settings UI to match their actual PHP default. Verified live: trash/restore/status filters/search all work correctly, edit.php redirects to the Aurora page, edit.php?post_type=page (Pages) correctly does not. Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
d8bf1ae049
commit
56b6674527
@@ -11,6 +11,7 @@ use AuroraAdmin\Analytics\Analytics;
|
||||
use AuroraAdmin\Rest\AnalyticsData;
|
||||
use AuroraAdmin\Rest\PluginsData;
|
||||
use AuroraAdmin\Rest\CommentsData;
|
||||
use AuroraAdmin\Rest\PostsData;
|
||||
use AuroraAdmin\Posts\ScheduledDayFilter;
|
||||
use AuroraAdmin\WhiteLabel\WhiteLabel;
|
||||
use AuroraAdmin\Pages\SubApps;
|
||||
@@ -47,6 +48,7 @@ class AuroraAdmin
|
||||
new AnalyticsData();
|
||||
new PluginsData();
|
||||
new CommentsData();
|
||||
new PostsData();
|
||||
new ScheduledDayFilter();
|
||||
new WhiteLabel();
|
||||
new SubApps();
|
||||
|
||||
+32
-14
@@ -46,6 +46,7 @@ class SubApps
|
||||
"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],
|
||||
"aurora-admin-posts" => ["title" => "Posts", "entry" => "posts", "feature" => null],
|
||||
];
|
||||
|
||||
private static $hook_suffixes = [];
|
||||
@@ -78,6 +79,9 @@ class SubApps
|
||||
if ($slug === "aurora-admin-plugins") {
|
||||
return "plugins.php";
|
||||
}
|
||||
if ($slug === "aurora-admin-posts") {
|
||||
return "edit.php";
|
||||
}
|
||||
return "aurora-admin";
|
||||
}
|
||||
|
||||
@@ -86,7 +90,7 @@ class SubApps
|
||||
if ($slug === "aurora-admin-plugins") {
|
||||
return "activate_plugins";
|
||||
}
|
||||
if ($slug === "aurora-admin-comments") {
|
||||
if ($slug === "aurora-admin-comments" || $slug === "aurora-admin-posts") {
|
||||
return "edit_posts";
|
||||
}
|
||||
return "manage_options";
|
||||
@@ -112,23 +116,36 @@ class SubApps
|
||||
{
|
||||
global $pagenow;
|
||||
|
||||
// Settings > Comments > "Modern comments page" — previously always
|
||||
// redirected regardless of this setting; default true so existing
|
||||
// installs keep their current behavior until explicitly turned off.
|
||||
if (!Settings::get("modern_comments", true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($pagenow !== "edit-comments.php" || !current_user_can("edit_posts")) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isset($_GET["page"]) || wp_doing_ajax()) {
|
||||
return;
|
||||
}
|
||||
|
||||
wp_safe_redirect(admin_url("admin.php?page=aurora-admin-comments"));
|
||||
exit;
|
||||
// Settings > Comments > "Modern comments page" — previously always
|
||||
// redirected regardless of this setting; default true so existing
|
||||
// installs keep their current behavior until explicitly turned off.
|
||||
if (
|
||||
$pagenow === "edit-comments.php" &&
|
||||
current_user_can("edit_posts") &&
|
||||
Settings::get("modern_comments", true)
|
||||
) {
|
||||
wp_safe_redirect(admin_url("admin.php?page=aurora-admin-comments"));
|
||||
exit;
|
||||
}
|
||||
|
||||
// Settings > Posts > "Modern posts table". Only the default post-list
|
||||
// view (edit.php with no post_type, or post_type=post) redirects —
|
||||
// edit.php?post_type=page (Pages) and other custom post types are
|
||||
// untouched, since only Posts has a replacement page.
|
||||
$post_type = isset($_GET["post_type"]) ? sanitize_key(wp_unslash($_GET["post_type"])) : "post";
|
||||
if (
|
||||
$pagenow === "edit.php" &&
|
||||
$post_type === "post" &&
|
||||
current_user_can("edit_posts") &&
|
||||
Settings::get("modern_posts", true)
|
||||
) {
|
||||
wp_safe_redirect(admin_url("admin.php?page=aurora-admin-posts"));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
public static function render()
|
||||
@@ -160,6 +177,7 @@ class SubApps
|
||||
"userName" => wp_get_current_user()->display_name,
|
||||
"roles" => $roles,
|
||||
"tablePrefix" => $wpdb->prefix,
|
||||
"newPostUrl" => esc_url_raw(admin_url("post-new.php")),
|
||||
];
|
||||
$root_id = "aurora-admin-" . self::PAGES[$page]["entry"] . "-root";
|
||||
?>
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
<?php
|
||||
namespace AuroraAdmin\Rest;
|
||||
|
||||
defined("ABSPATH") || exit();
|
||||
|
||||
/**
|
||||
* REST endpoints for Aurora's custom Posts page — a real replacement for
|
||||
* the native edit.php list table (unlike Settings > Posts' old "Modern
|
||||
* posts table" toggle, which described this but was never actually built;
|
||||
* that toggle was removed once this landed).
|
||||
*/
|
||||
class PostsData
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
add_action("rest_api_init", [self::class, "register"]);
|
||||
}
|
||||
|
||||
public static function register()
|
||||
{
|
||||
register_rest_route("aurora-admin/v1", "/posts", [
|
||||
"methods" => "GET",
|
||||
"callback" => [self::class, "list_posts"],
|
||||
"permission_callback" => function () {
|
||||
return current_user_can("edit_posts");
|
||||
},
|
||||
"args" => [
|
||||
"status" => ["type" => "string", "required" => false],
|
||||
"search" => ["type" => "string", "required" => false],
|
||||
"page" => ["type" => "integer", "required" => false],
|
||||
"perPage" => ["type" => "integer", "required" => false],
|
||||
],
|
||||
]);
|
||||
|
||||
register_rest_route("aurora-admin/v1", "/posts/trash", [
|
||||
"methods" => "POST",
|
||||
"callback" => [self::class, "trash_post"],
|
||||
"permission_callback" => [self::class, "can_edit_param_post"],
|
||||
"args" => [
|
||||
"id" => ["type" => "integer", "required" => true],
|
||||
],
|
||||
]);
|
||||
|
||||
register_rest_route("aurora-admin/v1", "/posts/restore", [
|
||||
"methods" => "POST",
|
||||
"callback" => [self::class, "restore_post"],
|
||||
"permission_callback" => [self::class, "can_edit_param_post"],
|
||||
"args" => [
|
||||
"id" => ["type" => "integer", "required" => true],
|
||||
],
|
||||
]);
|
||||
|
||||
register_rest_route("aurora-admin/v1", "/posts/(?P<id>\d+)", [
|
||||
"methods" => "DELETE",
|
||||
"callback" => [self::class, "delete_post"],
|
||||
"permission_callback" => [self::class, "can_delete_route_post"],
|
||||
"args" => [
|
||||
"id" => ["type" => "integer", "required" => true],
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public static function can_edit_param_post($request)
|
||||
{
|
||||
$id = (int) $request->get_param("id");
|
||||
return $id && current_user_can("edit_post", $id);
|
||||
}
|
||||
|
||||
public static function can_delete_route_post($request)
|
||||
{
|
||||
$id = (int) $request["id"];
|
||||
return $id && current_user_can("delete_post", $id);
|
||||
}
|
||||
|
||||
public static function list_posts($request)
|
||||
{
|
||||
$status = self::query_status($request->get_param("status"));
|
||||
$page = max(1, (int) ($request->get_param("page") ?: 1));
|
||||
$per_page = min(100, max(10, (int) ($request->get_param("perPage") ?: 20)));
|
||||
$search = sanitize_text_field((string) $request->get_param("search"));
|
||||
|
||||
$args = [
|
||||
"post_type" => "post",
|
||||
"post_status" => $status,
|
||||
"posts_per_page" => $per_page,
|
||||
"paged" => $page,
|
||||
"orderby" => "date",
|
||||
"order" => "DESC",
|
||||
"ignore_sticky_posts" => true,
|
||||
];
|
||||
|
||||
if ($search !== "") {
|
||||
$args["s"] = $search;
|
||||
}
|
||||
|
||||
$query = new \WP_Query($args);
|
||||
|
||||
return new \WP_REST_Response([
|
||||
"items" => array_map([self::class, "serialize_post"], $query->posts),
|
||||
"stats" => self::stats(),
|
||||
"pagination" => [
|
||||
"page" => $page,
|
||||
"perPage" => $per_page,
|
||||
"total" => (int) $query->found_posts,
|
||||
"totalPages" => max(1, (int) $query->max_num_pages),
|
||||
],
|
||||
], 200);
|
||||
}
|
||||
|
||||
public static function trash_post($request)
|
||||
{
|
||||
$id = (int) $request->get_param("id");
|
||||
$post = get_post($id);
|
||||
if (!$post || $post->post_type !== "post") {
|
||||
return new \WP_Error("aurora_post_invalid", __("Invalid post.", "aurora-admin"), ["status" => 400]);
|
||||
}
|
||||
|
||||
$result = wp_trash_post($id);
|
||||
if (!$result) {
|
||||
return new \WP_Error("aurora_post_trash_failed", __("Could not trash the post.", "aurora-admin"), ["status" => 500]);
|
||||
}
|
||||
|
||||
return new \WP_REST_Response(["success" => true, "message" => __("Post moved to trash.", "aurora-admin")], 200);
|
||||
}
|
||||
|
||||
public static function restore_post($request)
|
||||
{
|
||||
$id = (int) $request->get_param("id");
|
||||
$post = get_post($id);
|
||||
if (!$post || $post->post_type !== "post") {
|
||||
return new \WP_Error("aurora_post_invalid", __("Invalid post.", "aurora-admin"), ["status" => 400]);
|
||||
}
|
||||
|
||||
$result = wp_untrash_post($id);
|
||||
if (!$result) {
|
||||
return new \WP_Error("aurora_post_restore_failed", __("Could not restore the post.", "aurora-admin"), ["status" => 500]);
|
||||
}
|
||||
|
||||
return new \WP_REST_Response(["success" => true, "message" => __("Post restored.", "aurora-admin")], 200);
|
||||
}
|
||||
|
||||
public static function delete_post($request)
|
||||
{
|
||||
$id = (int) $request["id"];
|
||||
$post = get_post($id);
|
||||
if (!$post || $post->post_type !== "post") {
|
||||
return new \WP_Error("aurora_post_invalid", __("Invalid post.", "aurora-admin"), ["status" => 400]);
|
||||
}
|
||||
|
||||
$result = wp_delete_post($id, true);
|
||||
if (!$result) {
|
||||
return new \WP_Error("aurora_post_delete_failed", __("Could not delete the post.", "aurora-admin"), ["status" => 500]);
|
||||
}
|
||||
|
||||
return new \WP_REST_Response(["success" => true, "message" => __("Post deleted.", "aurora-admin")], 200);
|
||||
}
|
||||
|
||||
private static function serialize_post($post)
|
||||
{
|
||||
$author = get_userdata($post->post_author);
|
||||
$categories = wp_get_post_categories($post->ID, ["fields" => "names"]);
|
||||
$status = $post->post_status;
|
||||
|
||||
return [
|
||||
"id" => (int) $post->ID,
|
||||
"title" => get_the_title($post) ?: __("(No title)", "aurora-admin"),
|
||||
"author" => $author ? $author->display_name : __("Unknown", "aurora-admin"),
|
||||
"categories" => is_array($categories) ? $categories : [],
|
||||
"commentCount" => (int) $post->comment_count,
|
||||
"date" => get_the_date("M j, Y", $post),
|
||||
"dateLabel" =>
|
||||
$status === "publish"
|
||||
? __("Published", "aurora-admin")
|
||||
: ($status === "future"
|
||||
? __("Scheduled", "aurora-admin")
|
||||
: __("Last Modified", "aurora-admin")),
|
||||
"status" => $status,
|
||||
"statusLabel" => self::status_label($status),
|
||||
"editUrl" => get_edit_post_link($post->ID, "raw") ?: "",
|
||||
"viewUrl" => $status === "publish" ? get_permalink($post) : get_preview_post_link($post),
|
||||
"canDelete" => current_user_can("delete_post", $post->ID),
|
||||
];
|
||||
}
|
||||
|
||||
private static function stats()
|
||||
{
|
||||
$counts = wp_count_posts("post");
|
||||
|
||||
return [
|
||||
"all" => (int) $counts->publish + (int) $counts->draft + (int) $counts->pending + (int) $counts->private + (int) $counts->future,
|
||||
"publish" => (int) $counts->publish,
|
||||
"draft" => (int) $counts->draft,
|
||||
"pending" => (int) $counts->pending,
|
||||
"trash" => (int) ($counts->trash ?? 0),
|
||||
];
|
||||
}
|
||||
|
||||
private static function query_status($status)
|
||||
{
|
||||
switch ($status) {
|
||||
case "publish":
|
||||
case "draft":
|
||||
case "pending":
|
||||
case "trash":
|
||||
return $status;
|
||||
case "all":
|
||||
default:
|
||||
return ["publish", "draft", "pending", "private", "future"];
|
||||
}
|
||||
}
|
||||
|
||||
private static function status_label($status)
|
||||
{
|
||||
switch ($status) {
|
||||
case "publish":
|
||||
return __("Published", "aurora-admin");
|
||||
case "draft":
|
||||
return __("Draft", "aurora-admin");
|
||||
case "pending":
|
||||
return __("Pending", "aurora-admin");
|
||||
case "future":
|
||||
return __("Scheduled", "aurora-admin");
|
||||
case "private":
|
||||
return __("Private", "aurora-admin");
|
||||
case "trash":
|
||||
return __("Trash", "aurora-admin");
|
||||
default:
|
||||
return ucfirst($status);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,6 +155,13 @@ class MenuSerializer
|
||||
return admin_url("admin.php?page=aurora-admin-comments");
|
||||
}
|
||||
|
||||
// "edit.php" (no query string) is specifically the default post type's
|
||||
// (Posts) list — other post types register as "edit.php?post_type=xyz",
|
||||
// a different string, so they're untouched.
|
||||
if ($slug === "edit.php" && Settings::get("modern_posts", true)) {
|
||||
return admin_url("admin.php?page=aurora-admin-posts");
|
||||
}
|
||||
|
||||
// Already an absolute URL.
|
||||
if (preg_match('#^https?://#', $slug)) {
|
||||
return $slug;
|
||||
|
||||
Reference in New Issue
Block a user