(Edit: going for the "Student" badge, and wondering if anyone out there could up vote this question?)
(Edit: going for the "Student" badge, and wondering if anyone out there could up vote this question?)
// 1.) Works as expected if user has every custom capability add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => TRUE, // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } // 2.) Same as #1 with the exception that access to 'post-new.php' when "Add New" button is clicked is prohibited add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => 'my-menu-item', // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } add_action('admin_menu', function() { if(current_user_can('special-subscriber') ) { add_menu_page( 'CPT in title bar', 'CPT in menu', 'edit_subs', 'my-menu-item', '' ); } } // 3.) Breaks the same as #2 add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => FALSE, // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } add_action('admin_menu', function() { global $submenu; if(current_user_can('special-subscriber') ) { add_menu_page( 'CPT in title bar', 'CPT in menu', 'edit_subs', 'my-menu-item' ); add_submenu_page( 'my-menu-item', get_post_type_object('sub-cpt')->label, get_post_type_object('sub-cpt')->label, 'edit_subs', 'my-menu-item-sub' ); // Change link $url = 'edit.php?post_type=sub-cpt'; $submenu['my-menu-item'][1][2] = admin_url($url); // Set URL to view CPT unset($submenu['my-menu-item'][0]); } // Remove WP generated menu item });
// 1.) Works as expected if user has every custom capability add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => TRUE, // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } // 2.) Same as #1 with the exception that access to 'post-new.php' when "Add New" button is clicked is prohibited add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => 'my-menu-item', // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } add_action('admin_menu', function() { if(current_user_can('special-subscriber') ) { add_menu_page( 'CPT in title bar', 'CPT in menu', 'edit_subs', 'my-menu-item', '' ); } } // 3.) Breaks the same as #2 add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => FALSE, // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } add_action('admin_menu', function() { global $submenu; if(current_user_can('special-subscriber') ) { add_menu_page( 'CPT in title bar', 'CPT in menu', 'edit_subs', 'my-menu-item' ); add_submenu_page( 'my-menu-item', get_post_type_object('sub-cpt')->label, get_post_type_object('sub-cpt')->label, 'edit_subs', 'my-menu-item-sub' ); // Change link $url = 'edit.php?post_type=sub-cpt'; $submenu['my-menu-item'][1][2] = admin_url($url); unset($submenu['my-menu-item'][0]); } });
// 1.) Works as expected if user has every custom capability add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => TRUE, // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } // 2.) Same as #1 with the exception that access to 'post-new.php' when "Add New" button is clicked is prohibited add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => 'my-menu-item', // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } add_action('admin_menu', function() { add_menu_page( 'CPT in title bar', 'CPT in menu', 'edit_subs', 'my-menu-item', '' ); } // 3.) Breaks the same as #2 add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => FALSE, // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } add_action('admin_menu', function() { global $submenu; add_menu_page( 'CPT in title bar', 'CPT in menu', 'edit_subs', 'my-menu-item' ); add_submenu_page( 'my-menu-item', get_post_type_object('sub-cpt')->label, get_post_type_object('sub-cpt')->label, 'edit_subs', 'my-menu-item-sub' ); // Change link $url = 'edit.php?post_type=sub-cpt'; $submenu['my-menu-item'][1][2] = admin_url($url); // Set URL to view CPT unset($submenu['my-menu-item'][0]); // Remove WP generated menu item });
How to allow only the editing/creation"Add New" capability of specific CPT when links to its UI are placed as a submenu?
I have two custom post types both fully accessible to Administrators. I have Subscribers, some having access to one of these CPT's which is a "child" of the first and stores it's parent's ID in its _adm_id
metadata. These "special" Subscribers have access to the parent CPT admin table so they can click a link to create child CPT posts of parents with a special status. Next, the Subscriber is allowed to edit child posts (both its own and those created by others) but only if it is of a particular custom post status. Lastly, special Subscribers are not allowed to delete posts (or edit deleted posts), not even their own.
Here's what I've got (working code) ...
I have two custom post types both fully accessible to Administrators. I have Subscribers, some having access to one of these CPT's which is a "child" of the first and stores it's parent's ID in its _adm_id
metadata. These "special" Subscribers have access to the parent CPT admin table so they can click a link to create child CPT posts of parents with a special status. Next, the Subscriber is allowed to edit child posts (both its own and those created by others) but only if it is of a particular custom post status. Lastly, special Subscribers are not allowed to delete posts (or edit deleted posts), not even their own.
Here's what I've got (working code) ...
// Setup custom post types and statuses
add_action('init', function() {
// Custom Post Types
register_post_type('adm-cpt', array(
'label' => __('Admin Only CPT'),
'show_ui' => TRUE,
'show_in_menu' => 'my-menu-item',
'show_in_admin_bar' => FALSE,
'capability_type' => 'adm',
'map_meta_cap' => TRUE,
'capabilities' => array(
'create_posts' => 'administrator', // Only admin can create, not special Subscribers
),
));
register_post_type('sub-cpt', array(
'label' => __('Subscriber/Admin CPT'),
'show_ui' => TRUE,
'show_in_menu' => 'my-menu-item',
'show_in_admin_bar' => FALSE,
'capability_type' => 'sub',
'map_meta_cap' => TRUE,
));
// Custom Post Statuses
foreach(array(
'adm-childable' => __('Can Create Children'),
'sub-editable' => __('Any Subscriber Can Edit'),
) as $slug => $label) {
register_post_status($slug, array(
'label' => _x($label, 'post'),
'label_count' => _n_noop($label .' <span class="count">(%s)</span>', $label .' <span class="count">(%s)</span>' ),
'public' => TRUE,
));
}
});
// Setup parent page in admin menu
add_action('admin_menu', function() {
// Add menu item
if(current_user_can('administrator')
|| current_user_can('special-subscriber')
) {
// Admin menu header
add_menu_page(
NULL,
'CPTs',
'exist',
'my-menu-item',
''
);
}
});
// Set up role
add_action('wp_roles_init', function($wp_roles){
// Prepare
$role = 'special-subscriber';
$caps = array(
'delete_subs' => FALSE, // No trashing ...
'delete_others_subs' => FALSE,
'delete_published_subs' => FALSE,
'delete_private_subs' => FALSE,
'edit_published_subs' => FALSE, // And no editing published/private posts ...
'edit_private_subs' => FALSE,
'edit_adms' => TRUE, // Allow viewing of adm-cpt table
'edit_posts' => TRUE, // WARNING: Here's the permission that is causing the problems!
);
$name = __('"Special" Subscriber');
// Update role in database, if needed
if($wp_roles->get_role($role) === NULL
|| $wp_roles->get_role($role)->capabilities != $caps
|| $wp_roles->roles[$role]['name'] !== $name
) {
$wp_roles->remove_role($role);
$wp_roles->add_role($role, $name, $caps);
}
});
// Dynamicly set capabilities
add_action('user_has_cap', function($allcaps, $caps, $args, $user) {
foreach($caps as $cap) {
$perm = substr($cap, 0, strrpos($cap, '_'));
$type = substr($cap, strlen($perm)+1);
if(in_array($type, array('adm', 'adms')) && in_array('administrator', $user->roles)
|| in_array($type, array('sub', 'subs')) && !empty(array_intersect(array('administrator', 'special-subscriber'), $user->roles))
) {
// Check Subscriber if post is editable
if(in_array($cap, array('edit_subs', 'edit_others_subs'))
&& in_array('special-subscriber', $user->roles)
&& !in_array('administrator', $user->roles)
&& !empty($args[2])
&& ( !in_array(get_post_status($args[2]), array('sub-editable'))
&& !in_array($_REQUEST['original_post_status'], array('sub-editable', 'auto-draft')) // Creating
|| get_post_status(get_post_meta($args[2], '_adm_id', TRUE)) === 'trash'
)
) {
$allcaps[$cap] = FALSE;
}
// Add the cap
if(!isset($allcaps[$cap])
) {
$allcaps[$cap] = TRUE; // All the _adm and _sub capabilities are made available.
}
}
}
return $allcaps;
}, 10, 4);
// Add stuff to force proper navigation
add_action('post_row_actions', function($actions, $post) {
// Add link to adm-cpt table entries to create child
if(get_post_type($post) === 'adm-cpt'
&& get_post_status($post) === 'adm-childable'
&& current_user_can('edit_subs')
) {
$lbl = __('New '). get_post_type_object('sub-cpt')->labels->name;
$actions['adm-cpt-create-sub-cpt'] = sprintf(
'<a href="%s" aria-label="%s">%s</a>',
admin_url('post-new.php?post_type=sub-cpt&adm_id='. $post->ID),
esc_attr('“'. $lbl .'”'),
$lbl
);
}
// Return
return $actions;
}, 10, 2);
// Modify publish metabox
add_action('post_submitbox_misc_actions', function($post) {
$arr = array();
switch(get_post_type($post)) {
case 'adm-cpt':
$arr = array('adm-childable');
break;
case 'sub-cpt':
$arr = array('sub-editable');
break;
default:
return;
}
// Check that parent exists -- Should be in an init hook, but it's prettier here.
if($_REQUEST['post_type'] === 'sub-cpt'
&& (empty($_REQUEST['adm_id']) || get_post_type($_REQUEST['adm_id']) !== 'adm-cpt')
&& (empty($post->_adm_id) || get_post_type($post->_adm_id) !== 'adm-cpt')
){
?><script>window.document.location.replace("<?= admin_url('edit.php?post_type=adm-cpt') ?>")</script><?php
return;
}
// Add custom post statuses
?><input type='hidden' name='adm_id' value='<?= $_REQUEST['adm_id'] ?>'><?php
if(count($arr)) {
?><script>
<?php foreach($arr as $k) { $obj = get_post_status_object($k); ?>
jQuery("select#post_status").append("<option value=\"<?= $k ?>\"><?= $obj->label ?></option>");
<?php if(get_post_status($post) == $k) { ?>
jQuery("#post-status-display").text("<?= $obj->label ?>");
jQuery("select#post_status").val("<?= $k ?>");
<?php } ?>
<?php } ?>
</script><?php
}
// Display parent -- Informational
if(!empty($_REQUEST['adm_id'])
|| !empty($post->_adm_id)
) {
$parent_id = $post->_adm_id;
if(!$parent_id) $parent_id = $_REQUEST['adm_id'];
?><div class="misc-pub-section misc-pub-adm-cpt">Parent: <span id="post-status-display"><?= get_the_title($parent_id) ?></span></div><?php
}
});
// Save parent ID
add_action('save_post_sub-cpt', function($post_id, $post, $update) {
// Ensure we continue only id a new child is created
if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE
|| get_post_type($post_id) !== 'sub-cpt'
|| empty($_REQUEST['adm_id'])
|| get_post_type($_REQUEST['adm_id']) !== 'adm-cpt'
) return;
// Set parent ID
update_post_meta($post_id, '_adm_id', $_REQUEST['adm_id']);
}, 10, 3);
// Navigation when changed to uneditable
add_action('load-post.php', function(){
if(!empty($_REQUEST['post'])
&& get_post_type($_REQUEST['post']) === 'sub-cpt'
&& !current_user_can('edit_subs', $_REQUEST['post'])
) {
delete_post_meta($_REQUEST['post'], '_edit_lock');
wp_redirect('edit.php?post_type=sub-cpt');
die();
}
});
This issue here is that the special Subscriber is able to edit regular Posts and Comments. I understand this comes from the edit_posts
capability, and that capability allows for the editing/creation of all post types. However, removing it prevents special Subscribers from being able to create sub-cpt
posts, and granting edit_subs
does not solve the problem. Neither does setting the capabilities->create_post=special-subscriber
when registering the child CPT. I've been able to limit the ability of Subscribers from being able to create adm-cpt
posts by defining the capabilities
parameter when registering the post type. But I don't want special Subscribers to be able to edit/create any other posts other than those of the sub-cpt
type, and I can't seem to figure out how.
I've found a Q&A related to the subject, but this doesn't seem to work. The CPT's are mapped to custom capabilities, they exist, and the user_has_cap
filter dynamically grants each of these capabilities. I've even tried expressly defining them in the special-subscriber
role definition. Anyways, I'm sure the change is simple--what is it?
(If you're interested, I have another capability problem. When a special Subscriber sets the child CPT post_status
to publish, the post is locked and they are forwarded to edit.php
but I want the post to unlock and for the viewer to be forwarded to edit.php?post_type=sub-cpt
just like is done in the load-post.php
hook of my code, and I can't seem to figure out how.)
UPDATE: I've isolated it down to the placement of the CPT in the menu. When the CPT is registered as showing the UI using the register_post_type
option of show_in_menu=TRUE
, everything works as expected. But, when the CPT is added as a submenu of an old-fashioned admin menu item, things break. Adding the UI and hiding it results in the same problems, along with adding a subpage and redirecting it to the UI of the CPT. Examples:
// Setup1.) custom postWorks typesas andexpected statusesif user has every custom capability add_action('init', function() { // Custom Post Types register_post_type('adm'sub-cpt', array( 'label' => __('Admin Only'Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => 'my-menu-item'TRUE, // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'adm''sub', 'map_meta_cap' => TRUE, 'capabilities' => array( 'create_posts' => 'administrator', // Only admin can create, not special Subscribers ), )); } // 2.) Same as #1 with the exception that access to 'post-new.php' when "Add New" button is clicked is prohibited add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => 'my-menu-item', // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); // Custom Post Statuses foreach(array( 'adm-childable' => __('Can Create Children'), 'sub-editable' => __('Any Subscriber Can Edit'), ) as $slug => $label) { register_post_status($slug, array( 'label' => _x($label, 'post'), 'label_count' => _n_noop($label .' <span class="count">(%s)</span>', $label .' <span class="count">(%s)</span>' ), 'public' => TRUE, )); } }); // Setup parent page in admin menu add_action('admin_menu', function() { // Add menu item if(current_user_can('administrator') || current_user_can('special-subscriber') ) { // Admin menu header add_menu_page( NULL'CPT in title bar', 'CPTs''CPT in menu', 'exist''edit_subs', 'my-menu-item', '' ); } }); // Set up role add_action('wp_roles_init', function($wp_roles){ // Prepare $role = 'special-subscriber'; $caps = array( 'delete_subs' => FALSE, // No trashing ... 'delete_others_subs' => FALSE, 'delete_published_subs' => FALSE, 'delete_private_subs' => FALSE, 'edit_published_subs' => FALSE, // And no editing published/private posts ... 'edit_private_subs' => FALSE, 'edit_adms' => TRUE, // Allow viewing of adm-cpt table 'edit_posts' => TRUE, // WARNING: Here's the permission that is causing the problems! ); $name = __('"Special" Subscriber'); // Update role in database, if needed if($wp_roles->get_role($role) === NULL || $wp_roles->get_role($role)->capabilities != $caps || $wp_roles->roles[$role]['name'] !== $name ) { $wp_roles->remove_role($role); $wp_roles->add_role($role, $name, $caps); } }); // Dynamicly set capabilities add_action('user_has_cap', function($allcaps, $caps, $args, $user) { foreach($caps as $cap) { $perm = substr($cap, 0, strrpos($cap, '_')); $type = substr($cap, strlen($perm)+1); if(in_array($type, array('adm', 'adms')) && in_array('administrator', $user->roles) || in_array($type, array('sub', 'subs')) && !empty(array_intersect(array('administrator', 'special-subscriber'), $user->roles)) ) { // Check Subscriber if post is editable if(in_array($cap, array('edit_subs', 'edit_others_subs')) && in_array('special-subscriber', $user->roles) && !in_array('administrator', $user->roles) && !empty($args[2]) && ( !in_array(get_post_status($args[2]), array('sub-editable')) && !in_array($_REQUEST['original_post_status'], array('sub-editable', 'auto-draft')) // Creating || get_post_status(get_post_meta($args[2], '_adm_id', TRUE)) === 'trash' ) ) { $allcaps[$cap] = FALSE; } // Add the cap if(!isset($allcaps[$cap]) ) { $allcaps[$cap] = TRUE; // All the _adm and _sub capabilities are made available. } } } return $allcaps; }, 10, 4); // Add3.) stuff toBreaks forcethe propersame navigationas #2 add_action('post_row_actions''init', function($actions) { register_post_type('sub-cpt', $postarray( 'label' => __('Subscriber/Admin CPT'), { 'show_ui' => TRUE, 'show_in_menu' => FALSE, // AddTake linknote toof adm-cptthis table entries to create child 'show_in_admin_bar' => FALSE, if(get_post_type($post) === 'adm-cpt' 'capability_type' => 'sub', && get_post_status($post 'map_meta_cap' => TRUE, )); } add_action('admin_menu', ===function() 'adm-childable'{ &&global $submenu; if(current_user_can('edit_subs''special-subscriber') ) { $lbl = __('New '). get_post_type_objectadd_menu_page('sub-cpt')->labels->name; $actions['adm-cpt-create-sub-cpt'] = sprintf( 'CPT '<ain href="%s"title aria-label="%s">%s</a>'bar', admin_url('post-new.php?post_type=sub-cpt&adm_id='.'CPT $post->ID)in menu', esc_attr('“'. $lbl .'”')'edit_subs', $lbl'my-menu-item' ); } // Return return $actions; }, 10, 2); // Modify publish metabox add_action('post_submitbox_misc_actions', function($post) { $arr = array(); switch(get_post_type($post)) { case 'adm-cpt': $arr = array('adm-childable'); break; case 'sub-cpt': $arr = arrayadd_submenu_page('sub-editable'); break; default: return; } // Check that parent exists 'my-menu- Should be in an init hookitem', but it's prettier here. if($_REQUEST['post_type'] === 'sub-cpt' && (empty($_REQUEST['adm_id']) || get_post_typeget_post_type_object($_REQUEST['adm_id']) !== 'adm'sub-cpt') && (empty($post->_adm_id) || get_post_type($post->_adm_id) !== 'adm-cpt')>label, ){ ?><script>window.document.location.replace("<?= admin_urlget_post_type_object('edit.php?post_type=adm'sub-cpt') ?>")</script><?php return; } // Add custom post statuses ?><input type='hidden' name='adm_id' value='<?= $_REQUEST['adm_id'] ?>'><?php if(count($arr)) { ?><script> <?php foreach($arr as $k) { $obj = get_post_status_object($k); ?> jQuery("select#post_status").append("<option value=\"<?= $k ?>\"><?= $obj->label ?></option>");, <?php if(get_post_status($post) == $k) { ?>'edit_subs', jQuery("#post'my-statusmenu-display").text("<?= $objitem->label ?>");sub' jQuery("select#post_status").val("<?= $k ?>"); <?php } ?> <?php } ?> </script><?php } // Display parent -- Informational if(!empty($_REQUEST['adm_id']) || !empty($post->_adm_id) ) { $parent_id = $post->_adm_id; if(!$parent_id) $parent_id =Change $_REQUEST['adm_id'];link ?><div class="misc-pub-section misc-pub-adm-cpt">Parent: <span$url id="post-status-display"><?= get_the_title($parent_id) ?></span></div><?'edit.php } }); // Save parent ID add_action('save_post_sub?post_type=sub-cpt', function($post_id, $post, $update) { // Ensure we continue only id a new child is createdcpt'; if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || get_post_type($post_id) !== 'sub$submenu['my-cpt' || empty($_REQUEST['adm_id']) || get_post_type($_REQUEST['adm_id']) !== 'admmenu-cpt' ) return; // Set parent ID item'][1][2] = update_post_metaadmin_url($post_id, '_adm_id', $_REQUEST['adm_id']); }, 10, 3$url); // Navigation when changed to uneditable add_action('load-post.php', function(){ if(!empty($_REQUEST['post']) && get_post_typeunset($_REQUEST['post']) === 'sub$submenu['my-cpt' && !current_user_can('edit_subs', $_REQUEST['post']) ) { delete_post_meta($_REQUEST['post'], '_edit_lock'); wp_redirect('edit.php?post_type=submenu-cpt'); die(item'][0]); } });
This issue here is thatIf, I can get the special Subscriber is able"Add New" functionality to edit regular Posts and Comments. I understand this comes fromwork with the CPT as a subpage, I think my problem will be solved because the edit_posts
capability, and that capability allows for the editing/creation of all post types. However, removing it prevents special Subscribers from being able giving me trouble can be specifically mapped to create sub-cpt
posts, and granting edit_subs
does not solve the problem. Neither does setting the capabilities->create_post=special-subscriber
when registering the child CPT. I've been able to limit the ability of Subscribers from being able to create adm-cpt
posts by defining the capabilities
parameter when registering the post type. But I don't want special Subscribers to be able to edit/create any other posts other than those of the sub-cpt
type, and I can't seem to figure out Anyone know how.
I've found a Q&A related to the subject, butdo this doesn't seem to work. The CPT's are mapped to custom capabilities, they exist, and the user_has_cap
filter dynamically grants each of these capabilities. I've even tried expressly defining them in the special-subscriber
role definition. Anyways, I'm sure the change is simple--what is it?
(If you're interested, I have another capability problem. When a special Subscriber sets the child CPT post_status
to publish, the post is locked and they are forwarded to edit.php
but I want the post to unlock and for the viewer to be forwarded to edit.php?post_type=sub-cpt
just like is done in the load-post.php
hook of my code, and I can't seem to figure out how.)
How to allow only the editing/creation of specific CPT?
I have two custom post types both fully accessible to Administrators. I have Subscribers, some having access to one of these CPT's which is a "child" of the first and stores it's parent's ID in its _adm_id
metadata. These "special" Subscribers have access to the parent CPT admin table so they can click a link to create child CPT posts of parents with a special status. Next, the Subscriber is allowed to edit child posts (both its own and those created by others) but only if it is of a particular custom post status. Lastly, special Subscribers are not allowed to delete posts (or edit deleted posts), not even their own.
Here's what I've got (working code) ...
// Setup custom post types and statuses add_action('init', function() { // Custom Post Types register_post_type('adm-cpt', array( 'label' => __('Admin Only CPT'), 'show_ui' => TRUE, 'show_in_menu' => 'my-menu-item', 'show_in_admin_bar' => FALSE, 'capability_type' => 'adm', 'map_meta_cap' => TRUE, 'capabilities' => array( 'create_posts' => 'administrator', // Only admin can create, not special Subscribers ), )); register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => 'my-menu-item', 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); // Custom Post Statuses foreach(array( 'adm-childable' => __('Can Create Children'), 'sub-editable' => __('Any Subscriber Can Edit'), ) as $slug => $label) { register_post_status($slug, array( 'label' => _x($label, 'post'), 'label_count' => _n_noop($label .' <span class="count">(%s)</span>', $label .' <span class="count">(%s)</span>' ), 'public' => TRUE, )); } }); // Setup parent page in admin menu add_action('admin_menu', function() { // Add menu item if(current_user_can('administrator') || current_user_can('special-subscriber') ) { // Admin menu header add_menu_page( NULL, 'CPTs', 'exist', 'my-menu-item', '' ); } }); // Set up role add_action('wp_roles_init', function($wp_roles){ // Prepare $role = 'special-subscriber'; $caps = array( 'delete_subs' => FALSE, // No trashing ... 'delete_others_subs' => FALSE, 'delete_published_subs' => FALSE, 'delete_private_subs' => FALSE, 'edit_published_subs' => FALSE, // And no editing published/private posts ... 'edit_private_subs' => FALSE, 'edit_adms' => TRUE, // Allow viewing of adm-cpt table 'edit_posts' => TRUE, // WARNING: Here's the permission that is causing the problems! ); $name = __('"Special" Subscriber'); // Update role in database, if needed if($wp_roles->get_role($role) === NULL || $wp_roles->get_role($role)->capabilities != $caps || $wp_roles->roles[$role]['name'] !== $name ) { $wp_roles->remove_role($role); $wp_roles->add_role($role, $name, $caps); } }); // Dynamicly set capabilities add_action('user_has_cap', function($allcaps, $caps, $args, $user) { foreach($caps as $cap) { $perm = substr($cap, 0, strrpos($cap, '_')); $type = substr($cap, strlen($perm)+1); if(in_array($type, array('adm', 'adms')) && in_array('administrator', $user->roles) || in_array($type, array('sub', 'subs')) && !empty(array_intersect(array('administrator', 'special-subscriber'), $user->roles)) ) { // Check Subscriber if post is editable if(in_array($cap, array('edit_subs', 'edit_others_subs')) && in_array('special-subscriber', $user->roles) && !in_array('administrator', $user->roles) && !empty($args[2]) && ( !in_array(get_post_status($args[2]), array('sub-editable')) && !in_array($_REQUEST['original_post_status'], array('sub-editable', 'auto-draft')) // Creating || get_post_status(get_post_meta($args[2], '_adm_id', TRUE)) === 'trash' ) ) { $allcaps[$cap] = FALSE; } // Add the cap if(!isset($allcaps[$cap]) ) { $allcaps[$cap] = TRUE; // All the _adm and _sub capabilities are made available. } } } return $allcaps; }, 10, 4); // Add stuff to force proper navigation add_action('post_row_actions', function($actions, $post) { // Add link to adm-cpt table entries to create child if(get_post_type($post) === 'adm-cpt' && get_post_status($post) === 'adm-childable' && current_user_can('edit_subs') ) { $lbl = __('New '). get_post_type_object('sub-cpt')->labels->name; $actions['adm-cpt-create-sub-cpt'] = sprintf( '<a href="%s" aria-label="%s">%s</a>', admin_url('post-new.php?post_type=sub-cpt&adm_id='. $post->ID), esc_attr('“'. $lbl .'”'), $lbl ); } // Return return $actions; }, 10, 2); // Modify publish metabox add_action('post_submitbox_misc_actions', function($post) { $arr = array(); switch(get_post_type($post)) { case 'adm-cpt': $arr = array('adm-childable'); break; case 'sub-cpt': $arr = array('sub-editable'); break; default: return; } // Check that parent exists -- Should be in an init hook, but it's prettier here. if($_REQUEST['post_type'] === 'sub-cpt' && (empty($_REQUEST['adm_id']) || get_post_type($_REQUEST['adm_id']) !== 'adm-cpt') && (empty($post->_adm_id) || get_post_type($post->_adm_id) !== 'adm-cpt') ){ ?><script>window.document.location.replace("<?= admin_url('edit.php?post_type=adm-cpt') ?>")</script><?php return; } // Add custom post statuses ?><input type='hidden' name='adm_id' value='<?= $_REQUEST['adm_id'] ?>'><?php if(count($arr)) { ?><script> <?php foreach($arr as $k) { $obj = get_post_status_object($k); ?> jQuery("select#post_status").append("<option value=\"<?= $k ?>\"><?= $obj->label ?></option>"); <?php if(get_post_status($post) == $k) { ?> jQuery("#post-status-display").text("<?= $obj->label ?>"); jQuery("select#post_status").val("<?= $k ?>"); <?php } ?> <?php } ?> </script><?php } // Display parent -- Informational if(!empty($_REQUEST['adm_id']) || !empty($post->_adm_id) ) { $parent_id = $post->_adm_id; if(!$parent_id) $parent_id = $_REQUEST['adm_id']; ?><div class="misc-pub-section misc-pub-adm-cpt">Parent: <span id="post-status-display"><?= get_the_title($parent_id) ?></span></div><?php } }); // Save parent ID add_action('save_post_sub-cpt', function($post_id, $post, $update) { // Ensure we continue only id a new child is created if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE || get_post_type($post_id) !== 'sub-cpt' || empty($_REQUEST['adm_id']) || get_post_type($_REQUEST['adm_id']) !== 'adm-cpt' ) return; // Set parent ID update_post_meta($post_id, '_adm_id', $_REQUEST['adm_id']); }, 10, 3); // Navigation when changed to uneditable add_action('load-post.php', function(){ if(!empty($_REQUEST['post']) && get_post_type($_REQUEST['post']) === 'sub-cpt' && !current_user_can('edit_subs', $_REQUEST['post']) ) { delete_post_meta($_REQUEST['post'], '_edit_lock'); wp_redirect('edit.php?post_type=sub-cpt'); die(); } });
This issue here is that the special Subscriber is able to edit regular Posts and Comments. I understand this comes from the edit_posts
capability, and that capability allows for the editing/creation of all post types. However, removing it prevents special Subscribers from being able to create sub-cpt
posts, and granting edit_subs
does not solve the problem. Neither does setting the capabilities->create_post=special-subscriber
when registering the child CPT. I've been able to limit the ability of Subscribers from being able to create adm-cpt
posts by defining the capabilities
parameter when registering the post type. But I don't want special Subscribers to be able to edit/create any other posts other than those of the sub-cpt
type, and I can't seem to figure out how.
I've found a Q&A related to the subject, but this doesn't seem to work. The CPT's are mapped to custom capabilities, they exist, and the user_has_cap
filter dynamically grants each of these capabilities. I've even tried expressly defining them in the special-subscriber
role definition. Anyways, I'm sure the change is simple--what is it?
(If you're interested, I have another capability problem. When a special Subscriber sets the child CPT post_status
to publish, the post is locked and they are forwarded to edit.php
but I want the post to unlock and for the viewer to be forwarded to edit.php?post_type=sub-cpt
just like is done in the load-post.php
hook of my code, and I can't seem to figure out how.)
How to allow "Add New" capability of CPT when links to its UI are placed as a submenu?
I have two custom post types both fully accessible to Administrators. I have Subscribers, some having access to one of these CPT's which is a "child" of the first and stores it's parent's ID in its _adm_id
metadata. These "special" Subscribers have access to the parent CPT admin table so they can click a link to create child CPT posts of parents with a special status. Next, the Subscriber is allowed to edit child posts (both its own and those created by others) but only if it is of a particular custom post status. Lastly, special Subscribers are not allowed to delete posts (or edit deleted posts), not even their own.
Here's what I've got (working code) ...
// Setup custom post types and statuses
add_action('init', function() {
// Custom Post Types
register_post_type('adm-cpt', array(
'label' => __('Admin Only CPT'),
'show_ui' => TRUE,
'show_in_menu' => 'my-menu-item',
'show_in_admin_bar' => FALSE,
'capability_type' => 'adm',
'map_meta_cap' => TRUE,
'capabilities' => array(
'create_posts' => 'administrator', // Only admin can create, not special Subscribers
),
));
register_post_type('sub-cpt', array(
'label' => __('Subscriber/Admin CPT'),
'show_ui' => TRUE,
'show_in_menu' => 'my-menu-item',
'show_in_admin_bar' => FALSE,
'capability_type' => 'sub',
'map_meta_cap' => TRUE,
));
// Custom Post Statuses
foreach(array(
'adm-childable' => __('Can Create Children'),
'sub-editable' => __('Any Subscriber Can Edit'),
) as $slug => $label) {
register_post_status($slug, array(
'label' => _x($label, 'post'),
'label_count' => _n_noop($label .' <span class="count">(%s)</span>', $label .' <span class="count">(%s)</span>' ),
'public' => TRUE,
));
}
});
// Setup parent page in admin menu
add_action('admin_menu', function() {
// Add menu item
if(current_user_can('administrator')
|| current_user_can('special-subscriber')
) {
// Admin menu header
add_menu_page(
NULL,
'CPTs',
'exist',
'my-menu-item',
''
);
}
});
// Set up role
add_action('wp_roles_init', function($wp_roles){
// Prepare
$role = 'special-subscriber';
$caps = array(
'delete_subs' => FALSE, // No trashing ...
'delete_others_subs' => FALSE,
'delete_published_subs' => FALSE,
'delete_private_subs' => FALSE,
'edit_published_subs' => FALSE, // And no editing published/private posts ...
'edit_private_subs' => FALSE,
'edit_adms' => TRUE, // Allow viewing of adm-cpt table
'edit_posts' => TRUE, // WARNING: Here's the permission that is causing the problems!
);
$name = __('"Special" Subscriber');
// Update role in database, if needed
if($wp_roles->get_role($role) === NULL
|| $wp_roles->get_role($role)->capabilities != $caps
|| $wp_roles->roles[$role]['name'] !== $name
) {
$wp_roles->remove_role($role);
$wp_roles->add_role($role, $name, $caps);
}
});
// Dynamicly set capabilities
add_action('user_has_cap', function($allcaps, $caps, $args, $user) {
foreach($caps as $cap) {
$perm = substr($cap, 0, strrpos($cap, '_'));
$type = substr($cap, strlen($perm)+1);
if(in_array($type, array('adm', 'adms')) && in_array('administrator', $user->roles)
|| in_array($type, array('sub', 'subs')) && !empty(array_intersect(array('administrator', 'special-subscriber'), $user->roles))
) {
// Check Subscriber if post is editable
if(in_array($cap, array('edit_subs', 'edit_others_subs'))
&& in_array('special-subscriber', $user->roles)
&& !in_array('administrator', $user->roles)
&& !empty($args[2])
&& ( !in_array(get_post_status($args[2]), array('sub-editable'))
&& !in_array($_REQUEST['original_post_status'], array('sub-editable', 'auto-draft')) // Creating
|| get_post_status(get_post_meta($args[2], '_adm_id', TRUE)) === 'trash'
)
) {
$allcaps[$cap] = FALSE;
}
// Add the cap
if(!isset($allcaps[$cap])
) {
$allcaps[$cap] = TRUE; // All the _adm and _sub capabilities are made available.
}
}
}
return $allcaps;
}, 10, 4);
// Add stuff to force proper navigation
add_action('post_row_actions', function($actions, $post) {
// Add link to adm-cpt table entries to create child
if(get_post_type($post) === 'adm-cpt'
&& get_post_status($post) === 'adm-childable'
&& current_user_can('edit_subs')
) {
$lbl = __('New '). get_post_type_object('sub-cpt')->labels->name;
$actions['adm-cpt-create-sub-cpt'] = sprintf(
'<a href="%s" aria-label="%s">%s</a>',
admin_url('post-new.php?post_type=sub-cpt&adm_id='. $post->ID),
esc_attr('“'. $lbl .'”'),
$lbl
);
}
// Return
return $actions;
}, 10, 2);
// Modify publish metabox
add_action('post_submitbox_misc_actions', function($post) {
$arr = array();
switch(get_post_type($post)) {
case 'adm-cpt':
$arr = array('adm-childable');
break;
case 'sub-cpt':
$arr = array('sub-editable');
break;
default:
return;
}
// Check that parent exists -- Should be in an init hook, but it's prettier here.
if($_REQUEST['post_type'] === 'sub-cpt'
&& (empty($_REQUEST['adm_id']) || get_post_type($_REQUEST['adm_id']) !== 'adm-cpt')
&& (empty($post->_adm_id) || get_post_type($post->_adm_id) !== 'adm-cpt')
){
?><script>window.document.location.replace("<?= admin_url('edit.php?post_type=adm-cpt') ?>")</script><?php
return;
}
// Add custom post statuses
?><input type='hidden' name='adm_id' value='<?= $_REQUEST['adm_id'] ?>'><?php
if(count($arr)) {
?><script>
<?php foreach($arr as $k) { $obj = get_post_status_object($k); ?>
jQuery("select#post_status").append("<option value=\"<?= $k ?>\"><?= $obj->label ?></option>");
<?php if(get_post_status($post) == $k) { ?>
jQuery("#post-status-display").text("<?= $obj->label ?>");
jQuery("select#post_status").val("<?= $k ?>");
<?php } ?>
<?php } ?>
</script><?php
}
// Display parent -- Informational
if(!empty($_REQUEST['adm_id'])
|| !empty($post->_adm_id)
) {
$parent_id = $post->_adm_id;
if(!$parent_id) $parent_id = $_REQUEST['adm_id'];
?><div class="misc-pub-section misc-pub-adm-cpt">Parent: <span id="post-status-display"><?= get_the_title($parent_id) ?></span></div><?php
}
});
// Save parent ID
add_action('save_post_sub-cpt', function($post_id, $post, $update) {
// Ensure we continue only id a new child is created
if(defined('DOING_AUTOSAVE') && DOING_AUTOSAVE
|| get_post_type($post_id) !== 'sub-cpt'
|| empty($_REQUEST['adm_id'])
|| get_post_type($_REQUEST['adm_id']) !== 'adm-cpt'
) return;
// Set parent ID
update_post_meta($post_id, '_adm_id', $_REQUEST['adm_id']);
}, 10, 3);
// Navigation when changed to uneditable
add_action('load-post.php', function(){
if(!empty($_REQUEST['post'])
&& get_post_type($_REQUEST['post']) === 'sub-cpt'
&& !current_user_can('edit_subs', $_REQUEST['post'])
) {
delete_post_meta($_REQUEST['post'], '_edit_lock');
wp_redirect('edit.php?post_type=sub-cpt');
die();
}
});
This issue here is that the special Subscriber is able to edit regular Posts and Comments. I understand this comes from the edit_posts
capability, and that capability allows for the editing/creation of all post types. However, removing it prevents special Subscribers from being able to create sub-cpt
posts, and granting edit_subs
does not solve the problem. Neither does setting the capabilities->create_post=special-subscriber
when registering the child CPT. I've been able to limit the ability of Subscribers from being able to create adm-cpt
posts by defining the capabilities
parameter when registering the post type. But I don't want special Subscribers to be able to edit/create any other posts other than those of the sub-cpt
type, and I can't seem to figure out how.
I've found a Q&A related to the subject, but this doesn't seem to work. The CPT's are mapped to custom capabilities, they exist, and the user_has_cap
filter dynamically grants each of these capabilities. I've even tried expressly defining them in the special-subscriber
role definition. Anyways, I'm sure the change is simple--what is it?
(If you're interested, I have another capability problem. When a special Subscriber sets the child CPT post_status
to publish, the post is locked and they are forwarded to edit.php
but I want the post to unlock and for the viewer to be forwarded to edit.php?post_type=sub-cpt
just like is done in the load-post.php
hook of my code, and I can't seem to figure out how.)
UPDATE: I've isolated it down to the placement of the CPT in the menu. When the CPT is registered as showing the UI using the register_post_type
option of show_in_menu=TRUE
, everything works as expected. But, when the CPT is added as a submenu of an old-fashioned admin menu item, things break. Adding the UI and hiding it results in the same problems, along with adding a subpage and redirecting it to the UI of the CPT. Examples:
// 1.) Works as expected if user has every custom capability add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => TRUE, // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } // 2.) Same as #1 with the exception that access to 'post-new.php' when "Add New" button is clicked is prohibited add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => 'my-menu-item', // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } add_action('admin_menu', function() { if(current_user_can('special-subscriber') ) { add_menu_page( 'CPT in title bar', 'CPT in menu', 'edit_subs', 'my-menu-item', '' ); } } // 3.) Breaks the same as #2 add_action('init', function() { register_post_type('sub-cpt', array( 'label' => __('Subscriber/Admin CPT'), 'show_ui' => TRUE, 'show_in_menu' => FALSE, // Take note of this 'show_in_admin_bar' => FALSE, 'capability_type' => 'sub', 'map_meta_cap' => TRUE, )); } add_action('admin_menu', function() { global $submenu; if(current_user_can('special-subscriber') ) { add_menu_page( 'CPT in title bar', 'CPT in menu', 'edit_subs', 'my-menu-item' ); add_submenu_page( 'my-menu-item', get_post_type_object('sub-cpt')->label, get_post_type_object('sub-cpt')->label, 'edit_subs', 'my-menu-item-sub' ); // Change link $url = 'edit.php?post_type=sub-cpt'; $submenu['my-menu-item'][1][2] = admin_url($url); unset($submenu['my-menu-item'][0]); } });
If, I can get the "Add New" functionality to work with the CPT as a subpage, I think my problem will be solved because the edit_posts
capability giving me trouble can be specifically mapped to edit_subs
. Anyone know how to do this?