Hooking into add_submenu_page

Ever come across a plugin that is using the manage_options capability for a page that… really doesn’t need to be? Well, I’ve come across just that.

This may be more of a general question about hooking into add_submenu_page, so not just specific to my use-case.

I looked at add_dashboard_page which is simply a wrapper for add_submenu_page:

function add_submenu_page( $parent_slug, $page_title, $menu_title, $capability, $menu_slug, $function = '' ) {
    global $submenu;
    global $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);

    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;
    }

    // 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;
        }
    }

    $submenu[$parent_slug][] = array ( $menu_title, $capability, $menu_slug, $page_title );

    $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;

    // No parent as top level
    $_parent_pages[$menu_slug] = $parent_slug;

    return $hookname;
}

It honestly doesn’t look like there’s anything I can actually plug into with an existing dashboard page to change the capability. So I’m trying to decide if I’m better off using remove_submenu_page and then attempt to redeclare that same submenu. I know there will be things I need to look out for (if the display function used for the page has anything that is additionally checking for the capability or displaying anything vital to the site). Always helpful to have a second pair of eyes on these so I don’t over complicate things. Thanks all!

Update

Thanks to both @toscho and @userabuser here’s what I got:

function wpse_71303_change_menu_cap()
{
    global $submenu;
    foreach ($submenu['index.php'] as $dashboard => $key) {
        if ($key[0] == 'Analytics360°') {
            $submenu['index.php'][$dashboard][1] = 'analytics';
        }
    }
}
add_action( 'admin_head', 'wpse_71303_change_menu_cap' );

If I run a print_r($submenu) I do see the new capability - but I still can't access the menu item under the client role I created (with theanalytics_360` capability) (used the Members plugin to create). Possibly firing too late? Definitely a bit odd. Thanks as always!

The code from Update works for anyone that comes across this. It was an unneeded check around add_dashboard_page() giving me the issue.

2 Answers
2

Hook into admin_head, the last action before the menu is rendered, and change the global $menu:

add_action( 'admin_head', 'wpse_71303_change_menu_cap' );

/**
 * Change the capability to access an admin menu item.
 *
 * @wp-hook admin_head
 * @return void
 */
function wpse_71303_change_menu_cap()
{
    global $menu;

    foreach ( $menu as $key => $item )
    {
        // Find menu by name
        if ( 'Tools' === $item[0] ) // default cap: 'edit_posts'
        {
            $menu[ $key ][1] = 'new_capability';
        }
    }
}

Leave a Comment