Take the 2-minute tour ×
WordPress Development Stack Exchange is a question and answer site for WordPress developers and administrators. It's 100% free, no registration required.

How could I make particular role access widgets administration menu (wp-admin/widgets.php), but not access other theme administration menu elements.

I have got that far as to display Appearance menu linking to widgets.php by binding to _admin_menu event and modifying global variable $submenu. Although that clearly does not work, as I see this code in wp-admin/widgets.php itself:

    if ( ! current_user_can('edit_theme_options') )
            wp_die( __( 'Cheatin’ uh?' ));

It is clear - you SHALL NOT be allowed to access this page, unless you clearly have edit_theme_options capability. But what to do, if I do not want other elements of this capability, apart of widgets, modifiable by current user (role)?

It is easy to "hide" other options from menu. But that leaves user, who knows how WP works (understands addressing, at least), free to access them and that I would clearly like to avoid.

Thanks for any help in advance.

share|improve this question

3 Answers 3

Confirming…

Yes, there's currently (WP 3.4.1) no way to modify the access arguments for admin menu pages. The only one, that you can modify through the public wp API is the »Comments« menu item. All others are registered by hand.

But there's help coming from @scribu (read more at the trac ticket) who has so far taken a lot of effort to bring something more useful to core.

Explanation

When look deeper into core, then you'll see the function wp_widgets_add_menu() inside ~/wp-includes/functions.php. This one basically does add the submenu item since WP 2.2…

function wp_widgets_add_menu() {
    global $submenu;
    $submenu['themes.php'][7] = array( __( 'Widgets' ), 'edit_theme_options', 'widgets.php' );
    ksort( $submenu['themes.php'], SORT_NUMERIC );
}

This function gets added to the _admin_menu action by the wp_maybe_load_widgets() function.

Intermediate work around for the menu item & widgets page

Currently the function, that loads the default widgets and registers the sub menu item (namely wp_maybe_load_widgets) is called during the plugins_loaded hook with a priority of 0.

That makes it tough to deregister it with a normal plugin. Therefore you need to use a plugin in your mu-plugins folder.

<?php
/* Plugin Name: »Kaisers« Deny Widgets page access */
! defined( 'ABSPATH' ) AND exit;

// Init the plugin
add_action( 'muplugins_loaded', array( 'wpse6106_deny_widgets', 'init' ), 0 );

class wpse6106_deny_widgets
{
    static public $instance;

    public $required_cap = 'SET_CUSTOM_CAP_HERE';

    /**
     * Get the instance of the plugin
     * @since  2012-08-07.1505
     * @return void
     */
    static function init()
    {
        null === self :: $instance AND self :: $instance = new self;
        return self :: $instance;
    }

    /**
     * Setup
     * Removes the default function that registers the widgets.php sub menu item.
     * @since  2012-08-07.1505
     * @return void
     */
    function __construct()
    {
        // remove core function...
        remove_action( 'plugins_loaded', 'wp_maybe_load_widgets', 0 );

        // ...and add our own
        add_action( 'admin_head', array( $this, 'widgets_menu_access' ), 0 );

        // Then abort any attempt to access the widgets page
        add_action( 'load-widgets.php', array( $this, 'widgets_page_access' ), 0 );
    }

    /**
     * Adds an action, that re-registers the sub menu item with a custom capability.
     * @since  2012-08-07.1505
     * @return void
     */
    function widgets_menu_access()
    {
        global $submenu;

        // Call default widgets file
        require_once( ABSPATH . WPINC . '/default-widgets.php' );

        $submenu['themes.php'][7] = array( 
             __( 'Widgets' )
            ,$this->required_cap
            ,'widgets.php'
        );
        ksort( $submenu['themes.php'], SORT_NUMERIC );
    }

    /**
     * Does a second check if someone without the custom cap entered the widgets page and dies.
     * @since  2012-08-07.1505
     * @return void
     */
    function widgets_page_access()
    {
        get_currentuserinfo();
        global $current_user;

        if ( ! current_user_can( $this->required_cap ) )
            wp_die( __( 'Cheatin&#8217; uh?' ) );
    }
}

Simply drop this into your MU-Plugins folder, adjust the SET_CUSTOM_CAP_HERE string inside the plugin (class variable on top ↑) and you're ready to go. Make sure that you're using some role manager (like Members, that allows you to give this role only to those who are meant to access the widgets page. Or add it manually with some own/custom plugin.

Also make sure, that users don't have some left over capability stuff. If it's not working, deactivate all plugins, switch back to TwentyTen/Eleven and do reset of your local database with a plugin like »WordPress Reset«.

Proven Result

enter image description here

Note: The plugin is tested and works in a plain vanilla installation.


Disable default widgets and the submenu items

Note: This is only for later readers, who want to get rid of it all.

If you want to completely get rid of all default widgets, then there's a simple filter, that you can call, that stops including the ~/wp-includes/default-widgets.php file and disables the registration of the page:

add_filter( 'load_default_widgets', '__return_false' );
share|improve this answer
    
thank you for the lengthy comment (haven't seen that trac issue), but this does not work due to the reason I have explained: in [this][core.svn.wordpress.org/trunk/wp-admin/widgets.php] file there is a check if ( ! current_user_can('edit_theme_options') ), thus any other permission fails. But yes, it works in menu items, nevertheless. –  Justas Butkus Aug 7 '12 at 6:24
    
@JustasButkus No problem. In theory it would've been enough to change the permission of the custom cap (have you tried it, or are you just assuming it doesn't work? My test showed it working...). But anyway, I give you another update with a full plugin that does a second check inside the widgets.php page and aborts. Up voting appreciated. –  kaiser Aug 7 '12 at 13:13
    
thank you very much for the help, although have you actually tested, that given user, who does not have edit_theme_options permission could still access widgets configuration (wp-admin/widgets.php)? Because I have tested it (wp_get_current_user()->remove_cap('edit_theme_options')) and given the code above - I could not access widgets configuration, as I get the above mentioned error (stands for a reason). –  Justas Butkus Aug 7 '12 at 14:27
    
@JustasButkus Yes, I tested it - else I wouldn't have said so. For me, it's enough to only the function restricting the menu. This prompts me with what you can see in the screenshot. If I remove the admin menu function from the MU-Plugin, I get the "Cheatin' uh?"-message. If I remove the capability nothing changes. Please deactivate all your plugins, switch back to the default TwentyEleven theme and try again. This is a problem with your setup (or some left overs in the DB/userWordPress Development Meta table) and not with my plugin. I also added this info as edit and linked a plugin that will help u. –  kaiser Aug 7 '12 at 14:39
    
maybe we are discussing different points. I am not talking about ADDING a new capability, without which users would be unable to see menu entry. Create a new user with Subscriber privileges. Then check if he can access wp-widgets.php (he should NOT). Then use your plugin to grant him SET_CUSTOM_CAP_HERE and check again - if you can access wp-widgets.php. Please make sure, that user was not granted edit_theme_options capability. For me, given clean (I actually downloaded fresh WP and installed in clean DB) installation - user does not get access. –  Justas Butkus Aug 9 '12 at 10:50

OVERVIEW

While the question was about limiting editor roles to access only Widgets, the following example shows how to limit access only to Menus. However as you will see, it can easily be changed to allow only Widgets or more!

I added Step #3 because I forgot about the Admin Bar. Oops! So now whether logged into the Dashboard or logged in and on the WP website, you have full control of what is available to an editor for the 'edit_theme_capablity' sub-menus.

If you don't have a Roles & Capabilities Plugin installed, you can do this:

(If you do, skip #1 and go to #2, then #3)

(1) Add this to your theme's functions.php:

// Add all Editors the privilege to edit Themes, Widgets, Menus, Backgrounds

// get the the role object - editor, author, etc. (or those specially created)
$role_object = get_role( 'editor' );

// add $cap capability to this role object
// 'edit_theme_options' enables Dashboard APPEARANCE sub-menus
// for Themes, Widgets, Menus, and Backgrounds for users with that role
$role_object->add_cap( 'edit_theme_options' );


(2) Add this to admin-footer.php : (located in wp-admin directory)
What this does is to allow you to choose which option you want Editors to have on their Dashboard.
READ THIS for more info from the author of the jQuery snippet.

<?php
  //  Using jQuery: How to allow Editors to edit only Menus (or more!)
  //  Placed in admin-footer.php as Dashboard comes from the wp-admin files

  if ( is_user_logged_in() ) { // This IF may be redundant, but safe is better than sorry...
    if ( current_user_can('edit_theme_options') && !current_user_can('manage_options') ) { // Check if non-Admin
?>
      <script>
    jQuery.noConflict();
    jQuery(document).ready(function() {
      //  Comment out the line you WANT to enable, so it displays (is NOT removed).
      //  For example, the jQuery line for MENUS is commented out below so it's not removed.

      // THEMES:  If you want to allow THEMES, also comment out APPEARANCE if you want it to display Themes when clicked. (Default behaviour)
      jQuery('li#menu-appearance.wp-has-submenu li a[href="themes.php"]').remove();
      jQuery('li#menu-appearance.wp-has-submenu a.wp-has-submenu').removeAttr("href");

      // WIDGETS:
      jQuery('li#menu-appearance.wp-has-submenu li a[href="widgets.php"]').remove();

      // MENUS:
      // jQuery('li#menu-appearance.wp-has-submenu li a[href="nav-menus.php"]').remove();

      // BACKGROUND:
      jQuery('li#menu-appearance.wp-has-submenu li a[href="themes.php?page=custom-background"]').remove();
    });
      </script>
<?php
    } // End IF current_user_can...
  } // End IF is_user_logged_in...
?>


(3) Add this to the Theme's footer.php :
What this does is to allow you to choose which option you want Editors to have on their Admin Bar.

<?php
  //  Using jQuery: How to allow Editors to edit only Menus (or more!)
  //  Placed in THEME's footer.php as the Admin Bar is added when a user is logged in

  if ( is_user_logged_in() ) { // This IF may be redundant, but safe is better than sorry...
    if ( current_user_can('edit_theme_options') && !current_user_can('manage_options') ) { // Check if non-Admin
?>
      <script>
    jQuery.noConflict();
    jQuery(document).ready(function() {
      //  Comment out the line you WANT to enable, so it displays (is NOT removed).
      //  For example, the jQuery line for MENUS is commented out below so it's not removed.

      // THEMES:
      jQuery('li#wp-admin-bar-themes').remove();

      // CUSTOMIZE:
      jQuery('li#wp-admin-bar-customize').remove();

      // WIDGETS:
      jQuery('li#wp-admin-bar-widgets').remove();

      // MENUS:
      // jQuery('li#wp-admin-bar-menus').remove();

      // BACKGROUND:
      jQuery('li#wp-admin-bar-background').remove();
    });
      </script>
<?php
    } // End IF current_user_can...
  } // End IF is_user_logged_in...
?>
share|improve this answer
1  
Note that <style> contents are not meant to work backwards, the cannot style elements that are already part of the DOM when the <style> is printed. It works in some browsers, but you cannot rely on that. –  toscho Feb 18 '13 at 12:49
    
I know, and added comments in the code snippet that addresses that, and suggested that some enterprising person fix that on their own. Therefore, it naturally would need to go in the <code>admin-header.php</code> (somewhere at the end of the file) and perhaps I should have done that initially. I just came up with the solution hours before and tested it's functionality and proof of concept... And in a hurry to share it with the world. lol –  Chris Lemke Feb 18 '13 at 18:36
    
I replaced the code for #2 to use jQuery instead and that should fix any validation issues. All in all, the jQuery piece coupled with any Role & Capability Plugin (or use code in #1) should address the original question to their satisfaction. I wrote it to provide the granularity of control that was/is missing. Others are welcome to use it (why I posted it) and even to others who want to add it to their Plugins for Roles & Capabilities. –  Chris Lemke Feb 19 '13 at 8:26
    
I added Step #3 because I forgot about the Admin Bar. Oops! Now you have full control whether an Editor is in their Dashboard or logged in and on the website. –  Chris Lemke Feb 28 '13 at 5:01

I have found a partial way around this.

Binding actions

I add late (priority=10) method to user_has_cap action. In the bound method I check for page accessed (ensure it is wp-admin/widgets.php) and if it is the case, and permission being checked is edit_theme_options - I grant that permission return $all_caps + array('edit_theme_options' => true);.

Additionally I bound very late (priority=999) method to admin_menu action. That method, given that current user has my own defined capability to access only widgets menu (does not make sense - could be user_can_customize_themes_widgets_only) I iterate over global $submenu array section related to appearance ($submenu['themes.php']) and remove any elements, that do not have 'widgets.php' as path element (index for path is 2). And finally I re-add widgets.php to it, in case it got missing.

Things to consider

Why I say it is partial?

Because it is a work-around. I grant user edit_theme_options right, even if for a short time period and after making sure user is accessing widgets.php and not any other page.

And also - I can not rely on this to work with future versions of WP, as I am modifying global variable $submenu, that could change in any way at any time.

Given that - it is easy to implement solution, but it is not nice in any way.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.