0

I am toying around with WordPress capabilities, and having some difficulty understanding where I am going wrong. Right now, I have an admin menu that appears for all administrators with STUDENTS as a submenu of SCHOOL: each of those link to http://example.com/wp-admin/edit.php?post_type=student. (When the user is in the HEADMASTER role, the link to the built-in UI for TEACHER appears and SCHOOL mimics its link). So far, all is well.

The bump in the road is when a non-HEADMASTER administrator clicks either SCHOOL or STUDENTS, the error "Sorry, you are not allowed to access this page" appears. (As expected, when that same admin accesses http://example.com/wp-admin/edit.php?post_type=teacher, the errors "You need a higher level of permission" and "Sorry, you are not allowed to edit posts in this post type" appear.) The issue is that the STUDENTS built-in UI is not accessible to all administrators. (As expected, there are no problems for HEADMASTERs.) How can this be resolved in such a way as to give all site admins access to STUDENTS and only HEADMASTERs access to TEACHERS while at the same time preserving the layout and functionality of the admin menu and built-in UI?

What I've Tried: When the 'show_in_menu' => 'edit.php?post_type=teacher' line is removed from the STUDENTS CPT, everything works as expected but STUDENTS becomes a top-level menu item instead of SCHOOL. I tried putting TEACHERS as a sub-menu item as STUDENTS, but SCHOOL does not change its link when the user is in the HEADMASTER role (also the ordering of TEACHERS and STUDENTS is backwards). I also tried add_menu_page() for SCHOOL and adding the slug returned for the show_in_menu argument of the TEACHERS and STUDENTS register_post_type args, respectively, but that didn't work because it created a page instead of forwarding to the appropriate built-in UI.

class School {
    public static function init() {
        add_action('init', array(__CLASS__, 'register_cpt'));
        add_action('init', array(__CLASS__, 'add_role'));
        add_action('admin_menu', array(__CLASS__, 'modify_menu'));
    }

    public static function register_cpt() {
        register_post_type('teacher', array(
            'labels' => array(
                'name'          => __('TEACHER'),
                'all_items'     => __('TEACHERS'),
                'menu_name'     => __('SCHOOL'),
            ),
            'show_ui'           => TRUE,
            'capability_type'   => 'educator',
            'map_meta_cap'      => TRUE,
        ));
        register_post_type('student', array(
            'labels' => array(
                'name'          => __('STUDENT'),
                'menu_name'     => __('STUDENTS'),
            ),
            'show_ui'           => TRUE,
            'show_in_menu'      => 'edit.php?post_type=teacher',
        ));
    }

    public static function add_role() {
        if(get_role('headmaster') === NULL) {
            $caps = array();
            foreach(get_role('administrator')->capabilities as $cap => $tmp) {
                $pos = strpos($cap, 'post');
                if($pos !== FALSE) {
                    $cap = str_replace('post', 'educator', $cap);
                    $caps[$cap] = TRUE;
                }
            }
            add_role('headmaster', 'HEADMASTER', $caps);
        }
    }

    public static function modify_menu() {
        global $submenu;
        unset($submenu['edit.php?post_type=teacher'][10]);
    }
} School::init();

1 Answer 1

0

From the codex

The function which is hooked in to handle the output of the page must check that the user has the required ‘capability’ as well.

What it fails to mention is that the check for subpages iterates upwards to its parent. If a user is not capable of accessing TEACHERS, it will not be able to access the sub-pages that appear under it in the admin menu. (I have no idea what benefits exist in this functionality, and I think it's likely a bug.)

add_menu_page() is your answer. What it does not say in the codex is that it will implement the functionality that you desire if you pass its slug to the custom post type's show_in_menu argument of register_post_type() (as opposed to the page's hook suffix returned by add_menu_page()). In order to get this functionality, a submenu item must be added before the parent menu item, such as by way of registering a post type. (In this way, the parent menu item will take on the characteristics of the first sub-menu item added. Pay attention here in that TEACHERS is added before STUDENTS so it will appear above it in the menu order, and the SCHOOL item is added after the CPT submenu items are added.) So, here are the changes to make to your code so it will work the way you want it to:

public static function register_cpt() {
    register_post_type('teacher', array(
        'labels' => array(
            ...
        //  'all_items' => __('TEACHERS'),      // Remove
            'menu_name' => __('TEACHERS'),      // Change
        ),
        'show_in_menu'      => 'school',        // Add
        ...
    ));
    register_post_type('student', array(
        ...
        'show_in_menu'      => 'school',        // Change
    ));
}

public static function modify_menu() {          // Change
    $pg = add_menu_page(
        NULL,                                   // Page name will not be displayed.
        'SCHOOL',                               // Formerly register_post_type('teacher')->menu_name.
        'administrator',                        // Lowest level of permission required to be displayed.
        'school'                                // Slug (used in register_post_type).
    //  ,NULL                                   // Page callback omitted as will not be called.
    );
    // I think you were passing $pg to show_in_menu, but this will not work
    // The page slug (school) must be used instead.
}

UPDATE: I submitted a bug ticket about this issue to WordPress core. Also, take notice that whatever page the subpages are nested under must also have the permissions of their parents even though they are displayed as capable of being displayed. So, the lowest level permission required is what is given in the add_menu_page method. In example, the new SCHOOL admin menu page will not allow the UI of STUDENTS to be displayed if it's capability is educator, exactly like what is happening with TEACHERS in your original example.

1
  • If this any help at all, and up vote and setting as the successful answer would be appreciated.
    – Mort 1305
    Commented May 27, 2020 at 19:45

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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