generator-plugin-wp
generator-plugin-wp copied to clipboard
Custom capability types for custom post types doesn't automatically give administrator capabilities
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.
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.