Index: src/wp-admin/admin-header.php
===================================================================
--- src/wp-admin/admin-header.php	(revision 38511)
+++ src/wp-admin/admin-header.php	(working copy)
@@ -32,12 +32,7 @@
 get_admin_page_title();
 $title = esc_html( strip_tags( $title ) );
 
-if ( is_network_admin() )
-	$admin_title = sprintf( __( 'Network Admin: %s' ), esc_html( get_current_site()->site_name ) );
-elseif ( is_user_admin() )
-	$admin_title = sprintf( __( 'User Dashboard: %s' ), esc_html( get_current_site()->site_name ) );
-else
-	$admin_title = get_bloginfo( 'name' );
+$admin_title = get_current_administration_panel( '', true )->admin_title;
 
 if ( $admin_title == $title )
 	$admin_title = sprintf( __( '%1$s &#8212; WordPress' ), $title );
@@ -226,20 +221,18 @@
 
 $current_screen->render_screen_meta();
 
-if ( is_network_admin() ) {
+$_panel = get_current_administration_panel( '', true );
+
+if ( 'site' !== $_panel->name ) {
 	/**
-	 * Prints network admin screen notices.
+	 * Prints admin screen notices for a specific Admin.
 	 *
-	 * @since 3.1.0
-	 */
-	do_action( 'network_admin_notices' );
-} elseif ( is_user_admin() ) {
-	/**
-	 * Prints user admin screen notices.
+	 * The dynamic portion of the hook name, `$panel_name`, refers to the Administration Panel name.
 	 *
 	 * @since 3.1.0
+	 * @since 4.7.0 Hook is now dynamic.
 	 */
-	do_action( 'user_admin_notices' );
+	do_action( $_panel->name . '_admin_notices' );
 } else {
 	/**
 	 * Prints admin screen notices.
@@ -249,6 +242,8 @@
 	do_action( 'admin_notices' );
 }
 
+unset( $_panel );
+
 /**
  * Prints generic admin screen notices.
  *
Index: src/wp-admin/admin.php
===================================================================
--- src/wp-admin/admin.php	(revision 38511)
+++ src/wp-admin/admin.php	(working copy)
@@ -83,6 +83,37 @@
 
 auth_redirect();
 
+// Register the additional administration panels.
+$site_name = function_exists( 'get_network' ) ? get_network()->site_name : get_bloginfo( 'name' );
+
+register_administration_panel( 'network', array(
+	'constant_name'  => 'WP_NETWORK_ADMIN',
+	'check_callback' => 'is_network_admin',
+	'url_callback'   => 'network_admin_url',
+	'menu_file'      => ABSPATH . 'wp-admin/network/menu.php',
+	'admin_title'    => sprintf( __( 'Network Admin: %s' ), esc_html( $site_name ) ),
+) );
+
+register_administration_panel( 'user', array(
+	'constant_name'  => 'WP_USER_ADMIN',
+	'check_callback' => 'is_user_admin',
+	'url_callback'   => 'user_admin_url',
+	'menu_file'      => ABSPATH . 'wp-admin/user/menu.php',
+	'admin_title'    => sprintf( __( 'User Dashboard: %s' ), esc_html( $site_name ) ),
+) );
+
+unset( $site_name );
+
+/**
+ * Fires before an admin screen or script is being initialized.
+ *
+ * Note, this does not just run on user-facing admin screens.
+ * It runs on admin-ajax.php and admin-post.php as well.
+ *
+ * @since 4.7.0
+ */
+do_action( 'admin_setup' );
+
 // Schedule trash collection
 if ( ! wp_next_scheduled( 'wp_scheduled_delete' ) && ! wp_installing() )
 	wp_schedule_event(time(), 'daily', 'wp_scheduled_delete');
@@ -130,12 +161,7 @@
 else
 	$taxnow = '';
 
-if ( WP_NETWORK_ADMIN )
-	require(ABSPATH . 'wp-admin/network/menu.php');
-elseif ( WP_USER_ADMIN )
-	require(ABSPATH . 'wp-admin/user/menu.php');
-else
-	require(ABSPATH . 'wp-admin/menu.php');
+require( get_current_administration_panel()->menu_file );
 
 if ( current_user_can( 'manage_options' ) ) {
 	wp_raise_memory_limit( 'admin' );
Index: src/wp-admin/includes/admin.php
===================================================================
--- src/wp-admin/includes/admin.php	(revision 38511)
+++ src/wp-admin/includes/admin.php	(working copy)
@@ -49,6 +49,10 @@
 /** WordPress Post Administration API */
 require_once(ABSPATH . 'wp-admin/includes/post.php');
 
+/** WordPress Administration Panel API */
+require_once(ABSPATH . 'wp-admin/includes/class-wp-administration-panel.php');
+require_once(ABSPATH . 'wp-admin/includes/administration-panel.php');
+
 /** WordPress Administration Screen API */
 require_once(ABSPATH . 'wp-admin/includes/class-wp-screen.php');
 require_once(ABSPATH . 'wp-admin/includes/screen.php');
Index: src/wp-admin/includes/administration-panel.php
===================================================================
--- src/wp-admin/includes/administration-panel.php	(revision 0)
+++ src/wp-admin/includes/administration-panel.php	(working copy)
@@ -0,0 +1,76 @@
+<?php
+/**
+ * WordPress Administration Panel API.
+ *
+ * @package WordPress
+ * @subpackage Administration
+ */
+
+/**
+ * Registers an administration panel.
+ *
+ * This function should be used on the 'admin_setup' hook.
+ *
+ * A function to register a custom administration panel from a plugin. WordPress also
+ * uses it to register the 'network' and 'user' administration panels.
+ *
+ * @since 4.7.0
+ *
+ * @param string $name Name of the administration panel. Must not contain dashes.
+ * @param array|string $args {
+ *     Array or string of administration panel arguments.
+ *
+ *     @type string   $constant_name  Required. Name of the constant to check for whether we're currently
+ *                                    in that administration panel.
+ *     @type callable $check_callback Required. A callback function that can be used to check whether we're
+ *                                    currently in that administration panel.
+ *     @type callable $url_callback   Required. A callback function to return the URL to the administration
+ *                                    panel. The function must accept two parameters, `$path` and `$scheme`
+ *                                    and return the full URL to the administration panel for that path.
+ *     @type string   $menu_file      Required. The filename of the PHP file to include to setup the menu
+ *                                    for the administration panel.
+ *     @type string   $admin_title    Optional. Title to use in the title tag.
+ * }
+ * @return WP_Administration_Panel|WP_Error The administration panel object on success, an error object otherwise.
+ */
+function register_administration_panel( $name, $args ) {
+	return WP_Administration_Panel::register( $name, $args );
+}
+
+/**
+ * Returns the administration panel object for a given name.
+ *
+ * @since 4.7.0
+ *
+ * @param string $name Name of the administration panel.
+ * @return WP_Administration_Panel|null Administration panel object or null if not found.
+ */
+function get_administration_panel( $name ) {
+	return WP_Administration_Panel::get( $name );
+}
+
+/**
+ * Returns the current administration panel object.
+ *
+ * @since 4.7.0
+ *
+ * @param bool $detect_via_callback Optional. Whether to detect the current panel via the callback function
+ *                                  instead of checking for the constant. Only used if $name is not provided.
+ *                                  Default false.
+ * @return WP_Administration_Panel Administration panel object.
+ */
+function get_current_administration_panel( $detect_via_callback = false ) {
+	return WP_Administration_Panel::get_current( $detect_via_callback );
+}
+
+/**
+ * Returns all administration panels.
+ *
+ * @since 4.7.0
+ *
+ * @param bool $include_default Optional. Whether to include the default administration panel. Default false.
+ * @return array Array of administration panel objects.
+ */
+function get_administration_panels( $include_default = false ) {
+	return WP_Administration_Panel::get_all( $include_default );
+}

Property changes on: src/wp-admin/includes/administration-panel.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: src/wp-admin/includes/class-wp-administration-panel.php
===================================================================
--- src/wp-admin/includes/class-wp-administration-panel.php	(revision 0)
+++ src/wp-admin/includes/class-wp-administration-panel.php	(working copy)
@@ -0,0 +1,292 @@
+<?php
+/**
+ * Administration Panel API: WP_Administration_Panel class
+ *
+ * @package WordPress
+ * @subpackage Administration
+ * @since 4.7.0
+ */
+
+/**
+ * Core class used to implement an administration panel API.
+ *
+ * @since 4.7.0
+ */
+final class WP_Administration_Panel {
+	/**
+	 * The panel identifier.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @var string
+	 */
+	public $name;
+
+	/**
+	 * The panel's constant name.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @var string
+	 */
+	public $constant_name;
+
+	/**
+	 * The panel's check function.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @var callable
+	 */
+	public $check_callback;
+
+	/**
+	 * The panel's URL function.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @var callable
+	 */
+	public $url_callback;
+
+	/**
+	 * The panel's menu file path.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @var string
+	 */
+	public $menu_file;
+
+	/**
+	 * The panel admin title.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @var string
+	 */
+	public $admin_title;
+
+	/**
+	 * The registered administration panels.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @static
+	 * @var array
+	 */
+	private static $panels = array();
+
+	/**
+	 * The default administration panel.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 * @static
+	 * @var WP_Administration_Panel
+	 */
+	private static $default_panel;
+
+	/**
+	 * Registers an administration panel.
+	 *
+	 * This function should be used on the 'admin_setup' hook.
+	 * It is by default used to register the 'network' and 'user' administration panels.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @static
+	 *
+	 * @see register_administration_panel()
+	 *
+	 * @param string $name Name of the administration panel. Must not contain dashes.
+	 * @param array|string $args {
+	 *     Array or string of administration panel arguments.
+	 *
+	 *     @type string   $constant_name  Required. Name of the constant to check for whether we're currently
+	 *                                    in that administration panel.
+	 *     @type callable $check_callback Required. A callback function that can be used to check whether we're
+	 *                                    currently in that administration panel.
+	 *     @type callable $url_callback   Required. A callback function to return the URL to the administration
+	 *                                    panel. The function must accept two parameters, `$path` and `$scheme`
+	 *                                    and return the full URL to the administration panel for that path.
+	 *     @type string   $menu_file      Required. The filename of the PHP file to include to setup the menu
+	 *                                    for the administration panel.
+	 *     @type string   $admin_title    Optional. Title to use in the title tag.
+	 * }
+	 * @return WP_Administration_Panel|WP_Error The administration panel object on success, an error object otherwise.
+	 */
+	public static function register( $name, $args ) {
+		// 'site' represents the default administration panel, so it is not explicitly registered, but still forbidden.
+		if ( self::get_default()->name === $name ) {
+			return new WP_Error( 'admin_panel_already_exist', sprintf( __( 'The administration panel %s already exists.' ), 'site' ) );
+		}
+
+		// It is not allowed to override an existing administration panel.
+		if ( isset( self::$panels[ $name ] ) ) {
+			return new WP_Error( 'admin_panel_already_exist', sprintf( __( 'The administration panel %s already exists.' ), $name ) );
+		}
+
+		$args = wp_parse_args( $args );
+
+		$required = array( 'constant_name', 'check_callback', 'url_callback', 'menu_file' );
+
+		foreach ( $required as $key ) {
+			if ( empty( $args[ $key ] ) ) {
+				return new WP_Error( 'empty_panel_argument', sprintf( __( 'The administration panel argument %s must not be empty.' ), $key ) );
+			}
+		}
+
+		$panel = new self( $name, $args );
+
+		self::$panels[ $name ] = $panel;
+
+		return $panel;
+	}
+
+	/**
+	 * Returns the administration panel object for a given name.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @static
+	 *
+	 * @see get_administration_panel()
+	 *
+	 * @param string $name Name of the administration panel.
+	 * @return WP_Administration_Panel|null Administration panel object or null if not found.
+	 */
+	public static function get( $name ) {
+		if ( self::get_default()->name === $name ) {
+			return self::get_default();
+		}
+
+		if ( ! isset( self::$panels[ $name ] ) ) {
+			return null;
+		}
+
+		return self::$panels[ $name ];
+	}
+
+	/**
+	 * Returns the current administration panel object.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @static
+	 *
+	 * @see get_current_administration_panel()
+	 *
+	 * @param bool $detect_via_callback Optional. Whether to detect the current panel via the callback function
+	 *                                  instead of checking for the constant. Only used if $name is not provided.
+	 *                                  Default false.
+	 * @return WP_Administration_Panel Administration panel object.
+	 */
+	public static function get_current( $detect_via_callback = false ) {
+		foreach ( self::$panels as $panel_name => $panel ) {
+			if ( $panel->is_active( $detect_via_callback ) ) {
+				return $panel;
+			}
+		}
+
+		return self::get_default();
+	}
+
+	/**
+	 * Returns all administration panels.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @static
+	 *
+	 * @see get_administration_panels()
+	 *
+	 * @param bool $include_default Optional. Whether to include the default administration panel. Default false.
+	 * @return array Array of administration panel objects.
+	 */
+	public static function get_all( $include_default = false ) {
+		$panels = self::$panels;
+
+		if ( $include_default ) {
+			$panels[ self::$default_panel->name ] = self::$default_panel;
+		}
+
+		return $panels;
+	}
+
+	/**
+	 * Returns the default administration panel.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 * @static
+	 *
+	 * @return WP_Administration_Panel The default administration panel.
+	 */
+	public static function get_default() {
+		if ( ! isset( self::$default_panel ) ) {
+			self::$default_panel = new self( 'site', array(
+				'constant_name'  => 'WP_BLOG_ADMIN',
+				'check_callback' => 'is_blog_admin',
+				'url_callback'   => 'admin_url',
+				'menu_file'      => ABSPATH . 'wp-admin/menu.php',
+			) );
+		}
+
+		return self::$default_panel;
+	}
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 4.7.0
+	 * @access private
+	 *
+	 * @param string       $name Name of the administration panel. Must not contain dashes.
+	 * @param array|string $args Array or string of arguments for registering an administration panel.
+	 */
+	private function __construct( $name, $args ) {
+		$defaults = array(
+			'constant_name'  => '',
+			'check_callback' => null,
+			'url_callback'   => null,
+			'menu_file'      => '',
+			'admin_title'    => '',
+		);
+
+		$args = wp_parse_args( $args, $defaults );
+
+		if ( empty( $args['admin_title'] ) ) {
+			$args['admin_title'] = get_bloginfo( 'name' );
+		}
+
+		$this->name = $name;
+
+		foreach ( $args as $key => $value ) {
+			$this->$key = $value;
+		}
+	}
+
+	/**
+	 * Checks whether this is the currently active administration panel.
+	 *
+	 * @since 4.7.0
+	 * @access public
+	 *
+	 * @param bool $detect_via_callback Optional. Whether to detect the current panel via the callback function
+	 *                                  instead of checking for the constant. Only used if $name is not provided.
+	 *                                  Default false.
+	 * @return bool True if active, false otherwise.
+	 */
+	public function is_active( $detect_via_callback = false ) {
+		if ( $detect_via_callback && call_user_func( $this->check_callback ) ) {
+			return true;
+		}
+
+		if ( ! $detect_via_callback && defined( $this->constant_name ) && constant( $this->constant_name ) ) {
+			return true;
+		}
+
+		return false;
+	}
+}

Property changes on: src/wp-admin/includes/class-wp-administration-panel.php
___________________________________________________________________
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Index: src/wp-admin/includes/class-wp-screen.php
===================================================================
--- src/wp-admin/includes/class-wp-screen.php	(revision 38511)
+++ src/wp-admin/includes/class-wp-screen.php	(working copy)
@@ -243,12 +243,13 @@
 		}
 
 		if ( ! $post_type && $hook_name ) {
-			if ( '-network' == substr( $id, -8 ) ) {
-				$id = substr( $id, 0, -8 );
-				$in_admin = 'network';
-			} elseif ( '-user' == substr( $id, -5 ) ) {
-				$id = substr( $id, 0, -5 );
-				$in_admin = 'user';
+			foreach ( get_administration_panels() as $panel ) {
+				$length = -1 - strlen( $panel->name );
+				if ( '-' . $panel->name == substr( $id, $length ) ) {
+					$id = substr( $id, 0, $length );
+					$in_admin = $panel->name;
+					break;
+				}
 			}
 
 			$id = sanitize_key( $id );
@@ -266,12 +267,7 @@
 			if ( ! $in_admin )
 				$in_admin = 'site';
 		} else {
-			if ( defined( 'WP_NETWORK_ADMIN' ) && WP_NETWORK_ADMIN )
-				$in_admin = 'network';
-			elseif ( defined( 'WP_USER_ADMIN' ) && WP_USER_ADMIN )
-				$in_admin = 'user';
-			else
-				$in_admin = 'site';
+			$in_admin = get_current_administration_panel()->name;
 		}
 
 		if ( 'index' == $id )
@@ -337,12 +333,12 @@
 				break;
 		}
 
-		if ( 'network' == $in_admin ) {
-			$id   .= '-network';
-			$base .= '-network';
-		} elseif ( 'user' == $in_admin ) {
-			$id   .= '-user';
-			$base .= '-user';
+		foreach ( get_administration_panels() as $panel ) {
+			if ( $panel->name == $in_admin ) {
+				$id   .= '-' . $panel->name;
+				$base .= '-' . $panel->name;
+				break;
+			}
 		}
 
 		if ( isset( self::$_registry[ $id ] ) ) {
Index: src/wp-admin/includes/menu.php
===================================================================
--- src/wp-admin/includes/menu.php	(revision 38511)
+++ src/wp-admin/includes/menu.php	(working copy)
@@ -6,28 +6,21 @@
  * @subpackage Administration
  */
 
-if ( is_network_admin() ) {
+$_panel_name = get_current_administration_panel()->name;
+if ( 'site' !== $_panel_name ) {
 
 	/**
-	 * Fires before the administration menu loads in the Network Admin.
+	 * Fires before the administration menu loads in a specific Admin.
 	 *
 	 * The hook fires before menus and sub-menus are removed based on user privileges.
 	 *
-	 * @private
-	 * @since 3.1.0
-	 */
-	do_action( '_network_admin_menu' );
-} elseif ( is_user_admin() ) {
-
-	/**
-	 * Fires before the administration menu loads in the User Admin.
+	 * The dynamic portion of the hook name, `$panel_name`, refers to the Administration Panel name.
 	 *
-	 * The hook fires before menus and sub-menus are removed based on user privileges.
-	 *
 	 * @private
 	 * @since 3.1.0
+	 * @since 4.7.0 Hook is now dynamic.
 	 */
-	do_action( '_user_admin_menu' );
+	do_action( '_' . $_panel_name . '_admin_menu' );
 } else {
 
 	/**
@@ -117,26 +110,20 @@
 }
 unset($id, $data, $subs, $first_sub, $old_parent, $new_parent);
 
-if ( is_network_admin() ) {
+if ( 'site' !== $_panel_name ) {
 
 	/**
-	 * Fires before the administration menu loads in the Network Admin.
+	 * Fires before the administration menu loads in a specific Admin.
 	 *
-	 * @since 3.1.0
+	 * The dynamic portion of the hook name, `$panel_name`, refers to the Administration Panel name.
 	 *
-	 * @param string $context Empty context.
-	 */
-	do_action( 'network_admin_menu', '' );
-} elseif ( is_user_admin() ) {
-
-	/**
-	 * Fires before the administration menu loads in the User Admin.
-	 *
+	 * @private
 	 * @since 3.1.0
+	 * @since 4.7.0 Hook is now dynamic.
 	 *
 	 * @param string $context Empty context.
 	 */
-	do_action( 'user_admin_menu', '' );
+	do_action( $_panel_name . '_admin_menu', '' );
 } else {
 
 	/**
@@ -149,6 +136,8 @@
 	do_action( 'admin_menu', '' );
 }
 
+unset( $_panel_name );
+
 /*
  * Remove menus that have no accessible submenus and require privileges
  * that the user does not have. Run re-parent loop again.
Index: src/wp-includes/link-template.php
===================================================================
--- src/wp-includes/link-template.php	(revision 38511)
+++ src/wp-includes/link-template.php	(working copy)
@@ -3398,12 +3398,12 @@
  * @return string Admin URL link with optional path appended.
  */
 function self_admin_url( $path = '', $scheme = 'admin' ) {
-	if ( is_network_admin() )
-		return network_admin_url($path, $scheme);
-	elseif ( is_user_admin() )
-		return user_admin_url($path, $scheme);
-	else
-		return admin_url($path, $scheme);
+	if ( function_exists( 'get_current_administration_panel' ) ) {
+		$panel = get_current_administration_panel( true );
+		return call_user_func( $panel->url_callback, $path, $scheme );
+	}
+
+	return admin_url( $path, $scheme );
 }
 
 /**
