diff --git wp-admin/admin.php wp-admin/admin.php
index 6db6e32..5a3384f 100644
--- wp-admin/admin.php
+++ wp-admin/admin.php
@@ -98,6 +98,8 @@ if ( isset( $_REQUEST['taxonomy'] ) && taxonomy_exists( $_REQUEST['taxonomy'] )
 else
 	$taxnow = '';
 
+require(ABSPATH . 'wp-admin/includes/class-wp-admin-menu.php');
+
 if ( WP_NETWORK_ADMIN )
 	require(ABSPATH . 'wp-admin/network/menu.php');
 elseif ( WP_USER_ADMIN )
diff --git wp-admin/includes/class-wp-admin-menu.php wp-admin/includes/class-wp-admin-menu.php
new file mode 100644
index 0000000..4c1b11e
--- /dev/null
+++ wp-admin/includes/class-wp-admin-menu.php
@@ -0,0 +1,326 @@
+<?php
+
+/**
+ * A single menu item, with children
+ */
+class WP_Admin_Menu_Item {
+
+	protected $children;
+
+	function __construct( $payload ) {
+
+		if ( !isset( $payload['id'] ) )
+			$payload['id'] = $payload['slug'];
+
+		if ( isset( $payload['cap'] ) )
+			$payload['cap'] = $this->convert_caps( $payload['cap'] );
+
+		foreach ( $payload as $key => $value )
+			$this->$key = $value;
+
+		$this->children = array();
+	}
+
+	protected function prepare_item( $payload ) {
+		if ( is_a( $payload, __CLASS__ ) )
+			return $payload;
+
+		return new WP_Admin_Menu_Item( $payload );
+	}
+
+	// Return the first cap that the user has or last cap
+	protected function convert_caps( $caps ) {
+		foreach ( (array) $caps as $cap ) {
+			if ( current_user_can( $cap ) )
+				break;
+		}
+
+		return $cap;
+	}
+
+	function append( $payload ) {
+		$item = $this->prepare_item( $payload );
+
+		if ( isset( $this->children[ $item->id ] ) )
+			return false;
+
+		$this->children[ $item->id ] = $item;
+
+		return true;
+	}
+
+	function insert_before( $ref_id, $payload ) {
+		if ( !isset( $this->children[ $ref_id ] ) )
+			return false;
+
+		$new_array = array();
+
+		$item = $this->prepare_item( $payload );
+
+		foreach ( $this->children as $key => $value ) {
+			if ( $key == $ref_id ) {
+				$new_array[ $item->id ] = $item;
+			}
+
+			$new_array[ $key ] = $value;
+		}
+
+		$this->children = $new_array;
+
+		return true;
+	}
+
+	function insert_after( $ref_id, $payload ) {
+		if ( !isset( $this->children[ $ref_id ] ) )
+			return false;
+
+		$new_array = array();
+
+		$item = $this->prepare_item( $payload );
+
+		foreach ( $this->children as $key => $value ) {
+			$new_array[ $key ] = $value;
+
+			if ( $key == $ref_id ) {
+				$new_array[ $item->id ] = $item;
+			}
+		}
+
+		$this->children = $new_array;
+
+		return true;
+	}
+
+	function replace( $ref_id, $payload ) {
+		if ( !$this->insert_after( $ref_id, $payload ) )
+			return false;
+
+		$this->remove( $ref_id );
+	}
+
+	function contains( $id ) {
+		return isset( $this->children[ $id ] );
+	}
+
+	function get( $id, $field = 'id' ) {
+		if ( 'id' != $field ) {
+			$items = $this->get_children( array( $field => $id ) );
+			if ( empty( $items ) )
+				return false;
+
+			return reset( $items );
+		}
+
+		if ( !isset( $this->children[ $id ] ) )
+			return false;
+
+		return $this->children[ $id ];
+	}
+
+	function has_children() {
+		return !empty( $this->children );
+	}
+
+	function get_children( $args = array() ) {
+		return wp_list_filter( $this->children, $args );
+	}
+
+	function remove( $id ) {
+		if ( !isset( $this->children[ $id ] ) )
+			return false;
+
+		unset( $this->children[ $id ] );
+
+		return true;
+	}
+}
+
+
+/**
+ * The root menu item, with some convenience methods
+ */
+class WP_Admin_Menu extends WP_Admin_Menu_Item {
+
+	function __construct() {
+		$this->children = array();
+	}
+
+	function append( $payload ) {
+		$payload = wp_parse_args( $payload, array(
+			'icon' => 'div'
+		) );
+
+		if ( !isset( $payload['class'] ) ) {
+			$payload['class'] = 'menu-icon-' . $payload['id'];
+		}
+
+		parent::append( $payload );
+	}
+
+	// Convenience method
+	function add_submenu( $parent_id, $payload ) {
+		$parent = $this->get( $parent_id );
+
+		if ( ! $parent )
+			return false;
+
+		return $parent->append( $payload );
+	}
+
+	// Super-convenience method
+	function add_first_submenu( $parent_id, $title ) {
+		$parent = $this->get( $parent_id );
+
+		if ( ! $parent )
+			return false;
+
+		return $parent->append( array(
+			'title' => $title,
+			'cap' => $parent->cap,
+			'slug' => $parent->slug,
+		) );
+	}
+
+	/** @private */
+	function _add_post_type_menus() {
+		$cpt_list = get_post_types( array(
+			'show_ui' => true,
+			'_builtin' => false,
+			'show_in_menu' => true
+		) );
+
+		foreach ( $cpt_list as $ptype ) {
+			$ptype_obj = get_post_type_object( $ptype );
+
+			if ( true !== $ptype_obj->show_in_menu )
+				continue; // handled in _add_post_type_submenus()
+
+			$ptype_for_id = sanitize_html_class( $ptype );
+
+			if ( is_string( $ptype_obj->menu_icon ) ) {
+				$admin_menu_icon = esc_url( $ptype_obj->menu_icon );
+				$ptype_class = $ptype_for_id;
+			} else {
+				$admin_menu_icon = 'div';
+				$ptype_class = 'post';
+			}
+
+			$args = array(
+				'title' => esc_attr( $ptype_obj->labels->menu_name ),
+				'cap' => $ptype_obj->cap->edit_posts,
+				'class' => 'menu-icon-' . $ptype_class,
+				'id' => 'posts-' . $ptype_for_id,
+				'slug' => "edit.php?post_type=$ptype",
+				'icon' => $admin_menu_icon,
+			);
+
+			if ( $ptype_obj->menu_position ) {
+				$before = $ptype_obj->menu_position;
+			} else {
+				$before = 'separator2';
+			}
+
+			$this->insert_before( $before, $args );
+
+			$this->add_first_submenu( 'posts-' . $ptype_for_id, $ptype_obj->labels->all_items );
+
+			$this->add_submenu( 'posts-' . $ptype_for_id, array(
+				'title' => $ptype_obj->labels->add_new,
+				'cap' => $ptype_obj->cap->edit_posts,
+				'slug' => "post-new.php?post_type=$ptype",
+			) );
+
+			$this->_add_tax_submenus( 'posts-' . $ptype_for_id, $ptype );
+		}
+	}
+
+	/** @private */
+	function _add_post_type_submenus() {
+		foreach ( get_post_types( array( 'show_ui' => true ) ) as $ptype ) {
+			$ptype_obj = get_post_type_object( $ptype );
+
+			// Submenus only.
+			if ( ! $ptype_obj->show_in_menu || $ptype_obj->show_in_menu === true )
+				continue;
+
+			add_submenu_page( $ptype_obj->show_in_menu, $ptype_obj->labels->name, $ptype_obj->labels->all_items, $ptype_obj->cap->edit_posts, "edit.php?post_type=$ptype" );
+		}
+	}
+
+	/** @private */
+	function _add_tax_submenus( $parent_id, $ptype ) {
+		foreach ( get_taxonomies( array(), 'objects' ) as $tax ) {
+			if ( ! $tax->show_ui || ! in_array($ptype, (array) $tax->object_type, true) )
+				continue;
+
+			$slug = 'edit-tags.php?taxonomy=' . $tax->name;
+
+			if ( 'post' != $ptype )
+				$slug .= '&amp;post_type=' . $ptype;
+
+			$this->add_submenu( $parent_id, array(
+				'title' => esc_attr( $tax->labels->menu_name ),
+				'cap' => $tax->cap->manage_terms,
+				'slug' => $slug,
+			) );
+		}
+	}
+
+	/** @private */
+	function _loop( $callback ) {
+		foreach ( $this->get_children() as $item ) {
+			if ( !isset( $item->slug ) )
+				continue;
+
+			call_user_func( $callback, $item, $this );
+		}
+	}
+}
+
+
+// TODO: use in admin bar?
+/** @private */
+function _admin_menu_comment_count( $awaiting_mod ) {
+	$count = sprintf(
+		"<span class='awaiting-mod count-%s'><span class='pending-count'>%s</span></span>",
+		$awaiting_mod,
+		number_format_i18n( $awaiting_mod )
+	);
+
+	return sprintf( __('Comments %s'), $count );
+}
+
+/** @private */
+function _admin_menu_update_count( $update_data ) {
+	$count = sprintf(
+		"<span class='update-plugins count-%s' title='%s'><span class='update-count'>%s</span></span>",
+		$update_data['counts']['total'],
+		$update_data['title'],
+		number_format_i18n( $update_data['counts']['total'] )
+	);
+
+	return sprintf( __( 'Updates %s' ), $count );
+}
+
+/** @private */
+function _admin_menu_plugin_update_count( $update_data ) {
+	$count = sprintf(
+		"<span class='update-plugins count-%s'><span class='plugin-count'>%s</span></span>",
+		$update_data['counts']['plugins'],
+		number_format_i18n( $update_data['counts']['plugins'] )
+	);
+
+	return sprintf( __( 'Plugins %s' ), $count );
+}
+
+/** @private */
+function _admin_menu_theme_update_count( $update_data ) {
+	$count = sprintf(
+		"<span class='update-plugins count-%s'><span class='theme-count'>%s</span></span>",
+		$update_data['counts']['themes'],
+		number_format_i18n( $update_data['counts']['themes'] )
+	);
+
+	return sprintf( __( 'Themes %s' ), $count );
+}
+
diff --git wp-admin/includes/menu.php wp-admin/includes/menu.php
index 14cadfa..5d61b64 100644
--- wp-admin/includes/menu.php
+++ wp-admin/includes/menu.php
@@ -14,12 +14,20 @@ elseif ( is_user_admin() )
 else
 	do_action('_admin_menu');
 
+$_wp_submenu_nopriv = array();
+$_wp_menu_nopriv = array();
+
+$admin_menu->_loop( '_generate_admin_page_hooks' );
+$admin_menu->_loop( '_check_admin_submenu_privs' );
+
 // Create list of page plugin hook names.
-foreach ($menu as $menu_page) {
-	if ( false !== $pos = strpos($menu_page[2], '?') ) {
+function _generate_admin_page_hooks( $menu_item, $admin_menu ) {
+	global $admin_page_hooks;
+
+	if ( false !== $pos = strpos($menu_item->slug, '?') ) {
 		// Handle post_type=post|page|foo pages.
-		$hook_name = substr($menu_page[2], 0, $pos);
-		$hook_args = substr($menu_page[2], $pos + 1);
+		$hook_name = substr($menu_item->slug, 0, $pos);
+		$hook_args = substr($menu_item->slug, $pos + 1);
 		wp_parse_str($hook_args, $hook_args);
 		// Set the hook name to be the post type.
 		if ( isset($hook_args['post_type']) )
@@ -28,7 +36,7 @@ foreach ($menu as $menu_page) {
 			$hook_name = basename($hook_name, '.php');
 		unset($hook_args);
 	} else {
-		$hook_name = basename($menu_page[2], '.php');
+		$hook_name = basename($menu_item->slug, '.php');
 	}
 	$hook_name = sanitize_title($hook_name);
 
@@ -37,57 +45,45 @@ foreach ($menu as $menu_page) {
 	elseif ( !$hook_name )
 		continue;
 
-	$admin_page_hooks[$menu_page[2]] = $hook_name;
+	$admin_page_hooks[$menu_item->slug] = $hook_name;
 }
-unset($menu_page, $compat);
 
-$_wp_submenu_nopriv = array();
-$_wp_menu_nopriv = array();
-// Loop over submenus and remove pages for which the user does not have privs.
-foreach ( array( 'submenu' ) as $sub_loop ) {
-	foreach ($$sub_loop as $parent => $sub) {
-		foreach ($sub as $index => $data) {
-			if ( ! current_user_can($data[1]) ) {
-				unset(${$sub_loop}[$parent][$index]);
-				$_wp_submenu_nopriv[$parent][$data[2]] = true;
-			}
-		}
-		unset($index, $data);
+function _check_admin_submenu_privs( $menu_item, $admin_menu ) {
+	global $_wp_submenu_nopriv;
 
-		if ( empty(${$sub_loop}[$parent]) )
-			unset(${$sub_loop}[$parent]);
+	// Loop over submenus and remove items for which the user does not have privs.
+	foreach ( $menu_item->get_children() as $submenu ) {
+		if ( !current_user_can( $submenu->cap ) ) {
+			$menu_item->remove( $submenu->id );
+			$_wp_submenu_nopriv[$menu_item->slug][$submenu->slug] = true;
+		}
 	}
-	unset($sub, $parent);
-}
-unset($sub_loop);
 
-// Loop over the top-level menu.
-// Menus for which the original parent is not accessible due to lack of privs will have the next
-// submenu in line be assigned as the new menu parent.
-foreach ( $menu as $id => $data ) {
-	if ( empty($submenu[$data[2]]) )
-		continue;
-	$subs = $submenu[$data[2]];
-	$first_sub = array_shift($subs);
-	$old_parent = $data[2];
-	$new_parent = $first_sub[2];
-	// If the first submenu is not the same as the assigned parent,
-	// make the first submenu the new parent.
-	if ( $new_parent != $old_parent ) {
-		$_wp_real_parent_file[$old_parent] = $new_parent;
-		$menu[$id][2] = $new_parent;
+	// Menus for which the original parent is not accessible due to lack of privs
+	// will have the next submenu in line be assigned as the new menu parent.
+	$subs = $menu_item->get_children();
+
+	if ( empty( $subs ) )
+		return;
+
+	$first_sub = array_shift( $subs );
 
-		foreach ($submenu[$old_parent] as $index => $data) {
-			$submenu[$new_parent][$index] = $submenu[$old_parent][$index];
-			unset($submenu[$old_parent][$index]);
+	$old_parent = $menu_item->slug;
+	$new_parent = $first_sub->slug;
+
+	if ( $new_parent != $old_parent ) {
+		foreach ( $subs as $sub ) {
+			$first_sub->append( $sub );
 		}
-		unset($submenu[$old_parent], $index);
+
+		$admin_menu->replace( $menu_item->id, $first_sub );
+
+		$_wp_real_parent_file[$old_parent] = $new_parent;
 
 		if ( isset($_wp_submenu_nopriv[$old_parent]) )
 			$_wp_submenu_nopriv[$new_parent] = $_wp_submenu_nopriv[$old_parent];
 	}
 }
-unset($id, $data, $subs, $first_sub, $old_parent, $new_parent);
 
 if ( is_network_admin() )
 	do_action('network_admin_menu', '');
@@ -96,134 +92,53 @@ elseif ( is_user_admin() )
 else
 	do_action('admin_menu', '');
 
-// Remove menus that have no accessible submenus and require privs that the user does not have.
-// Run re-parent loop again.
-foreach ( $menu as $id => $data ) {
-	if ( ! current_user_can($data[1]) )
-		$_wp_menu_nopriv[$data[2]] = true;
+$admin_menu->_loop( '_check_admin_menu_privs' );
+
+// Remove menus that have no accessible submenus
+// and require privs that the user does not have.
+function _check_admin_menu_privs( $menu_item, $admin_menu ) {
+	global $_wp_menu_nopriv;
+
+	if ( ! current_user_can( $menu_item->cap ) )
+		$_wp_menu_nopriv[$menu_item->slug] = true;
+
+	$subs = $menu_item->get_children();
 
 	// If there is only one submenu and it is has same destination as the parent,
 	// remove the submenu.
-	if ( ! empty( $submenu[$data[2]] ) && 1 == count ( $submenu[$data[2]] ) ) {
-		$subs = $submenu[$data[2]];
-		$first_sub = array_shift($subs);
-		if ( $data[2] == $first_sub[2] )
-			unset( $submenu[$data[2]] );
+	if ( ! empty( $subs ) && 1 == count( $subs ) ) {
+		$first_sub = array_shift( $subs );
+		if ( $menu_item->slug == $first_sub->slug )
+			$menu_item->remove( $first_sub->id );
 	}
 
 	// If submenu is empty...
-	if ( empty($submenu[$data[2]]) ) {
+	if ( !$menu_item->has_children() ) {
 		// And user doesn't have privs, remove menu.
-		if ( isset( $_wp_menu_nopriv[$data[2]] ) ) {
-			unset($menu[$id]);
+		if ( isset( $_wp_menu_nopriv[$menu_item->slug] ) ) {
+			$admin_menu->remove( $menu_item->id );
 		}
 	}
 }
-unset($id, $data, $subs, $first_sub);
 
 // Remove any duplicated separators
 $separator_found = false;
-foreach ( $menu as $id => $data ) {
-	if ( 0 == strcmp('wp-menu-separator', $data[4] ) ) {
-		if (false == $separator_found) {
+foreach ( $admin_menu->get_children() as $menu_item ) {
+	if ( 'wp-menu-separator' == $menu_item->class ) {
+		if ( !$separator_found ) {
 			$separator_found = true;
 		} else {
-			unset($menu[$id]);
+			$admin_menu->remove( $menu_item->id );
 			$separator_found = false;
 		}
 	} else {
 		$separator_found = false;
 	}
 }
-unset($id, $data);
-
-function add_cssclass($add, $class) {
-	$class = empty($class) ? $add : $class .= ' ' . $add;
-	return $class;
-}
-
-function add_menu_classes($menu) {
-
-	$first = $lastorder = false;
-	$i = 0;
-	$mc = count($menu);
-	foreach ( $menu as $order => $top ) {
-		$i++;
-
-		if ( 0 == $order ) { // dashboard is always shown/single
-			$menu[0][4] = add_cssclass('menu-top-first', $top[4]);
-			$lastorder = 0;
-			continue;
-		}
-
-		if ( 0 === strpos($top[2], 'separator') ) { // if separator
-			$first = true;
-			$c = $menu[$lastorder][4];
-			$menu[$lastorder][4] = add_cssclass('menu-top-last', $c);
-			continue;
-		}
-
-		if ( $first ) {
-			$c = $menu[$order][4];
-			$menu[$order][4] = add_cssclass('menu-top-first', $c);
-			$first = false;
-		}
-
-		if ( $mc == $i ) { // last item
-			$c = $menu[$order][4];
-			$menu[$order][4] = add_cssclass('menu-top-last', $c);
-		}
-
-		$lastorder = $order;
-	}
-
-	return apply_filters( 'add_menu_classes', $menu );
-}
-
-uksort($menu, "strnatcasecmp"); // make it all pretty
-
-if ( apply_filters('custom_menu_order', false) ) {
-	$menu_order = array();
-	foreach ( $menu as $menu_item ) {
-		$menu_order[] = $menu_item[2];
-	}
-	unset($menu_item);
-	$default_menu_order = $menu_order;
-	$menu_order = apply_filters('menu_order', $menu_order);
-	$menu_order = array_flip($menu_order);
-	$default_menu_order = array_flip($default_menu_order);
-
-	function sort_menu($a, $b) {
-		global $menu_order, $default_menu_order;
-		$a = $a[2];
-		$b = $b[2];
-		if ( isset($menu_order[$a]) && !isset($menu_order[$b]) ) {
-			return -1;
-		} elseif ( !isset($menu_order[$a]) && isset($menu_order[$b]) ) {
-			return 1;
-		} elseif ( isset($menu_order[$a]) && isset($menu_order[$b]) ) {
-			if ( $menu_order[$a] == $menu_order[$b] )
-				return 0;
-			return ($menu_order[$a] < $menu_order[$b]) ? -1 : 1;
-		} else {
-			return ($default_menu_order[$a] <= $default_menu_order[$b]) ? -1 : 1;
-		}
-	}
-
-	usort($menu, 'sort_menu');
-	unset($menu_order, $default_menu_order);
-}
-
-// Remove the last menu item if it is a separator.
-$last_menu_key = array_keys( $menu );
-$last_menu_key = array_pop( $last_menu_key );
-if ( !empty( $menu ) && 'wp-menu-separator' == $menu[ $last_menu_key ][ 4 ] )
-	unset( $menu[ $last_menu_key ] );
-unset( $last_menu_key );
+unset($separator_found, $menu_item);
 
 if ( !user_can_access_admin_page() ) {
 	do_action('admin_page_access_denied');
 	wp_die( __('You do not have sufficient permissions to access this page.') );
 }
 
-$menu = add_menu_classes($menu);
diff --git wp-admin/includes/plugin.php wp-admin/includes/plugin.php
index 941f545..4f97259 100644
--- wp-admin/includes/plugin.php
+++ wp-admin/includes/plugin.php
@@ -885,38 +885,49 @@ function uninstall_plugin($plugin) {
  * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu)
  * @param callback $function The function to be called to output the content for this page.
  * @param string $icon_url The url to the icon to be used for this menu
- * @param int $position The position in the menu order this one should appear
+ * @param string $before The menu item before which this one should appear
  *
  * @return string The resulting page's hook_suffix
  */
-function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '', $position = null ) {
-	global $menu, $admin_page_hooks, $_registered_pages, $_parent_pages;
+function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '', $before = null ) {
+	global $admin_menu, $admin_page_hooks, $_registered_pages, $_parent_pages;
 
-	$menu_slug = plugin_basename( $menu_slug );
+	if ( is_numeric( $before ) ) {
+		_deprecated_argument( __FUNCTION__, '3.5', __( 'Numeric menu positions are deprecated. Use menu ids instead.' ) );
+		$before = null;
+	}
 
-	$admin_page_hooks[$menu_slug] = sanitize_title( $menu_title );
+	$menu_slug = plugin_basename( $menu_slug );
 
 	$hookname = get_plugin_page_hookname( $menu_slug, '' );
 
 	if ( !empty( $function ) && !empty( $hookname ) && current_user_can( $capability ) )
 		add_action( $hookname, $function );
 
+	$admin_page_hooks[$menu_slug] = sanitize_title( $menu_title );
+
+	$_registered_pages[$hookname] = true;
+
+	// No parent as top level
+	$_parent_pages[$menu_slug] = false;
+
 	if ( empty($icon_url) )
 		$icon_url = esc_url( admin_url( 'images/generic.png' ) );
 	elseif ( is_ssl() && 0 === strpos($icon_url, 'http://') )
 		$icon_url = 'https://' . substr($icon_url, 7);
 
-	$new_menu = array( $menu_title, $capability, $menu_slug, $page_title, 'menu-top ' . $hookname, $hookname, $icon_url );
+	$menu_args = array(
+		'title' => $menu_title,
+		'cap' => $capability,
+		'slug' => $menu_slug,
+		'class' => 'menu-top ' . $hookname,
+		'icon' => $icon_url
+	);
 
-	if ( null === $position )
-		$menu[] = $new_menu;
+	if ( null === $before )
+		$admin_menu->append( $args );
 	else
-		$menu[$position] = $new_menu;
-
-	$_registered_pages[$hookname] = true;
-
-	// No parent as top level
-	$_parent_pages[$menu_slug] = false;
+		$admin_menu->insert_before( $before, $args );
 
 	return $hookname;
 }
@@ -940,11 +951,7 @@ function add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $func
  * @return string The resulting page's hook_suffix
  */
 function add_object_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '') {
-	global $_wp_last_object_menu;
-
-	$_wp_last_object_menu++;
-
-	return add_menu_page($page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $_wp_last_object_menu);
+	return add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, 'separator2' );
 }
 
 /**
@@ -966,11 +973,7 @@ function add_object_page( $page_title, $menu_title, $capability, $menu_slug, $fu
  * @return string The resulting page's hook_suffix
  */
 function add_utility_page( $page_title, $menu_title, $capability, $menu_slug, $function = '', $icon_url = '') {
-	global $_wp_last_utility_menu;
-
-	$_wp_last_utility_menu++;
-
-	return add_menu_page($page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, $_wp_last_utility_menu);
+	return add_menu_page( $page_title, $menu_title, $capability, $menu_slug, $function, $icon_url, 'separator-last' );
 }
 
 /**
@@ -989,48 +992,60 @@ function add_utility_page( $page_title, $menu_title, $capability, $menu_slug, $f
  * @param string $menu_slug The slug name to refer to this menu by (should be unique for this menu)
  * @param callback $function The function to be called to output the content for this page.
  *
- * @return string|bool The resulting page's hook_suffix, or false if the user does not have the capability required.
+ * @return string|bool The resulting page's hook_suffix, or false on failure
  */
 function add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
-	global $submenu;
-	global $menu;
+	global $admin_menu;
 	global $_wp_real_parent_file;
 	global $_wp_submenu_nopriv;
 	global $_registered_pages;
 	global $_parent_pages;
 
 	$menu_slug = plugin_basename( $menu_slug );
-	$parent_slug = plugin_basename( $parent_slug);
+	$parent_slug = plugin_basename( $parent_slug );
 
-	if ( isset( $_wp_real_parent_file[$parent_slug] ) )
+	if ( isset( $_wp_real_parent_file[$parent_slug] ) ) {
 		$parent_slug = $_wp_real_parent_file[$parent_slug];
+	}
 
 	if ( !current_user_can( $capability ) ) {
 		$_wp_submenu_nopriv[$parent_slug][$menu_slug] = true;
 		return false;
 	}
 
+	$parent_menu = $admin_menu->get( $parent_slug, 'slug' );
+	if ( !$parent_menu ) {
+		return false;
+	}
+
 	// If the parent doesn't already have a submenu, add a link to the parent
 	// as the first item in the submenu. If the submenu file is the same as the
 	// parent file someone is trying to link back to the parent manually. In
 	// this case, don't automatically add a link back to avoid duplication.
-	if (!isset( $submenu[$parent_slug] ) && $menu_slug != $parent_slug ) {
-		foreach ( (array)$menu as $parent_menu ) {
-			if ( $parent_menu[2] == $parent_slug && current_user_can( $parent_menu[1] ) )
-				$submenu[$parent_slug][] = $parent_menu;
-		}
+	if ( !$parent_menu->has_children() && $menu_slug != $parent_slug ) {
+		$admin_menu->add_first_submenu( $parent_menu->id, $parent_menu->title );
 	}
 
-	$submenu[$parent_slug][] = array ( $menu_title, $capability, $menu_slug, $page_title );
+	$parent_menu->append( array(
+		'title' => $menu_title,
+		'cap' => $capability,
+		'slug' => $menu_slug,
+		'page_title' => $page_title // TODO: why is this stored here?
+	) );
 
 	$hookname = get_plugin_page_hookname( $menu_slug, $parent_slug);
+
 	if (!empty ( $function ) && !empty ( $hookname ))
 		add_action( $hookname, $function );
 
 	$_registered_pages[$hookname] = true;
+
 	// backwards-compatibility for plugins using add_management page. See wp-admin/admin.php for redirect from edit.php to tools.php
-	if ( 'tools.php' == $parent_slug )
-		$_registered_pages[get_plugin_page_hookname( $menu_slug, 'edit.php')] = true;
+	if ( 'tools.php' == $parent_slug ) {
+		$compat_hookname = get_plugin_page_hookname( $menu_slug, 'edit.php' );
+
+		$_registered_pages[ $compat_hookname ] = true;
+	}
 
 	// No parent as top level
 	$_parent_pages[$menu_slug] = $parent_slug;
@@ -1277,20 +1292,21 @@ function add_comments_page( $page_title, $menu_title, $capability, $menu_slug, $
  *
  * @since 3.1.0
  *
- * @param string $menu_slug The slug of the menu
+ * @param string $menu_url The url of the menu
  * @return array|bool The removed menu on success, False if not found
  */
-function remove_menu_page( $menu_slug ) {
-	global $menu;
+function remove_menu_page( $menu_url ) {
+	global $admin_menu;
 
-	foreach ( $menu as $i => $item ) {
-		if ( $menu_slug == $item[2] ) {
-			unset( $menu[$i] );
-			return $item;
-		}
-	}
+	$item = $admin_menu->get( $menu_url, 'slug' );
+	if ( !$item )
+		return false;
 
-	return false;
+	$admin_menu->remove( $item->id );
+
+	// TODO: return legacy numeric array
+
+	return true;
 }
 
 /**
@@ -1298,24 +1314,26 @@ function remove_menu_page( $menu_slug ) {
  *
  * @since 3.1.0
  *
- * @param string $menu_slug The slug for the parent menu
- * @param string $submenu_slug The slug of the submenu
+ * @param string $menu_url The url for the parent menu
+ * @param string $submenu_url The url of the submenu
  * @return array|bool The removed submenu on success, False if not found
  */
-function remove_submenu_page( $menu_slug, $submenu_slug ) {
-	global $submenu;
+function remove_submenu_page( $menu_url, $submenu_url ) {
+	global $admin_menu;
 
-	if ( !isset( $submenu[$menu_slug] ) )
+	$item = $admin_menu->get( $menu_url, 'slug' );
+	if ( !$item )
 		return false;
 
-	foreach ( $submenu[$menu_slug] as $i => $item ) {
-		if ( $submenu_slug == $item[2] ) {
-			unset( $submenu[$menu_slug][$i] );
-			return $item;
-		}
-	}
+	$submenu = $item->get( $submenu_url, 'slug' );
+	if ( !$submenu )
+		return false;
 
-	return false;
+	$item->remove( $submenu->id );
+
+	// TODO: return legacy numeric array
+
+	return true;
 }
 
 /**
@@ -1357,8 +1375,7 @@ function menu_page_url($menu_slug, $echo = true) {
 
 function get_admin_page_parent( $parent = '' ) {
 	global $parent_file;
-	global $menu;
-	global $submenu;
+	global $admin_menu;
 	global $pagenow;
 	global $typenow;
 	global $plugin_page;
@@ -1372,24 +1389,15 @@ function get_admin_page_parent( $parent = '' ) {
 		return $parent;
 	}
 
-	/*
-	if ( !empty ( $parent_file ) ) {
-		if ( isset( $_wp_real_parent_file[$parent_file] ) )
-			$parent_file = $_wp_real_parent_file[$parent_file];
-
-		return $parent_file;
-	}
-	*/
-
 	if ( $pagenow == 'admin.php' && isset( $plugin_page ) ) {
-		foreach ( (array)$menu as $parent_menu ) {
-			if ( $parent_menu[2] == $plugin_page ) {
-				$parent_file = $plugin_page;
-				if ( isset( $_wp_real_parent_file[$parent_file] ) )
-					$parent_file = $_wp_real_parent_file[$parent_file];
-				return $parent_file;
-			}
+		$current_item = $admin_menu->get( $plugin_page, 'slug' );
+		if ( $current_item ) {
+			$parent_file = $plugin_page;
+			if ( isset( $_wp_real_parent_file[$parent_file] ) )
+				$parent_file = $_wp_real_parent_file[$parent_file];
+			return $parent_file;
 		}
+
 		if ( isset( $_wp_menu_nopriv[$plugin_page] ) ) {
 			$parent_file = $plugin_page;
 			if ( isset( $_wp_real_parent_file[$parent_file] ) )
@@ -1405,21 +1413,29 @@ function get_admin_page_parent( $parent = '' ) {
 		return $parent_file;
 	}
 
-	foreach (array_keys( (array)$submenu ) as $parent) {
-		foreach ( $submenu[$parent] as $submenu_array ) {
-			if ( isset( $_wp_real_parent_file[$parent] ) )
-				$parent = $_wp_real_parent_file[$parent];
-			if ( !empty($typenow) && ($submenu_array[2] == "$pagenow?post_type=$typenow") ) {
+	foreach ( $admin_menu->get_children() as $menu_item ) {
+		if ( !isset( $menu_item->slug ) )
+			continue;
+
+		$parent = $menu_item->slug;
+		if ( isset( $_wp_real_parent_file[$parent] ) )
+			$parent = $_wp_real_parent_file[$parent];
+
+		foreach ( $menu_item->get_children() as $submenu ) {
+			if ( !empty($typenow) && $submenu->slug == "$pagenow?post_type=$typenow" ) {
 				$parent_file = $parent;
 				return $parent;
-			} elseif ( $submenu_array[2] == $pagenow && empty($typenow) && ( empty($parent_file) || false === strpos($parent_file, '?') ) ) {
+			}
+
+			if ( $submenu->slug == $pagenow && empty($typenow) && ( empty($parent_file) || false === strpos($parent_file, '?') ) ) {
 				$parent_file = $parent;
 				return $parent;
-			} else
-				if ( isset( $plugin_page ) && ($plugin_page == $submenu_array[2] ) ) {
-					$parent_file = $parent;
-					return $parent;
-				}
+			}
+
+			if ( isset( $plugin_page ) && $plugin_page == $submenu->slug ) {
+				$parent_file = $parent;
+				return $parent;
+			}
 		}
 	}
 
@@ -1430,8 +1446,7 @@ function get_admin_page_parent( $parent = '' ) {
 
 function get_admin_page_title() {
 	global $title;
-	global $menu;
-	global $submenu;
+	global $admin_menu;
 	global $pagenow;
 	global $plugin_page;
 	global $typenow;
@@ -1441,64 +1456,59 @@ function get_admin_page_title() {
 
 	$hook = get_plugin_page_hook( $plugin_page, $pagenow );
 
-	$parent = $parent1 = get_admin_page_parent();
-
-	if ( empty ( $parent) ) {
-		foreach ( (array)$menu as $menu_array ) {
-			if ( isset( $menu_array[3] ) ) {
-				if ( $menu_array[2] == $pagenow ) {
-					$title = $menu_array[3];
-					return $menu_array[3];
-				} else
-					if ( isset( $plugin_page ) && ($plugin_page == $menu_array[2] ) && ($hook == $menu_array[3] ) ) {
-						$title = $menu_array[3];
-						return $menu_array[3];
-					}
-			} else {
-				$title = $menu_array[0];
-				return $title;
+	$parent = get_admin_page_parent();
+
+	if ( empty($parent) ) {
+		if ( isset( $menu_item->page_title ) ) {
+			if ( $menu_item->slug == $pagenow ) {
+				$title = $menu_item->page_title;
+				return $menu_item->page_title;
+			} elseif ( isset( $plugin_page ) && $plugin_page == $menu_item->slug && $hook == $menu_item->page_title ) {
+				$title = $menu_item->page_title;
+				return $menu_item->page_title;
 			}
+		} else {
+			$title = $menu_item->title;
+			return $title;
 		}
-	} else {
-		foreach ( array_keys( $submenu ) as $parent ) {
-			foreach ( $submenu[$parent] as $submenu_array ) {
-				if ( isset( $plugin_page ) &&
-					( $plugin_page == $submenu_array[2] ) &&
-					(
-						( $parent == $pagenow ) ||
-						( $parent == $plugin_page ) ||
-						( $plugin_page == $hook ) ||
-						( $pagenow == 'admin.php' && $parent1 != $submenu_array[2] ) ||
-						( !empty($typenow) && $parent == $pagenow . '?post_type=' . $typenow)
-					)
-					) {
-						$title = $submenu_array[3];
-						return $submenu_array[3];
-					}
+	}
 
-				if ( $submenu_array[2] != $pagenow || isset( $_GET['page'] ) ) // not the current page
-					continue;
+	$menu_item = $admin_menu->get( $parent, 'slug' );
+
+	foreach ( $menu_item->get_children() as $submenu ) {
+		if ( isset( $plugin_page ) &&
+			$plugin_page == $submenu->slug && (
+				( $parent == $pagenow ) ||
+				( $parent == $plugin_page ) ||
+				( $plugin_page == $hook ) ||
+				( $pagenow == 'admin.php' && $parent != $submenu->slug ) ||
+				( !empty($typenow) && $parent == $pagenow . '?post_type=' . $typenow)
+			)
+		) {
+			$title = $submenu->page_title;
+			return $submenu->page_title;
+		}
 
-				if ( isset( $submenu_array[3] ) ) {
-					$title = $submenu_array[3];
-					return $submenu_array[3];
-				} else {
-					$title = $submenu_array[0];
-					return $title;
-				}
-			}
+		if ( $submenu->slug != $pagenow || isset( $_GET['page'] ) ) // not the current page
+			continue;
+
+		if ( isset( $submenu->page_title ) ) {
+			$title = $submenu->page_title;
+			return $submenu->page_title;
+		} else {
+			$title = $submenu->title;
+			return $title;
 		}
-		if ( empty ( $title ) ) {
-			foreach ( $menu as $menu_array ) {
-				if ( isset( $plugin_page ) &&
-					( $plugin_page == $menu_array[2] ) &&
-					( $pagenow == 'admin.php' ) &&
-					( $parent1 == $menu_array[2] ) )
-					{
-						$title = $menu_array[3];
-						return $menu_array[3];
-					}
-			}
+	}
+
+	if ( empty ( $title ) ) {
+		if ( isset( $plugin_page ) &&
+			( $plugin_page == $menu_item->slug ) &&
+			( $pagenow == 'admin.php' ) &&
+			( $parent == $menu_item->slug ) )
+		{
+			$title = $menu_item->page_title;
+			return $menu_item->page_title;
 		}
 	}
 
@@ -1536,8 +1546,7 @@ function get_plugin_page_hookname( $plugin_page, $parent_page ) {
 
 function user_can_access_admin_page() {
 	global $pagenow;
-	global $menu;
-	global $submenu;
+	global $admin_menu;
 	global $_wp_menu_nopriv;
 	global $_wp_submenu_nopriv;
 	global $plugin_page;
@@ -1579,32 +1588,19 @@ function user_can_access_admin_page() {
 	if ( isset( $plugin_page ) && ( $plugin_page == $parent ) && isset( $_wp_menu_nopriv[$plugin_page] ) )
 		return false;
 
-	if ( isset( $submenu[$parent] ) ) {
-		foreach ( $submenu[$parent] as $submenu_array ) {
-			if ( isset( $plugin_page ) && ( $submenu_array[2] == $plugin_page ) ) {
-				if ( current_user_can( $submenu_array[1] ))
-					return true;
-				else
-					return false;
-			} else if ( $submenu_array[2] == $pagenow ) {
-				if ( current_user_can( $submenu_array[1] ))
-					return true;
-				else
-					return false;
-			}
-		}
-	}
+	$curret_item = $admin_menu->get( $parent, 'slug' );
+	if ( !$curret_item )
+		return true;
 
-	foreach ( $menu as $menu_array ) {
-		if ( $menu_array[2] == $parent) {
-			if ( current_user_can( $menu_array[1] ))
-				return true;
-			else
-				return false;
+	foreach ( $curret_item->get_children() as $submenu ) {
+		if ( isset( $plugin_page ) && $submenu->slug == $plugin_page ) {
+			return current_user_can( $submenu->cap );
+		} else if ( $submenu->slug == $pagenow ) {
+			return current_user_can( $submenu->cap );
 		}
 	}
 
-	return true;
+	return current_user_can( $curret_item->cap );
 }
 
 /* Whitelist functions */
diff --git wp-admin/menu-header.php wp-admin/menu-header.php
index ab51cbc..e77e767 100644
--- wp-admin/menu-header.php
+++ wp-admin/menu-header.php
@@ -18,11 +18,140 @@ $self = preg_replace('|^.*/wp-admin/|i', '', $self);
 $self = preg_replace('|^.*/plugins/|i', '', $self);
 $self = preg_replace('|^.*/mu-plugins/|i', '', $self);
 
-global $menu, $submenu, $parent_file; //For when admin-header is included from within a function.
+global $admin_menu, $parent_file; //For when admin-header is included from within a function.
 $parent_file = apply_filters("parent_file", $parent_file); // For plugins to move submenu tabs around.
 
 get_admin_page_parent();
 
+function _admin_menu_get_menu_file( $item ) {
+	$menu_file = $item->slug;
+
+	if ( false !== ( $pos = strpos( $menu_file, '?' ) ) )
+		$menu_file = substr($menu_file, 0, $pos);
+
+	return $menu_file;
+}
+
+function _admin_menu_get_url( $menu_hook, $item, &$admin_is_parent ) {
+	$menu_file = _admin_menu_get_menu_file( $item );
+
+	if (
+		!empty( $menu_hook ) ||
+		( 'index.php' != $item->slug && file_exists( WP_PLUGIN_DIR . "/$menu_file" ) )
+	) {
+		$admin_is_parent = true;
+		$url = 'admin.php?page=' . $item->slug;
+	} else {
+		$url = $item->slug;
+	}
+
+	return $url;
+}
+
+function _admin_menu_is_current( $item ) {
+	global $self, $typenow, $parent_file;
+
+	if ( $parent_file && $item->slug == $parent_file )
+		return true;
+
+	if ( empty($typenow) && $self == $item->slug )
+		return true;
+
+	return false;
+}
+
+function _admin_submenu_is_current( $sub_item, $item ) {
+	global $self, $typenow, $submenu_file, $plugin_page;
+
+	if ( isset( $submenu_file ) && $submenu_file == $sub_item->slug )
+		return true;
+
+	if ( !isset( $plugin_page ) && $self == $sub_item->slug )
+		return true;
+
+	// Handle current for post_type=post|page|foo pages, which won't match $self.
+	$self_type = ! empty( $typenow ) ? $self . '?post_type=' . $typenow : 'nothing';
+
+	// If plugin_page is set the parent must either match the current page or not physically exist.
+	// This allows plugin pages with the same hook to exist under different parents.
+	if (
+		isset( $plugin_page ) &&
+		$plugin_page == $sub_item->slug &&
+		(
+			$item->slug == $self_type ||
+			$item->slug == $self ||
+			!file_exists( $menu_file )
+		)
+	)
+		return true;
+
+	return false;
+}
+
+function _admin_submenu_get_url( $sub_item, $item, $admin_is_parent ) {
+	$menu_file = _admin_menu_get_menu_file( $item );
+
+	$menu_hook = get_plugin_page_hook( $sub_item->slug, $item->slug );
+
+	$sub_file = _admin_menu_get_menu_file( $sub_item );
+
+	if ( !empty( $menu_hook ) || ( 'index.php' != $sub_item->slug && file_exists( WP_PLUGIN_DIR . "/$sub_file" ) ) ) {
+		if (
+			( !$admin_is_parent && file_exists( WP_PLUGIN_DIR . "/$menu_file" ) && !is_dir( WP_PLUGIN_DIR . "/{$item->slug}" ) )
+			|| file_exists( $menu_file )
+		) {
+			$base = $item->slug;
+		} else {
+			$base = 'admin.php';
+		}
+
+		return add_query_arg( 'page', $sub_item->slug, $base );
+	}
+
+	return $sub_item->slug;
+}
+
+function add_cssclass($add, $class) {
+	$class = empty($class) ? $add : $class .= ' ' . $add;
+	return $class;
+}
+
+function _add_admin_menu_classes( $admin_menu ) {
+	$items = array_values( $admin_menu->get_children() );
+
+	// Remove the last menu item if it is a separator.
+	$last = end( $items );
+	if ( 'wp-menu-separator' == $last->class ) {
+		$admin_menu->remove( $last->id );
+		array_pop( $items );
+	}
+
+	$first = false;
+
+	foreach ( $items as $i => $menu_item ) {
+		if ( 'dashboard' == $menu_item->id ) { // dashboard is always shown/single
+			$menu_item->class = add_cssclass( 'menu-top-first', $menu_item->class );
+			continue;
+		}
+
+		if ( 'wp-menu-separator' == $menu_item->class ) {
+			$first = true;
+			$previous = $items[$i-1];
+			$previous->class = add_cssclass( 'menu-top-last', $previous->class );
+			continue;
+		}
+
+		if ( $first ) {
+			$menu_item->class = add_cssclass( 'menu-top-first', $menu_item->class );
+			$first = false;
+		}
+	}
+
+	$last = end( $items );
+
+	$last->class = add_cssclass( 'menu-top-last', $last->class );
+}
+
 /**
  * Display menu.
  *
@@ -33,132 +162,111 @@ get_admin_page_parent();
  * @param array $submenu
  * @param bool $submenu_as_parent
  */
-function _wp_menu_output( $menu, $submenu, $submenu_as_parent = true ) {
-	global $self, $parent_file, $submenu_file, $plugin_page, $pagenow, $typenow;
-
+function _wp_menu_output( $menu, $submenu_as_parent = true ) {
 	$first = true;
-	// 0 = name, 1 = capability, 2 = file, 3 = class, 4 = id, 5 = icon src
-	foreach ( $menu as $key => $item ) {
+	foreach ( $menu->get_children() as $item ) {
+
+		if ( 'wp-menu-separator' == $item->class ) {
+			echo "\n\t<li class='wp-menu-separator' id='menu-$item->id'>";
+			echo '<div class="separator"></div>';
+			echo "</li>";
+			continue;
+		}
+
 		$admin_is_parent = false;
-		$class = array();
+
 		$aria_attributes = '';
 
+		$class = array();
+
 		if ( $first ) {
 			$class[] = 'wp-first-item';
 			$first = false;
 		}
 
-		$submenu_items = false;
-		if ( ! empty( $submenu[$item[2]] ) ) {
+		$submenu_items = $item->get_children();
+
+		if ( ! empty( $submenu_items ) ) {
 			$class[] = 'wp-has-submenu';
-			$submenu_items = $submenu[$item[2]];
 		}
 
-		if ( ( $parent_file && $item[2] == $parent_file ) || ( empty($typenow) && $self == $item[2] ) ) {
-			$class[] = ! empty( $submenu_items ) ? 'wp-has-current-submenu wp-menu-open' : 'current';
+		if ( _admin_menu_is_current( $item ) ) {
+			if ( ! empty( $submenu_items ) )
+				$class[] = 'wp-has-current-submenu wp-menu-open';
+			else
+				$class[] = 'current';
 		} else {
 			$class[] = 'wp-not-current-submenu';
 			if ( ! empty( $submenu_items ) )
 				$aria_attributes .= 'aria-haspopup="true"';
 		}
 
-		if ( ! empty( $item[4] ) )
-			$class[] = $item[4];
+		if ( ! empty( $item->class ) )
+			$class[] = $item->class;
+
+		$class[] = 'menu-top';
 
 		$class = $class ? ' class="' . join( ' ', $class ) . '"' : '';
-		$id = ! empty( $item[5] ) ? ' id="' . preg_replace( '|[^a-zA-Z0-9_:.]|', '-', $item[5] ) . '"' : '';
+
+		$id = ! empty( $item->id ) ? ' id="menu-' . preg_replace( '|[^a-zA-Z0-9_:.]|', '-', $item->id ) . '"' : '';
+
 		$img = '';
-		if ( ! empty( $item[6] ) )
-			$img = ( 'div' === $item[6] ) ? '<br />' : '<img src="' . $item[6] . '" alt="" />';
+		if ( ! empty( $item->icon ) )
+			$img = ( 'div' === $item->icon ) ? '<br />' : '<img src="' . $item->icon . '" alt="" />';
+
 		$arrow = '<div class="wp-menu-arrow"><div></div></div>';
 
-		$title = wptexturize( $item[0] );
-		$aria_label = esc_attr( strip_tags( $item[0] ) ); // strip the comment/plugins/updates bubbles spans but keep the pending number if any
+		$title = wptexturize( $item->title );
+
+		$aria_label = esc_attr( strip_tags( $item->title ) ); // strip the comment/plugins/updates bubbles spans but keep the pending number if any
 
 		echo "\n\t<li$class$id>";
 
-		if ( false !== strpos( $class, 'wp-menu-separator' ) ) {
-			echo '<div class="separator"></div>';
-		} elseif ( $submenu_as_parent && ! empty( $submenu_items ) ) {
-			$submenu_items = array_values( $submenu_items );  // Re-index.
-			$menu_hook = get_plugin_page_hook( $submenu_items[0][2], $item[2] );
-			$menu_file = $submenu_items[0][2];
-			if ( false !== ( $pos = strpos( $menu_file, '?' ) ) )
-				$menu_file = substr( $menu_file, 0, $pos );
-			if ( ! empty( $menu_hook ) || ( ('index.php' != $submenu_items[0][2]) && file_exists( WP_PLUGIN_DIR . "/$menu_file" ) ) ) {
-				$admin_is_parent = true;
-				echo "<div class='wp-menu-image'><a href='admin.php?page={$submenu_items[0][2]}' tabindex='-1' aria-label='$aria_label'>$img</a></div>$arrow<a href='admin.php?page={$submenu_items[0][2]}'$class $aria_attributes>$title</a>";
-			} else {
-				echo "\n\t<div class='wp-menu-image'><a href='{$submenu_items[0][2]}' tabindex='-1' aria-label='$aria_label'>$img</a></div>$arrow<a href='{$submenu_items[0][2]}'$class $aria_attributes>$title</a>";
-			}
-		} elseif ( ! empty( $item[2] ) && current_user_can( $item[1] ) ) {
-			$menu_hook = get_plugin_page_hook( $item[2], 'admin.php' );
-			$menu_file = $item[2];
-			if ( false !== ( $pos = strpos( $menu_file, '?' ) ) )
-				$menu_file = substr( $menu_file, 0, $pos );
-			if ( ! empty( $menu_hook ) || ( ('index.php' != $item[2]) && file_exists( WP_PLUGIN_DIR . "/$menu_file" ) ) ) {
-				$admin_is_parent = true;
-				echo "\n\t<div class='wp-menu-image'><a href='admin.php?page={$item[2]}' tabindex='-1' aria-label='$aria_label'>$img</a></div>$arrow<a href='admin.php?page={$item[2]}'$class $aria_attributes>{$item[0]}</a>";
-			} else {
-				echo "\n\t<div class='wp-menu-image'><a href='{$item[2]}' tabindex='-1' aria-label='$aria_label'>$img</a></div>$arrow<a href='{$item[2]}'$class $aria_attributes>{$item[0]}</a>";
-			}
+		$url = false;
+		if ( $submenu_as_parent && ! empty( $submenu_items ) ) {
+			$first_submenu = reset( $submenu_items );
+
+			$menu_hook = get_plugin_page_hook( $first_submenu->slug, $item->slug );
+			$url = _admin_menu_get_url( $menu_hook, $first_submenu, $admin_is_parent );
+		}
+		elseif ( ! empty( $item->slug ) && current_user_can( $item->cap ) ) {
+			$menu_hook = get_plugin_page_hook( $item->slug, 'admin.php' );
+			$url = _admin_menu_get_url( $menu_hook, $item, $admin_is_parent );
+		}
+
+		if ( $url ) {
+			echo "<div class='wp-menu-image'><a href='$url' tabindex='-1' aria-label='$aria_label'>$img</a></div>";
+			echo $arrow;
+			echo "<a href='$url'$class $aria_attributes>$title</a>";
 		}
 
 		if ( ! empty( $submenu_items ) ) {
 			echo "\n\t<div class='wp-submenu'><div class='wp-submenu-wrap'>";
-			echo "<div class='wp-submenu-head'>{$item[0]}</div><ul>";
+			echo "<div class='wp-submenu-head'>{$item->title}</div><ul>";
 			$first = true;
-			foreach ( $submenu_items as $sub_key => $sub_item ) {
-				if ( ! current_user_can( $sub_item[1] ) )
+			foreach ( $submenu_items as $sub_item ) {
+				if ( ! current_user_can( $sub_item->cap ) )
 					continue;
 
 				$class = array();
+
 				if ( $first ) {
 					$class[] = 'wp-first-item';
 					$first = false;
 				}
 
-				$menu_file = $item[2];
-
-				if ( false !== ( $pos = strpos( $menu_file, '?' ) ) )
-					$menu_file = substr( $menu_file, 0, $pos );
-
-				// Handle current for post_type=post|page|foo pages, which won't match $self.
-				$self_type = ! empty( $typenow ) ? $self . '?post_type=' . $typenow : 'nothing';
-
-				if ( isset( $submenu_file ) ) {
-					if ( $submenu_file == $sub_item[2] )
-						$class[] = 'current';
-				// If plugin_page is set the parent must either match the current page or not physically exist.
-				// This allows plugin pages with the same hook to exist under different parents.
-				} else if (
-					( ! isset( $plugin_page ) && $self == $sub_item[2] ) ||
-					( isset( $plugin_page ) && $plugin_page == $sub_item[2] && ( $item[2] == $self_type || $item[2] == $self || file_exists($menu_file) === false ) )
-				) {
+				if ( _admin_submenu_is_current( $sub_item, $item ) ) {
 					$class[] = 'current';
 				}
 
 				$class = $class ? ' class="' . join( ' ', $class ) . '"' : '';
 
-				$menu_hook = get_plugin_page_hook($sub_item[2], $item[2]);
-				$sub_file = $sub_item[2];
-				if ( false !== ( $pos = strpos( $sub_file, '?' ) ) )
-					$sub_file = substr($sub_file, 0, $pos);
+				$title = wptexturize( $sub_item->title );
 
-				$title = wptexturize($sub_item[0]);
+				$sub_item_url = _admin_submenu_get_url( $sub_item, $item, $admin_is_parent );
+				$sub_item_url = esc_url( $sub_item_url );
 
-				if ( ! empty( $menu_hook ) || ( ('index.php' != $sub_item[2]) && file_exists( WP_PLUGIN_DIR . "/$sub_file" ) ) ) {
-					// If admin.php is the current page or if the parent exists as a file in the plugins or admin dir
-					if ( (!$admin_is_parent && file_exists(WP_PLUGIN_DIR . "/$menu_file") && !is_dir(WP_PLUGIN_DIR . "/{$item[2]}")) || file_exists($menu_file) )
-						$sub_item_url = add_query_arg( array('page' => $sub_item[2]), $item[2] );
-					else
-						$sub_item_url = add_query_arg( array('page' => $sub_item[2]), 'admin.php' );
-
-					$sub_item_url = esc_url( $sub_item_url );
-					echo "<li$class><a href='$sub_item_url'$class>$title</a></li>";
-				} else {
-					echo "<li$class><a href='{$sub_item[2]}'$class>$title</a></li>";
-				}
+				echo "<li$class><a href='{$sub_item_url}'$class>$title</a></li>";
 			}
 			echo "</ul></div></div>";
 		}
@@ -169,19 +277,17 @@ function _wp_menu_output( $menu, $submenu, $submenu_as_parent = true ) {
 	echo '<span>' . esc_html__( 'Collapse menu' ) . '</span>';
 	echo '</li>';
 }
-
 ?>
 
 <div id="adminmenuback"></div>
 <div id="adminmenuwrap">
-<div id="adminmenushadow"></div>
-<ul id="adminmenu" role="navigation">
+	<div id="adminmenushadow"></div>
 
+	<ul id="adminmenu" role="navigation">
 <?php
-
-_wp_menu_output( $menu, $submenu );
-do_action( 'adminmenu' );
-
+	_add_admin_menu_classes( $admin_menu );
+	_wp_menu_output( $admin_menu );
+	do_action( 'adminmenu' );
 ?>
-</ul>
+	</ul>
 </div>
diff --git wp-admin/menu.php wp-admin/menu.php
index bf914e5..4a43239 100644
--- wp-admin/menu.php
+++ wp-admin/menu.php
@@ -6,212 +6,326 @@
  * @subpackage Administration
  */
 
-/**
- * Constructs the admin menu bar.
- *
- * The elements in the array are :
- *     0: Menu item name
- *     1: Minimum level or capability required.
- *     2: The URL of the item's file
- *     3: Class
- *     4: ID
- *     5: Icon for top level menu
- *
- * @global array $menu
- * @name $menu
- * @var array
- */
+$admin_menu = new WP_Admin_Menu;
 
-$menu[2] = array( __('Dashboard'), 'read', 'index.php', '', 'menu-top menu-top-first menu-icon-dashboard', 'menu-dashboard', 'div' );
+$admin_menu->append( array(
+	'title' => __( 'Dashboard' ),
+	'cap' => 'read',
+	'id' => 'dashboard',
+	'slug' => 'index.php',
+) );
 
-$submenu[ 'index.php' ][0] = array( __('Home'), 'read', 'index.php' );
+	$admin_menu->add_first_submenu( 'dashboard', __( 'Home' ) );
 
 if ( is_multisite() ) {
-	$submenu[ 'index.php' ][5] = array( __('My Sites'), 'read', 'my-sites.php' );
+	$admin_menu->add_submenu( 'dashboard', array(
+		'title' => __( 'My Sites' ),
+		'cap' => 'read',
+		'slug' => 'my-sites.php',
+	) );
 }
 
 if ( ! is_multisite() || is_super_admin() )
 	$update_data = wp_get_update_data();
 
 if ( ! is_multisite() ) {
-	if ( current_user_can( 'update_core' ) )
-		$cap = 'update_core';
-	elseif ( current_user_can( 'update_plugins' ) )
-		$cap = 'update_plugins';
-	else
-		$cap = 'update_themes';
-	$submenu[ 'index.php' ][10] = array( sprintf( __('Updates %s'), "<span class='update-plugins count-{$update_data['counts']['total']}' title='{$update_data['title']}'><span class='update-count'>" . number_format_i18n($update_data['counts']['total']) . "</span></span>" ), $cap, 'update-core.php');
-	unset( $cap );
+	$admin_menu->add_submenu( 'dashboard', array(
+		'title' => _admin_menu_update_count( $update_data ),
+		'cap' => array( 'update_core', 'update_plugins', 'update_themes' ),
+		'slug' => 'update-core.php',
+	) );
 }
 
-$menu[4] = array( '', 'read', 'separator1', '', 'wp-menu-separator' );
-
-$menu[5] = array( __('Posts'), 'edit_posts', 'edit.php', '', 'open-if-no-js menu-top menu-icon-post', 'menu-posts', 'div' );
-	$submenu['edit.php'][5]  = array( __('All Posts'), 'edit_posts', 'edit.php' );
-	/* translators: add new post */
-	$submenu['edit.php'][10]  = array( _x('Add New', 'post'), 'edit_posts', 'post-new.php' );
-
-	$i = 15;
-	foreach ( get_taxonomies( array(), 'objects' ) as $tax ) {
-		if ( ! $tax->show_ui || ! in_array('post', (array) $tax->object_type, true) )
-			continue;
-
-		$submenu['edit.php'][$i++] = array( esc_attr( $tax->labels->menu_name ), $tax->cap->manage_terms, 'edit-tags.php?taxonomy=' . $tax->name );
-	}
-	unset($tax);
-
-$menu[10] = array( __('Media'), 'upload_files', 'upload.php', '', 'menu-top menu-icon-media', 'menu-media', 'div' );
-	$submenu['upload.php'][5] = array( __('Library'), 'upload_files', 'upload.php');
-	/* translators: add new file */
-	$submenu['upload.php'][10] = array( _x('Add New', 'file'), 'upload_files', 'media-new.php');
-
-$menu[15] = array( __('Links'), 'manage_links', 'link-manager.php', '', 'menu-top menu-icon-links', 'menu-links', 'div' );
-	$submenu['link-manager.php'][5] = array( _x('All Links', 'admin menu'), 'manage_links', 'link-manager.php' );
-	/* translators: add new links */
-	$submenu['link-manager.php'][10] = array( _x('Add New', 'link'), 'manage_links', 'link-add.php' );
-	$submenu['link-manager.php'][15] = array( __('Link Categories'), 'manage_categories', 'edit-tags.php?taxonomy=link_category' );
-
-$menu[20] = array( __('Pages'), 'edit_pages', 'edit.php?post_type=page', '', 'menu-top menu-icon-page', 'menu-pages', 'div' );
-	$submenu['edit.php?post_type=page'][5] = array( __('All Pages'), 'edit_pages', 'edit.php?post_type=page' );
-	/* translators: add new page */
-	$submenu['edit.php?post_type=page'][10] = array( _x('Add New', 'page'), 'edit_pages', 'post-new.php?post_type=page' );
-	$i = 15;
-	foreach ( get_taxonomies( array(), 'objects' ) as $tax ) {
-		if ( ! $tax->show_ui || ! in_array('page', (array) $tax->object_type, true) )
-			continue;
-
-		$submenu['edit.php?post_type=page'][$i++] = array( esc_attr( $tax->labels->menu_name ), $tax->cap->manage_terms, 'edit-tags.php?taxonomy=' . $tax->name . '&amp;post_type=page' );
-	}
-	unset($tax);
-
-$awaiting_mod = wp_count_comments();
-$awaiting_mod = $awaiting_mod->moderated;
-$menu[25] = array( sprintf( __('Comments %s'), "<span class='awaiting-mod count-$awaiting_mod'><span class='pending-count'>" . number_format_i18n($awaiting_mod) . "</span></span>" ), 'edit_posts', 'edit-comments.php', '', 'menu-top menu-icon-comments', 'menu-comments', 'div' );
-unset($awaiting_mod);
-
-$submenu[ 'edit-comments.php' ][0] = array( __('All Comments'), 'edit_posts', 'edit-comments.php' );
-
-$_wp_last_object_menu = 25; // The index of the last top-level menu in the object menu group
-
-foreach ( (array) get_post_types( array('show_ui' => true, '_builtin' => false, 'show_in_menu' => true ) ) as $ptype ) {
-	$ptype_obj = get_post_type_object( $ptype );
-	// Check if it should be a submenu.
-	if ( $ptype_obj->show_in_menu !== true )
-		continue;
-	$ptype_menu_position = is_int( $ptype_obj->menu_position ) ? $ptype_obj->menu_position : ++$_wp_last_object_menu; // If we're to use $_wp_last_object_menu, increment it first.
-	$ptype_for_id = sanitize_html_class( $ptype );
-	if ( is_string( $ptype_obj->menu_icon ) ) {
-		$menu_icon   = esc_url( $ptype_obj->menu_icon );
-		$ptype_class = $ptype_for_id;
-	} else {
-		$menu_icon   = 'div';
-		$ptype_class = 'post';
+$admin_menu->append( array(
+	'id' => 'separator1',
+	'class' => 'wp-menu-separator',
+) );
+
+$admin_menu->append( array(
+	'title' => __( 'Posts' ),
+	'cap' => 'edit_posts',
+	'class' => 'open-if-no-js menu-icon-post',
+	'id' => 'posts',
+	'slug' => 'edit.php',
+) );
+
+	$admin_menu->add_first_submenu( 'posts', __( 'All Posts' ) );
+
+	$admin_menu->add_submenu( 'posts', array(
+		/* translators: add new post */
+		'title' => _x( 'Add New', 'post' ),
+		'cap' => 'edit_posts',
+		'slug' => 'post-new.php',
+	) );
+
+	$admin_menu->_add_tax_submenus( 'posts', 'post' );
+
+$admin_menu->append( array(
+	'title' => __( 'Media' ),
+	'cap' => 'upload_files',
+	'id' => 'media',
+	'slug' => 'upload.php',
+) );
+
+	$admin_menu->add_first_submenu( 'media', __( 'Library' ) );
+
+	$admin_menu->add_submenu( 'media', array(
+		/* translators: add new file */
+		'title' => _x( 'Add New', 'file' ),
+		'cap' => 'upload_files',
+		'slug' => 'media-new.php',
+	) );
+
+$admin_menu->append( array(
+	'title' => __( 'Links' ),
+	'cap' => 'manage_links',
+	'id' => 'links',
+	'slug' => 'link-manager.php',
+) );
+
+	$admin_menu->add_first_submenu( 'links', __( 'All Links' ) );
+
+	$admin_menu->add_submenu( 'links', array(
+		/* translators: add new link */
+		'title' => _x( 'Add New', 'link' ),
+		'cap' => 'manage_links',
+		'slug' => 'link-add.php',
+	) );
+
+	$admin_menu->add_submenu( 'links', array(
+		'title' => __( 'Link Categories' ),
+		'cap' => 'manage_categories',
+		'slug' => 'edit-tags.php?taxonomy=link_category',
+	) );
+
+$admin_menu->append( array(
+	'title' => __( 'Pages' ),
+	'cap' => 'edit_pages',
+	'class' => 'menu-icon-page',
+	'id' => 'pages',
+	'slug' => 'edit.php?post_type=page',
+) );
+
+	$admin_menu->add_first_submenu( 'pages', __( 'All Pages' ) );
+
+	$admin_menu->add_submenu( 'pages', array(
+		/* translators: add new link */
+		'title' => _x( 'Add New', 'page' ),
+		'cap' => 'edit_pages',
+		'slug' => 'post-new.php?post_type=page',
+	) );
+
+	$admin_menu->_add_tax_submenus( 'pages', 'page' );
+
+$admin_menu->append( array(
+	'title' => _admin_menu_comment_count( wp_count_comments()->moderated ),
+	'cap' => 'edit_posts',
+	'id' => 'comments',
+	'slug' => 'edit-comments.php',
+) );
+
+	$admin_menu->add_first_submenu( 'comments', __( 'All Comments' ) );
+
+$admin_menu->append( array(
+	'id' => 'separator2',
+	'class' => 'wp-menu-separator',
+) );
+
+$admin_menu->append( array(
+	'title' => __('Appearance'),
+	'cap' => array( 'switch_themes', 'edit_theme_options' ),
+	'slug' => 'themes.php',
+	'id' => 'appearance',
+) );
+
+	$admin_menu->add_first_submenu( 'appearance', __( 'Themes') );
+
+	if ( current_theme_supports( 'menus' ) || current_theme_supports( 'widgets' ) ) {
+		$admin_menu->add_submenu( 'appearance', array(
+			'title' => __('Menus'),
+			'cap' => array( 'switch_themes', 'edit_theme_options' ),
+			'slug' => 'nav-menus.php',
+		) );
 	}
 
-	// if $ptype_menu_position is already populated or will be populated by a hard-coded value below, increment the position.
-	$core_menu_positions = array(59, 60, 65, 70, 75, 80, 85, 99);
-	while ( isset($menu[$ptype_menu_position]) || in_array($ptype_menu_position, $core_menu_positions) )
-		$ptype_menu_position++;
-
-	$menu[$ptype_menu_position] = array( esc_attr( $ptype_obj->labels->menu_name ), $ptype_obj->cap->edit_posts, "edit.php?post_type=$ptype", '', 'menu-top menu-icon-' . $ptype_class, 'menu-posts-' . $ptype_for_id, $menu_icon );
-	$submenu["edit.php?post_type=$ptype"][5]  = array( $ptype_obj->labels->all_items, $ptype_obj->cap->edit_posts,  "edit.php?post_type=$ptype");
-	$submenu["edit.php?post_type=$ptype"][10]  = array( $ptype_obj->labels->add_new, $ptype_obj->cap->edit_posts, "post-new.php?post_type=$ptype" );
-
-	$i = 15;
-	foreach ( get_taxonomies( array(), 'objects' ) as $tax ) {
-		if ( ! $tax->show_ui || ! in_array($ptype, (array) $tax->object_type, true) )
-			continue;
-
-		$submenu["edit.php?post_type=$ptype"][$i++] = array( esc_attr( $tax->labels->menu_name ), $tax->cap->manage_terms, "edit-tags.php?taxonomy=$tax->name&amp;post_type=$ptype" );
+	if ( ! is_multisite() ) {
+		$admin_menu->add_submenu( 'appearance', array(
+			'title' => _x('Editor', 'theme editor'),
+			'cap' => 'edit_themes',
+			'slug' => 'theme-editor.php',
+		) );
 	}
-}
-unset($ptype, $ptype_obj, $ptype_class, $ptype_for_id, $ptype_menu_position, $menu_icon, $i, $tax);
-
-$menu[59] = array( '', 'read', 'separator2', '', 'wp-menu-separator' );
 
-if ( current_user_can( 'switch_themes') ) {
-	$menu[60] = array( __('Appearance'), 'switch_themes', 'themes.php', '', 'menu-top menu-icon-appearance', 'menu-appearance', 'div' );
-		$submenu['themes.php'][5]  = array(__('Themes'), 'switch_themes', 'themes.php');
-		if ( current_theme_supports( 'menus' ) || current_theme_supports( 'widgets' ) )
-			$submenu['themes.php'][10] = array(__('Menus'), 'edit_theme_options', 'nav-menus.php');
-} else {
-	$menu[60] = array( __('Appearance'), 'edit_theme_options', 'themes.php', '', 'menu-top menu-icon-appearance', 'menu-appearance', 'div' );
-		$submenu['themes.php'][5]  = array(__('Themes'), 'edit_theme_options', 'themes.php');
-		if ( current_theme_supports( 'menus' ) || current_theme_supports( 'widgets' ) )
-			$submenu['themes.php'][10] = array(__('Menus'), 'edit_theme_options', 'nav-menus.php' );
-}
-
-// Add 'Editor' to the bottom of the Appearance menu.
-if ( ! is_multisite() )
-	add_action('admin_menu', '_add_themes_utility_last', 101);
-function _add_themes_utility_last() {
-	// Must use API on the admin_menu hook, direct modification is only possible on/before the _admin_menu hook
-	add_submenu_page('themes.php', _x('Editor', 'theme editor'), _x('Editor', 'theme editor'), 'edit_themes', 'theme-editor.php');
-}
-
-$count = '';
 if ( ! is_multisite() && current_user_can( 'update_plugins' ) ) {
+
 	if ( ! isset( $update_data ) )
 		$update_data = wp_get_update_data();
-	$count = "<span class='update-plugins count-{$update_data['counts']['plugins']}'><span class='plugin-count'>" . number_format_i18n($update_data['counts']['plugins']) . "</span></span>";
+
+	$plugin_title = _admin_menu_plugin_update_count( $update_data );
+} else {
+	$plugin_title = __( 'Plugins' );
 }
 
-$menu[65] = array( sprintf( __('Plugins %s'), $count ), 'activate_plugins', 'plugins.php', '', 'menu-top menu-icon-plugins', 'menu-plugins', 'div' );
+$admin_menu->append( array(
+	'title' => $plugin_title,
+	'cap' => 'activate_plugins',
+	'slug' => 'plugins.php',
+	'id' => 'plugins',
+) );
 
-$submenu['plugins.php'][5]  = array( __('Installed Plugins'), 'activate_plugins', 'plugins.php' );
+	$admin_menu->add_first_submenu( 'plugins', __( 'Installed Plugins') );
 
 	if ( ! is_multisite() ) {
-		/* translators: add new plugin */
-		$submenu['plugins.php'][10] = array( _x('Add New', 'plugin'), 'install_plugins', 'plugin-install.php' );
-		$submenu['plugins.php'][15] = array( _x('Editor', 'plugin editor'), 'edit_plugins', 'plugin-editor.php' );
+		$admin_menu->add_submenu( 'plugins', array(
+			/* translators: add new plugin */
+			'title' => _x('Add New', 'plugin'),
+			'cap' => 'install_plugins',
+			'slug' => 'plugin-install.php',
+		) );
+
+		$admin_menu->add_submenu( 'plugins', array(
+			'title' => _x('Editor', 'plugin editor'),
+			'cap' => 'edit_plugins',
+			'slug' => 'plugin-editor.php',
+		) );
 	}
 
-unset( $update_data );
+unset( $update_data, $plugin_title );
 
-if ( current_user_can('list_users') )
-	$menu[70] = array( __('Users'), 'list_users', 'users.php', '', 'menu-top menu-icon-users', 'menu-users', 'div' );
-else
-	$menu[70] = array( __('Profile'), 'read', 'profile.php', '', 'menu-top menu-icon-users', 'menu-users', 'div' );
+if ( current_user_can('list_users') ) {
+	$admin_menu->append( array(
+		'title' => __('Users'),
+		'cap' => 'list_users',
+		'slug' => 'users.php',
+		'id' => 'users',
+	) );
+} else {
+	$admin_menu->append( array(
+		'title' => __('Profile'),
+		'cap' => 'read',
+		'slug' => 'profile.php',
+		'id' => 'users',
+	) );
+}
 
 if ( current_user_can('list_users') ) {
 	$_wp_real_parent_file['profile.php'] = 'users.php'; // Back-compat for plugins adding submenus to profile.php.
-	$submenu['users.php'][5] = array(__('All Users'), 'list_users', 'users.php');
-	if ( current_user_can('create_users') )
-		$submenu['users.php'][10] = array(_x('Add New', 'user'), 'create_users', 'user-new.php');
-	else
-		$submenu['users.php'][10] = array(_x('Add New', 'user'), 'promote_users', 'user-new.php');
 
-	$submenu['users.php'][15] = array(__('Your Profile'), 'read', 'profile.php');
+	$admin_menu->add_first_submenu( 'users', __( 'All Users') );
+
+	$admin_menu->add_submenu( 'users', array(
+		'title' => _x('Add New', 'user'),
+		'cap' => array( 'create_users', 'promote_users' ),
+		'slug' => 'user-new.php',
+	) );
+
+	$admin_menu->add_submenu( 'users', array(
+		'title' => __('Your Profile'),
+		'cap' => 'read',
+		'slug' => 'profile.php',
+	) );
 } else {
 	$_wp_real_parent_file['users.php'] = 'profile.php';
-	$submenu['profile.php'][5] = array(__('Your Profile'), 'read', 'profile.php');
-	if ( current_user_can('create_users') )
-		$submenu['profile.php'][10] = array(__('Add New User'), 'create_users', 'user-new.php');
-	else
-		$submenu['profile.php'][10] = array(__('Add New User'), 'promote_users', 'user-new.php');
-}
 
-$menu[75] = array( __('Tools'), 'edit_posts', 'tools.php', '', 'menu-top menu-icon-tools', 'menu-tools', 'div' );
-	$submenu['tools.php'][5] = array( __('Available Tools'), 'edit_posts', 'tools.php' );
-	$submenu['tools.php'][10] = array( __('Import'), 'import', 'import.php' );
-	$submenu['tools.php'][15] = array( __('Export'), 'export', 'export.php' );
-	if ( is_multisite() && !is_main_site() )
-		$submenu['tools.php'][25] = array( __('Delete Site'), 'manage_options', 'ms-delete-site.php' );
-	if ( ! is_multisite() && defined('WP_ALLOW_MULTISITE') && WP_ALLOW_MULTISITE )
-		$submenu['tools.php'][50] = array(__('Network Setup'), 'manage_options', 'network.php');
+	$admin_menu->add_first_submenu( 'users', __( 'Your Profile') );
 
-$menu[80] = array( __('Settings'), 'manage_options', 'options-general.php', '', 'menu-top menu-icon-settings', 'menu-settings', 'div' );
-	$submenu['options-general.php'][10] = array(_x('General', 'settings screen'), 'manage_options', 'options-general.php');
-	$submenu['options-general.php'][15] = array(__('Writing'), 'manage_options', 'options-writing.php');
-	$submenu['options-general.php'][20] = array(__('Reading'), 'manage_options', 'options-reading.php');
-	$submenu['options-general.php'][25] = array(__('Discussion'), 'manage_options', 'options-discussion.php');
-	$submenu['options-general.php'][30] = array(__('Media'), 'manage_options', 'options-media.php');
-	$submenu['options-general.php'][35] = array(__('Privacy'), 'manage_options', 'options-privacy.php');
-	$submenu['options-general.php'][40] = array(__('Permalinks'), 'manage_options', 'options-permalink.php');
+	$admin_menu->add_submenu( 'users', array(
+		'title' => _x('Add New', 'user'),
+		'cap' => array( 'create_users', 'promote_users' ),
+		'slug' => 'user-new.php',
+	) );
+}
+
+$admin_menu->append( array(
+	'title' => __('Tools'),
+	'cap' => 'edit_posts',
+	'slug' => 'tools.php',
+	'id' => 'tools',
+) );
+
+	$admin_menu->add_first_submenu( 'tools', __('Available Tools') );
+
+	$admin_menu->add_submenu( 'tools', array(
+		'title' => __('Import'),
+		'cap' => 'import',
+		'slug' => 'import.php',
+	) );
+
+	$admin_menu->add_submenu( 'tools', array(
+		'title' => __('Export'),
+		'cap' => 'export',
+		'slug' => 'export.php',
+	) );
+
+	if ( is_multisite() && !is_main_site() ) {
+		$admin_menu->add_submenu( 'tools', array(
+			'title' => __('Delete Site'),
+			'cap' => 'manage_options',
+			'slug' => 'ms-delete-site.php',
+		) );
+	}
 
-$_wp_last_utility_menu = 80; // The index of the last top-level menu in the utility menu group
+	if ( ! is_multisite() && defined('WP_ALLOW_MULTISITE') && WP_ALLOW_MULTISITE ) {
+		$admin_menu->add_submenu( 'tools', array(
+			'title' => __('Network Setup'),
+			'cap' => 'manage_options',
+			'slug' => 'network.php',
+		) );
+	}
 
-$menu[99] = array( '', 'read', 'separator-last', '', 'wp-menu-separator' );
+$admin_menu->append( array(
+	'title' => __('Settings'),
+	'cap' => 'manage_options',
+	'slug' => 'options-general.php',
+	'id' => 'settings',
+) );
+
+	$admin_menu->add_first_submenu( 'settings', _x('General', 'settings screen') );
+
+	$admin_menu->add_submenu( 'settings', array(
+		'title' => __('Writing'),
+		'cap' => 'manage_options',
+		'slug' => 'options-writing.php',
+	) );
+
+	$admin_menu->add_submenu( 'settings', array(
+		'title' => __('Reading'),
+		'cap' => 'manage_options',
+		'slug' => 'options-reading.php',
+	) );
+
+	$admin_menu->add_submenu( 'settings', array(
+		'title' => __('Discussion'),
+		'cap' => 'manage_options',
+		'slug' => 'options-writing.php',
+	) );
+
+	$admin_menu->add_submenu( 'settings', array(
+		'title' => __('Media'),
+		'cap' => 'manage_options',
+		'slug' => 'options-media.php',
+	) );
+
+	$admin_menu->add_submenu( 'settings', array(
+		'title' => __('Privacy'),
+		'cap' => 'manage_options',
+		'slug' => 'options-privacy.php',
+	) );
+
+	$admin_menu->add_submenu( 'settings', array(
+		'title' => __('Permalinks'),
+		'cap' => 'manage_options',
+		'slug' => 'options-permalink.php',
+	) );
+
+$admin_menu->append( array(
+	'id' => 'separator-last',
+	'class' => 'wp-menu-separator',
+) );
+
+// CPT menus need to be added later due to 'menu_position'
+$admin_menu->_add_post_type_menus();
+add_action( 'admin_menu', array( $admin_menu, '_add_post_type_submenus' ), 9 );
 
 // Back-compat for old top-levels
 $_wp_real_parent_file['post.php'] = 'edit.php';
@@ -233,6 +347,6 @@ $compat = array(
 	'edit-comments' => 'comments',
 	'options-general' => 'settings',
 	'themes' => 'appearance',
-	);
+);
 
 require_once(ABSPATH . 'wp-admin/includes/menu.php');
diff --git wp-admin/network/menu.php wp-admin/network/menu.php
index a35a280..a6308bf 100644
--- wp-admin/network/menu.php
+++ wp-admin/network/menu.php
@@ -7,57 +7,133 @@
  * @since 3.1.0
  */
 
-/* translators: Network menu item */
-$menu[2] = array(__('Dashboard'), 'manage_network', 'index.php', '', 'menu-top menu-top-first menu-icon-dashboard', 'menu-dashboard', 'div');
+$admin_menu = new WP_Admin_Menu;
 
-$menu[4] = array( '', 'read', 'separator1', '', 'wp-menu-separator' );
+$admin_menu->append( array(
+	/* translators: Network menu item */
+	'title' => __( 'Dashboard' ),
+	'cap' => 'manage_network',
+	'id' => 'dashboard',
+	'slug' => 'index.php',
+) );
 
-/* translators: Sites menu item */
-$menu[5] = array(__('Sites'), 'manage_sites', 'sites.php', '', 'menu-top menu-icon-site', 'menu-site', 'div');
-$submenu['sites.php'][5]  = array( __('All Sites'), 'manage_sites', 'sites.php' );
-$submenu['sites.php'][10]  = array( _x('Add New', 'site'), 'create_sites', 'site-new.php' );
+$admin_menu->append( array(
+	'id' => 'separator1',
+	'class' => 'wp-menu-separator',
+) );
 
-$menu[10] = array(__('Users'), 'manage_network_users', 'users.php', '', 'menu-top menu-icon-users', 'menu-users', 'div');
-$submenu['users.php'][5]  = array( __('All Users'), 'manage_network_users', 'users.php' );
-$submenu['users.php'][10]  = array( _x('Add New', 'user'), 'create_users', 'user-new.php' );
+$admin_menu->append( array(
+	/* translators: Sites menu item */
+	'title' => __( 'Sites' ),
+	'cap' => 'manage_sites',
+	'id' => 'site',
+	'slug' => 'sites.php',
+) );
+
+	$admin_menu->add_first_submenu( 'site', __( 'All Sites' ) );
+
+	$admin_menu->add_submenu( 'site', array(
+		/* translators: add new site */
+		'title' => _x( 'Add New', 'site' ),
+		'cap' => 'create_sites',
+		'slug' => 'site-new.php',
+	) );
+
+$admin_menu->append( array(
+	'title' => __( 'Users' ),
+	'cap' => 'manage_network_users',
+	'slug' => 'users.php',
+	'id' => 'users',
+) );
+
+	$admin_menu->add_first_submenu( 'users', __( 'All Users') );
+
+	$admin_menu->add_submenu( 'users', array(
+		'title' => _x( 'Add New', 'user' ),
+		'cap' => 'create_users',
+		'slug' => 'user-new.php',
+	) );
 
 $update_data = wp_get_update_data();
 
-if ( $update_data['counts']['themes'] ) {
-	$menu[15] = array(sprintf( __( 'Themes %s' ), "<span class='update-plugins count-{$update_data['counts']['themes']}'><span class='theme-count'>" . number_format_i18n( $update_data['counts']['themes'] ) . "</span></span>" ), 'manage_network_themes', 'themes.php', '', 'menu-top menu-icon-appearance', 'menu-appearance', 'div' );
-} else {
-	$menu[15] = array( __( 'Themes' ), 'manage_network_themes', 'themes.php', '', 'menu-top menu-icon-appearance', 'menu-appearance', 'div' );
-}
-$submenu['themes.php'][5]  = array( __('Installed Themes'), 'manage_network_themes', 'themes.php' );
-$submenu['themes.php'][10] = array( _x('Add New', 'theme'), 'install_themes', 'theme-install.php' );
-$submenu['themes.php'][15] = array( _x('Editor', 'theme editor'), 'edit_themes', 'theme-editor.php' );
-
-if ( current_user_can( 'update_plugins' ) ) {
-	$menu[20] = array( sprintf( __( 'Plugins %s' ), "<span class='update-plugins count-{$update_data['counts']['plugins']}'><span class='plugin-count'>" . number_format_i18n( $update_data['counts']['plugins'] ) . "</span></span>" ), 'manage_network_plugins', 'plugins.php', '', 'menu-top menu-icon-plugins', 'menu-plugins', 'div');
-} else {
-	$menu[20] = array( __('Plugins'), 'manage_network_plugins', 'plugins.php', '', 'menu-top menu-icon-plugins', 'menu-plugins', 'div' );
-}
-$submenu['plugins.php'][5]  = array( __('Installed Plugins'), 'manage_network_plugins', 'plugins.php' );
-$submenu['plugins.php'][10] = array( _x('Add New', 'plugin editor'), 'install_plugins', 'plugin-install.php' );
-$submenu['plugins.php'][15] = array( _x('Editor', 'plugin editor'), 'edit_plugins', 'plugin-editor.php' );
-
-$menu[25] = array(__('Settings'), 'manage_network_options', 'settings.php', '', 'menu-top menu-icon-settings', 'menu-settings', 'div');
-if ( defined( 'MULTISITE' ) && defined( 'WP_ALLOW_MULTISITE' ) && WP_ALLOW_MULTISITE ) {
-	$submenu['settings.php'][5]  = array( __('Network Settings'), 'manage_network_options', 'settings.php' );
-	$submenu['settings.php'][10] = array( __('Network Setup'), 'manage_network_options', 'setup.php' );
-}
-
-if ( $update_data['counts']['total'] ) {
-	$menu[30] = array( sprintf( __( 'Updates %s' ), "<span class='update-plugins count-{$update_data['counts']['total']}' title='{$update_data['title']}'><span class='update-count'>" . number_format_i18n($update_data['counts']['total']) . "</span></span>" ), 'manage_network', 'upgrade.php', '', 'menu-top menu-icon-tools', 'menu-update', 'div' );
-} else {
-	$menu[30] = array( __( 'Updates' ), 'manage_network', 'upgrade.php', '', 'menu-top menu-icon-tools', 'menu-update', 'div' );
-}
+$admin_menu->append( array(
+	'title' => _admin_menu_theme_update_count( $update_data ),
+	'cap' => 'manage_network_themes',
+	'slug' => 'themes.php',
+	'id' => 'appearance',
+) );
 
-unset($update_data);
+	$admin_menu->add_first_submenu( 'appearance', __( 'Installed Themes' ) );
+
+	$admin_menu->add_submenu( 'appearance', array(
+		'title' => _x( 'Add New', 'theme' ),
+		'cap' => 'install_themes',
+		'slug' => 'theme-install.php',
+	) );
+
+	$admin_menu->add_submenu( 'appearance', array(
+		'title' => _x( 'Editor', 'theme editor' ),
+		'cap' => 'edit_themes',
+		'slug' => 'theme-editor.php',
+	) );
+
+$admin_menu->append( array(
+	'title' => _admin_menu_plugin_update_count( $update_data ),
+	'cap' => 'manage_network_plugins',
+	'slug' => 'plugins.php',
+	'id' => 'plugins',
+) );
+
+	$admin_menu->add_first_submenu( 'plugins', __( 'Installed Plugins' ) );
 
-$submenu[ 'upgrade.php' ][10] = array( __( 'Available Updates' ), 'update_core', 'update-core.php' );
-$submenu[ 'upgrade.php' ][15] = array( __( 'Update Network' ), 'manage_network', 'upgrade.php' );
+	$admin_menu->add_submenu( 'plugins', array(
+		'title' => _x( 'Add New', 'plugin' ),
+		'cap' => 'install_plugins',
+		'slug' => 'plugin-install.php',
+	) );
+
+	$admin_menu->add_submenu( 'plugins', array(
+		'title' => _x( 'Editor', 'plugin editor' ),
+		'cap' => 'edit_plugins',
+		'slug' => 'plugin-editor.php',
+	) );
+
+$admin_menu->append( array(
+	'title' => __('Settings'),
+	'cap' => 'manage_network_options',
+	'slug' => 'settings.php',
+	'id' => 'settings',
+) );
+
+	$admin_menu->add_first_submenu( 'settings', __('Network Settings') );
+
+	$admin_menu->add_submenu( 'settings', array(
+		'title' => __('Writing'),
+		'cap' => 'manage_network_options',
+		'slug' => 'setup.php',
+	) );
+
+$admin_menu->append( array(
+	'title' => _admin_menu_update_count( $update_data ),
+	'cap' => 'manage_network',
+	'slug' => 'upgrade.php',
+	'class' => 'menu-icon-tools',
+	'id' => 'update',
+) );
+
+	$admin_menu->add_first_submenu( 'update', __('Update Network') );
+
+	$admin_menu->add_submenu( 'update', array(
+		'title' => __('Available Updates'),
+		'cap' => 'update_core',
+		'slug' => 'update-core.php',
+	) );
+
+unset($update_data);
 
-$menu[99] = array( '', 'read', 'separator-last', '', 'wp-menu-separator-last' );
+$admin_menu->append( array(
+	'id' => 'separator-last',
+	'class' => 'wp-menu-separator',
+) );
 
 require_once(ABSPATH . 'wp-admin/includes/menu.php');
diff --git wp-admin/themes.php wp-admin/themes.php
index 5a78fe3..57682df 100644
--- wp-admin/themes.php
+++ wp-admin/themes.php
@@ -150,36 +150,32 @@ $customize_title = sprintf( __( 'Customize &#8220;%s&#8221;' ), $ct->display('Na
 		<?php theme_update_available( $ct ); ?>
 	</div>
 
-	<?php
-	// Pretend you didn't see this.
+<?php
 	$options = array();
-	if ( is_array( $submenu ) && isset( $submenu['themes.php'] ) ) {
-		foreach ( (array) $submenu['themes.php'] as $item) {
-			$class = '';
-			if ( 'themes.php' == $item[2] || 'theme-editor.php' == $item[2] )
-				continue;
-			// 0 = name, 1 = capability, 2 = file
-			if ( ( strcmp($self, $item[2]) == 0 && empty($parent_file)) || ($parent_file && ($item[2] == $parent_file)) )
-				$class = ' class="current"';
-			if ( !empty($submenu[$item[2]]) ) {
-				$submenu[$item[2]] = array_values($submenu[$item[2]]); // Re-index.
-				$menu_hook = get_plugin_page_hook($submenu[$item[2]][0][2], $item[2]);
-				if ( file_exists(WP_PLUGIN_DIR . "/{$submenu[$item[2]][0][2]}") || !empty($menu_hook))
-					$options[] = "<a href='admin.php?page={$submenu[$item[2]][0][2]}'$class>{$item[0]}</a>";
-				else
-					$options[] = "<a href='{$submenu[$item[2]][0][2]}'$class>{$item[0]}</a>";
-			} else if ( current_user_can($item[1]) ) {
-				if ( file_exists(ABSPATH . 'wp-admin/' . $item[2]) ) {
-					$options[] = "<a href='{$item[2]}'$class>{$item[0]}</a>";
-				} else {
-					$options[] = "<a href='themes.php?page={$item[2]}'$class>{$item[0]}</a>";
-				}
-			}
+	$parent_menu = $admin_menu->get( 'appearance' );
+	foreach ( $parent_menu->get_children() as $item ) {
+		if ( 'themes.php' == $item->slug || 'theme-editor.php' == $item->slug )
+			continue;
+
+		if ( !current_user_can( $item->cap ) )
+			continue;
+
+		$class = '';
+		if ( ( $self == $item->slug && empty($parent_file) ) ||
+			 ( $parent_file && ($item->slug == $parent_file) ) )
+			$class = ' class="current"';
+
+		if ( file_exists( ABSPATH . 'wp-admin/' . $item->slug ) ) {
+			$url = $item->slug;
+		} else {
+			$url = 'themes.php?page=' . $item->slug;
 		}
+
+		$options[] = "<a href='$url'$class>{$item->title}</a>";
 	}
 
 	if ( $options || current_user_can( 'edit_theme_options' ) ) :
-	?>
+?>
 	<div class="theme-options">
 		<?php if ( current_user_can( 'edit_theme_options' ) ) : ?>
 		<a id="customize-current-theme-link" href="<?php echo wp_customize_url(); ?>" class="load-customize hide-if-no-customize" title="<?php echo esc_attr( $customize_title ); ?>"><?php _e( 'Customize' ); ?></a>
diff --git wp-admin/user/menu.php wp-admin/user/menu.php
index 9f30076..2a8cf99 100644
--- wp-admin/user/menu.php
+++ wp-admin/user/menu.php
@@ -7,16 +7,33 @@
  * @since 3.1.0
  */
 
-$menu[2] = array(__('Dashboard'), 'exist', 'index.php', '', 'menu-top menu-top-first menu-icon-dashboard', 'menu-dashboard', 'div');
+$admin_menu = new WP_Admin_Menu;
 
-$menu[4] = array( '', 'exist', 'separator1', '', 'wp-menu-separator' );
+$admin_menu->append( array(
+	'title' => __( 'Dashboard' ),
+	'cap' => 'exist',
+	'id' => 'dashboard',
+	'slug' => 'index.php',
+) );
 
-$menu[70] = array( __('Profile'), 'exist', 'profile.php', '', 'menu-top menu-icon-users', 'menu-users', 'div' );
+$admin_menu->append( array(
+	'id' => 'separator1',
+	'class' => 'wp-menu-separator',
+) );
 
-$menu[99] = array( '', 'exist', 'separator-last', '', 'wp-menu-separator-last' );
+$admin_menu->append( array(
+	'title' => __( 'Profile' ),
+	'cap' => 'exist',
+	'id' => 'users',
+	'slug' => 'profile.php',
+) );
+
+$admin_menu->append( array(
+	'id' => 'separator-last',
+	'class' => 'wp-menu-separator',
+) );
 
 $_wp_real_parent_file['users.php'] = 'profile.php';
 $compat = array();
-$submenu = array();
 
 require_once(ABSPATH . 'wp-admin/includes/menu.php');
diff --git wp-includes/class-wp-xmlrpc-server.php wp-includes/class-wp-xmlrpc-server.php
index b65e4fb..6932709 100644
--- wp-includes/class-wp-xmlrpc-server.php
+++ wp-includes/class-wp-xmlrpc-server.php
@@ -709,7 +709,7 @@ class wp_xmlrpc_server extends IXR_Server {
 		}
 
 		if ( in_array( 'menu', $fields ) ) {
-			$_post_type['menu_position'] = (int) $post_type->menu_position;
+			$_post_type['menu_position'] = $post_type->menu_position;
 			$_post_type['menu_icon'] = $post_type->menu_icon;
 			$_post_type['show_in_menu'] = (bool) $post_type->show_in_menu;
 		}
diff --git wp-includes/functions.php wp-includes/functions.php
index 2f0b6f0..0fcbba8 100644
--- wp-includes/functions.php
+++ wp-includes/functions.php
@@ -2456,12 +2456,18 @@ function wp_maybe_load_widgets() {
  * Append the Widgets menu to the themes main menu.
  *
  * @since 2.2.0
- * @uses $submenu The administration submenu list.
+ * @uses $admin_menu The administration menu object.
  */
 function wp_widgets_add_menu() {
-	global $submenu;
-	$submenu['themes.php'][7] = array( __( 'Widgets' ), 'edit_theme_options', 'widgets.php' );
-	ksort( $submenu['themes.php'], SORT_NUMERIC );
+	global $admin_menu;
+
+	$parent = $admin_menu->get( 'appearance' );
+
+	$parent->insert_after( 'themes.php', array(
+		'title' => __( 'Widgets' ),
+		'cap' => 'edit_theme_options',
+		'slug' => 'widgets.php'
+	) );
 }
 
 /**
diff --git wp-includes/post.php wp-includes/post.php
index b22abf6..e09bb1c 100644
--- wp-includes/post.php
+++ wp-includes/post.php
@@ -918,9 +918,9 @@ function get_post_types( $args = array(), $output = 'names', $operator = 'and' )
  *     * If not set, the default is inherited from show_ui
  * - show_in_admin_bar - Makes this post type available via the admin bar.
  *     * If not set, the default is inherited from show_in_menu
- * - menu_position - The position in the menu order the post type should appear.
  *     * show_in_menu must be true
  *     * Defaults to null, which places it at the bottom of its area.
+ * - menu_position - The id of the admin menu; this menu will show up above it.
  * - menu_icon - The url to the icon to be used for this menu. Defaults to use the posts icon.
  * - capability_type - The string to use to build the read, edit, and delete capabilities. Defaults to 'post'.
  *     * May be passed as an array to allow for alternative plurals when using this argument as a base to construct the
@@ -980,9 +980,10 @@ function register_post_type( $post_type, $args = array() ) {
 		'_builtin' => false, '_edit_link' => 'post.php?post=%d', 'hierarchical' => false,
 		'public' => false, 'rewrite' => true, 'has_archive' => false, 'query_var' => true,
 		'supports' => array(), 'register_meta_box_cb' => null,
-		'taxonomies' => array(), 'show_ui' => null, 'menu_position' => null, 'menu_icon' => null,
+		'taxonomies' => array(),
+		'show_ui' => null, 'show_in_menu' => null, 'menu_position' => null, 'menu_icon' => null,
 		'can_export' => true,
-		'show_in_nav_menus' => null, 'show_in_menu' => null, 'show_in_admin_bar' => null,
+		'show_in_nav_menus' => null, 'show_in_admin_bar' => null,
 		'delete_with_user' => null,
 	);
 	$args = wp_parse_args($args, $defaults);
@@ -1006,6 +1007,11 @@ function register_post_type( $post_type, $args = array() ) {
 	if ( null === $args->show_in_menu || ! $args->show_ui )
 		$args->show_in_menu = $args->show_ui;
 
+	if ( is_numeric( $args->menu_position ) ) {
+		_deprecated_argument( __FUNCTION__, '3.5', __( "Numeric values for 'menu_position' are deprecated. Use menu ids instead." ) );
+		$args->menu_position = null;
+	}
+
 	// If not set, default to the whether the full UI is shown.
 	if ( null === $args->show_in_admin_bar )
 		$args->show_in_admin_bar = true === $args->show_in_menu;
@@ -1293,23 +1299,6 @@ function _get_custom_object_labels( $object, $nohier_vs_hier_defaults ) {
 }
 
 /**
- * Adds submenus for post types.
- *
- * @access private
- * @since 3.1.0
- */
-function _add_post_type_submenus() {
-	foreach ( get_post_types( array( 'show_ui' => true ) ) as $ptype ) {
-		$ptype_obj = get_post_type_object( $ptype );
-		// Submenus only.
-		if ( ! $ptype_obj->show_in_menu || $ptype_obj->show_in_menu === true )
-			continue;
-		add_submenu_page( $ptype_obj->show_in_menu, $ptype_obj->labels->name, $ptype_obj->labels->all_items, $ptype_obj->cap->edit_posts, "edit.php?post_type=$ptype" );
-	}
-}
-add_action( 'admin_menu', '_add_post_type_submenus' );
-
-/**
  * Register support of certain features for a post type.
  *
  * All features are directly associated with a functional area of the edit screen, such as the
