generator-plugin-wp icon indicating copy to clipboard operation
generator-plugin-wp copied to clipboard

Custom capability types for custom post types doesn't automatically give administrator capabilities

Open bignall opened this issue 9 years ago • 1 comments

If a custom capability type is defined for a custom post type the administrator role doesn't automatically get capabilities for that capability type. I discovered this when I did a fresh install of wordpress that has only my plugin in it, so I must have some plugin that changes that behavior in my old installation.

So I propose adding to the CPT sub generator the following:

A helper class (mine has some other stuff in it too, but this is what's pertinent to this issue):

class Helper {  
    /**
     * Adds all capabilities for a capability type to a role
     * 
     * @param string|array $cap_type the capability type - array allows for specifying 
     *      singular and plural, otherwise plural is constructed by adding an 's' 
     *      to the string
     * @param string $role_name the role name (default: administrator)
     * @return WP_Role
     */
    static function add_caps( $cap_type, $role_name = 'administrator' ) {
        $role = get_role( $role_name );
        if ( is_array( $cap_type ) ) {
            $singular = $cap_type[0];
            $plural = $cap_type[1];
        } else {
            $singular = $cap_type;
            $plural = $cap_type . 's';
        }

        $role->add_cap( 'edit_' . $singular );
        $role->add_cap( 'edit_' . $plural );
        $role->add_cap( 'edit_other_' . $plural );
        $role->add_cap( 'publish_' . $plural );
        $role->add_cap( 'read_' . $singular );
        $role->add_cap( 'read_private_' . $plural );
        $role->add_cap( 'delete_' . $singular );

        return $role;
    }
}

In the generated post type class:

Define the capability type:

       /**
     * Capability type
     * 
     * @var string
     * @since 0.0.0
     */
    const CAPABILITY_TYPE = '<custom capability_type specified>';

In the constructor - parent::__construct args array:

                'capability_type' => self::CAPABILITY_TYPE,

In the hooks method:

        add_action( 'admin_init', array( $this, 'add_caps' ) );

And the add_caps method:

      /**
     * Add capabilities for our custom capability type
     * 
     * @since 0.0.0
     * @return void
     */
    public function add_caps() {
        Helper::add_caps( self::CAPABILITY_TYPE );
    }

I don't remember if the generator has an option for adding a custom capability_type in the args for the CPT, but if not that could be added and this code generated if a custom capability_type is selected.

This would give the user a starting point for their custom capabilities.

bignall avatar Oct 01 '16 15:10 bignall

Well since I wrote this issue yesterday I learned a few things (I'm a newbie at wordpress development so please forgive my ignorance :). First I learned that the reason I hadn't seen this come up before was because I was using a multi-site installation and I was on a super admin account which automatically has all capabilities and I hadn't yet tested with any other accounts. Second, I learned that since assigning capabilities to roles is persistent it's better to do it during plugin activation and remove them during deactivation. So I'm modifying my suggestion with the following:

In the Helper class add a remove_caps method:

    /**
     * Removes all capabilities for a capability type to a role
     * 
     * @param string|array $cap_type the capability array allows for specifying 
     *      singular and plural, otherwise plural is constructed by adding an 's' 
     *      to the string
     * @param string $role_name the role name (default: administrator)
     * @return WP_Role
     */
    static function remove_caps( $cap_type, $role_name = 'administrator' ) {
        $role = get_role( $role_name );
        if ( is_array( $cap_type ) ) {
            $singular = $cap_type[0];
            $plural = $cap_type[1];
        } else {
            $singular = $cap_type;
            $plural = $cap_type . 's';
        }

        $role->remove_cap( 'edit_' . $singular );
        $role->remove_cap( 'edit_' . $plural );
        $role->remove_cap( 'edit_other_' . $plural );
        $role->remove_cap( 'publish_' . $plural );
        $role->remove_cap( 'read_' . $singular );
        $role->remove_cap( 'read_private_' . $plural );
        $role->remove_cap( 'delete_' . $singular );

        return $role;
    }

In the main plugin class _activate function add:

        <Custom post type class name>::activate();

In the _deactivate function:

             <Custom post type class name>::deactivate();

In the custom post type class:

    /**
     * Capability type
     * 
     * @var string
     * @since 0.0.0
     */
    const CAPABILITY_TYPE = '<capability type specified>';

    /**
     * Things to do on plugin activation
     * 
     * @since 0.0.0
     * @return void
     */
    static function activate() {
        self::add_caps();
    }

    /**
     * Things to do on plugin deactivation
     * 
     * @since 0.0.0
     * @return void
     */
    static function deactivate() {
        self::remove_caps();
    }

    /**
     * Add capabilities for our custom capability type
     * 
     * @since 0.0.0
     * @return void
     */
    static function add_caps() {
        Helper::add_caps( self::CAPABILITY_TYPE );
    }

    /**
     * Remove capabilities for our custom capability type
     * 
     * @since 0.0.0
     * @return void
     */
    static function remove_caps() {
        Helper::remove_caps( self::CAPABILITY_TYPE );
    }

And finally, to fill out the testing in tests/test-helper.php

class <prefix>_Helper_Test extends WP_UnitTestCase {

    function test_sample() {
        // replace this with some actual testing code
        $this->assertTrue( true );
    }

    function test_class_exists() {
        $this->assertTrue( class_exists( 'Helper') );
    }

    /**
     * Test Helper::add_caps
     */
    function test_add_caps() {
        Helper::add_caps('test');
        $role = get_role('administrator');
        $this->assertTrue( $role->has_cap('edit_test') );
        $this->assertTrue( $role->has_cap('edit_tests') );
        $this->assertTrue( $role->has_cap('edit_other_tests') );
        $this->assertTrue( $role->has_cap('publish_tests') );
        $this->assertTrue( $role->has_cap('read_test') );
        $this->assertTrue( $role->has_cap('read_private_tests') );
        $this->assertTrue( $role->has_cap('delete_test') );
    }

    function test_add_caps_role() {
        Helper::add_caps('test', 'author');
        $role = get_role('author');
        $this->assertTrue( $role->has_cap('edit_test') );
        $this->assertTrue( $role->has_cap('edit_tests') );
        $this->assertTrue( $role->has_cap('edit_other_tests') );
        $this->assertTrue( $role->has_cap('publish_tests') );
        $this->assertTrue( $role->has_cap('read_test') );
        $this->assertTrue( $role->has_cap('read_private_tests') );
        $this->assertTrue( $role->has_cap('delete_test') );
    }

    function test_add_caps_plural() {
        Helper::add_caps( array('test', 'testies') );
        $role = get_role('administrator');
        $this->assertTrue( $role->has_cap('edit_test') );
        $this->assertTrue( $role->has_cap('edit_testies') );
        $this->assertTrue( $role->has_cap('edit_other_testies') );
        $this->assertTrue( $role->has_cap('publish_testies') );
        $this->assertTrue( $role->has_cap('read_test') );
        $this->assertTrue( $role->has_cap('read_private_testies') );
        $this->assertTrue( $role->has_cap('delete_test') );
    }

    /**
     * Test Helper::remove_caps
     */
    function test_remove_caps() {
        Helper::add_caps('test');
        Helper::remove_caps('test');
        $role = get_role('administrator');
        $this->assertFalse( $role->has_cap('edit_test') );
        $this->assertFalse( $role->has_cap('edit_tests') );
        $this->assertFalse( $role->has_cap('edit_other_tests') );
        $this->assertFalse( $role->has_cap('publish_tests') );
        $this->assertFalse( $role->has_cap('read_test') );
        $this->assertFalse( $role->has_cap('read_private_tests') );
        $this->assertFalse( $role->has_cap('delete_test') );
    }

    function test_remove_caps_role() {
        Helper::add_caps('test', 'author' );
        Helper::remove_caps('test', 'author');
        $role = get_role('author');
        $this->assertFalse( $role->has_cap('edit_test') );
        $this->assertFalse( $role->has_cap('edit_tests') );
        $this->assertFalse( $role->has_cap('edit_other_tests') );
        $this->assertFalse( $role->has_cap('publish_tests') );
        $this->assertFalse( $role->has_cap('read_test') );
        $this->assertFalse( $role->has_cap('read_private_tests') );
        $this->assertFalse( $role->has_cap('delete_test') );
    }

    function test_remove_caps_plural() {
        Helper::add_caps( array('test', 'testies') );
        Helper::remove_caps( array('test', 'testies') );
        $role = get_role('administrator');
        $this->assertFalse( $role->has_cap('edit_test') );
        $this->assertFalse( $role->has_cap('edit_testies') );
        $this->assertFalse( $role->has_cap('edit_other_testies') );
        $this->assertFalse( $role->has_cap('publish_testies') );
        $this->assertFalse( $role->has_cap('read_test') );
        $this->assertFalse( $role->has_cap('read_private_testies') );
        $this->assertFalse( $role->has_cap('delete_test') );
    }

}

There are, of course, other possible implementations such as just putting the add_caps and remove_caps calls directly in the _activate/_deactivate plugin functions, but this is the one I chose to keep the capabilities with the post type that was the reason for adding them.

bignall avatar Oct 03 '16 01:10 bignall