Question

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.

Était-ce utile?

La solution

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

Autres conseils

Putting together the earlier answer as well as the original poster's update, here's what worked for me:

Note that the code below happens to be for the CiviCRM plugin. It changes the access permissions from access_civicrm to administer_civicrm for the "Settings" menu item, since I don't want regular users to access that. In my case, rather than inspect the submenu name in English, I'm inspecting the slug for the page civi_options (i.e. civi_options.php) that is invoked. Also, I found it necessary to set $submenu['CiviCRM'][$key] = $item; to update the $submenu structure.

Also note that the method below is not true security! It suppresses the submenu item, but it does not prevent access to the submenu if the user knows the correct URL.


/////////////////////////////////////////////////
// ...this goes in my class `init()` function...
    add_action( 'admin_head', array( $this, 'cv_adjust_submenu_permissions') );

/////////////////////////////////////////////////
// ...and then later in the body of the class...

    function cv_adjust_submenu_permissions() {
        global $submenu;
        //error_log(print_r($submenu, true));
        foreach ( $submenu['CiviCRM'] as $key => $item ) {
            // change CiviCRM... Settings submenu item capability
            if ( 'civi_options' === $item[2] ) { // default capability: 'access_civicrm'
                $item[1] = 'administer_civicrm'; // new capability
                $submenu['CiviCRM'][$key] = $item;
            }
        }
    }
Licencié sous: CC-BY-SA avec attribution
Non affilié à wordpress.stackexchange
scroll top