Commit 559c771618 for wordpress.org
commit 559c7716180486aa06c4e85afe8d81a5db260bb2
Author: peterwilsoncc <peterwilsoncc@git.wordpress.org>
Date: Thu Oct 15 03:21:53 2026 +0000
Administration: Display 404 error for unregistered admin pages.
Replaces the misleading "sorry, you are not allowed to access this page" message with "the requested page does not exist" when a user attempts to visit an unregistered page in the dashboard.
Unregistered pages now return an HTTP 404 response rather than a 403.
Props ahsannayem, apermo, arifulislam1, audrasjb, chinmoy709, chriscct7, dd32, envygeeks, habiburdev, hakre, huzaifaalmesbah, jane, jorbin, marzanhusain, mindctrl, muryam, nacin, nikschavan, nirajgirixd, noruzzaman, ornani, ozgursar, pento, poojapadamad, presskopp, ramiy, sainathpoojary, sergeybiryukov, shailu25, sirlouen, syedjakir91, thabotswana, vgnavada, westonruter, wildworks
Fixes #14060.
Built from https://develop.svn.wordpress.org/trunk@64351
git-svn-id: http://core.svn.wordpress.org/trunk@63501 1a063a9b-81f0-0310-95a4-ce76da25c4cd
diff --git a/wp-admin/includes/menu.php b/wp-admin/includes/menu.php
index f1c2985e93..ff4635fffc 100644
--- a/wp-admin/includes/menu.php
+++ b/wp-admin/includes/menu.php
@@ -376,7 +376,17 @@ if ( ! empty( $menu ) && 'wp-menu-separator' === $menu[ $last_menu_key ][4] ) {
}
unset( $last_menu_key );
-if ( ! user_can_access_admin_page() ) {
+if ( ! wp_admin_page_exists() ) {
+
+ /**
+ * Fires when a request is made to access an admin page that does not exist.
+ *
+ * @since 7.2.0
+ */
+ do_action( 'wp_admin_page_not_found' );
+
+ wp_die( __( 'The requested page does not exist.' ), 404 );
+} elseif ( ! user_can_access_admin_page() ) {
/**
* Fires when access to an admin page is denied.
diff --git a/wp-admin/includes/plugin.php b/wp-admin/includes/plugin.php
index c627e6d8f5..c655ef21c2 100644
--- a/wp-admin/includes/plugin.php
+++ b/wp-admin/includes/plugin.php
@@ -1477,12 +1477,13 @@ function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $call
* @since 1.5.0
* @since 5.3.0 Added the `$position` parameter.
*
- * @global array $submenu
- * @global array $menu
- * @global array $_wp_real_parent_file
- * @global bool $_wp_submenu_nopriv
- * @global array $_registered_pages
- * @global array $_parent_pages
+ * @global array $submenu
+ * @global array $menu
+ * @global array $_wp_real_parent_file
+ * @global array<string, array<string, true>> $_wp_submenu_nopriv
+ * @global array<string, true> $_wp_nopriv_pages
+ * @global array<string, true> $_registered_pages
+ * @global array $_parent_pages
*
* @param string $parent_slug The slug name for the parent menu (or the file name of a standard
* WordPress admin page).
@@ -1501,7 +1502,7 @@ function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $call
*/
function add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $callback = '', $position = null ) {
global $submenu, $menu, $_wp_real_parent_file, $_wp_submenu_nopriv,
- $_registered_pages, $_parent_pages;
+ $_wp_nopriv_pages, $_registered_pages, $_parent_pages;
$menu_slug = plugin_basename( $menu_slug );
$parent_slug = plugin_basename( $parent_slug );
@@ -1512,6 +1513,7 @@ function add_submenu_page( $parent_slug, $page_title, $menu_title, $capability,
if ( ! current_user_can( $capability ) ) {
$_wp_submenu_nopriv[ $parent_slug ][ $menu_slug ] = true;
+ $_wp_nopriv_pages[ $menu_slug ] = true;
return false;
}
@@ -2162,6 +2164,54 @@ function get_plugin_page_hook( $plugin_page, $parent_page ) {
}
}
+/**
+ * Determines whether the requested admin page is registered.
+ *
+ * This does not check whether the current user has the capability to access
+ * the page, only whether the page itself exists. Pages registered for a
+ * capability the current user lacks are recorded in the no-privilege
+ * registries rather than in $_registered_pages, and are still considered
+ * to exist so that the caller can report a permission error instead of a
+ * "page not found" error.
+ *
+ * @since 7.2.0
+ *
+ * @global string|null $plugin_page The plugin page slug being loaded.
+ * @global array<string, true> $_registered_pages Array of all registered admin page hooks.
+ * @global array<string, true> $_wp_menu_nopriv Array of top-level menu slugs the current user cannot access.
+ * @global array<string, array<string, true>> $_wp_submenu_nopriv Array of submenu slugs the current user cannot access, keyed by parent slug.
+ * @global array<string, true> $_wp_nopriv_pages Array of page slugs registered for a capability the current user lacks.
+ *
+ * @return bool True if the admin page exists, false otherwise.
+ */
+function wp_admin_page_exists(): bool {
+ global $plugin_page, $_registered_pages, $_wp_menu_nopriv, $_wp_submenu_nopriv,
+ $_wp_nopriv_pages;
+
+ if ( ! isset( $plugin_page ) ) {
+ return true;
+ }
+
+ $hookname = get_plugin_page_hookname( $plugin_page, get_admin_page_parent() );
+
+ if ( isset( $_registered_pages[ $hookname ] ) ) {
+ return true;
+ }
+
+ // The page may be registered for a capability the current user lacks.
+ if ( isset( $_wp_menu_nopriv[ $plugin_page ] ) || isset( $_wp_nopriv_pages[ $plugin_page ] ) ) {
+ return true;
+ }
+
+ foreach ( (array) $_wp_submenu_nopriv as $nopriv_submenus ) {
+ if ( isset( $nopriv_submenus[ $plugin_page ] ) ) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
/**
* Gets the hook name for the administrative page of a plugin.
*
@@ -2202,17 +2252,17 @@ function get_plugin_page_hookname( $plugin_page, $parent_page ) {
*
* @since 1.5.0
*
- * @global string $pagenow The filename of the current screen.
- * @global array $menu
- * @global array $submenu
- * @global array $_wp_menu_nopriv
- * @global array $_wp_submenu_nopriv
- * @global string $plugin_page
- * @global array $_registered_pages
+ * @global string $pagenow The filename of the current screen.
+ * @global array $menu The top-level admin menu items.
+ * @global array $submenu The admin submenu items, keyed by parent slug.
+ * @global array<string, true> $_wp_menu_nopriv Array of top-level menu slugs the current user cannot access.
+ * @global array<string, array<string, true>> $_wp_submenu_nopriv Array of submenu slugs the current user cannot access, keyed by parent slug.
+ * @global string|null $plugin_page The plugin page slug being loaded.
+ * @global array<string, true> $_registered_pages Array of all registered admin page hooks.
*
* @return bool True if the current user can access the admin page, false otherwise.
*/
-function user_can_access_admin_page() {
+function user_can_access_admin_page(): bool {
global $pagenow, $menu, $submenu, $_wp_menu_nopriv, $_wp_submenu_nopriv,
$plugin_page, $_registered_pages;
diff --git a/wp-includes/version.php b/wp-includes/version.php
index e6b6a1d6e1..e3dee3a630 100644
--- a/wp-includes/version.php
+++ b/wp-includes/version.php
@@ -16,7 +16,7 @@
*
* @global string $wp_version
*/
-$wp_version = '7.2-alpha-64350';
+$wp_version = '7.2-alpha-64351';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.