Role that can edit only widgets, not other theme options

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:

Read More
    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.

Related posts

Leave a Reply

3 comments

  1. 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’ 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' );
    
  2. 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...
    ?>
    
  3. 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.