Capacidades de usuario para custom post types WordPress
Cuándo se registra un tipo de post personalizado (utilizaré en adelante CPT para abreviar), este se asocia automáticamente con las capacidades predeterminadas para el tipo de post estándar. Esto se debe a que el parámetro capability_type
tiene post
como valor predeterminado en la función register_post_type()
:
add_action( 'init', 'cyb_register_book_cpt' );
function cyb_register_book_cpt() {
$args = [
'public' => true,
'label' => __( 'Books', 'cyb-textdomain' ),
// "post" es el valor predeterminado, así que
// no es necesario si es lo que queremos.
'capability_type' => 'post'
];
register_post_type( 'book', $args );
}
Cualquier usuario tendrá acceso a publicar, actualizar o borrar el CPT de acuerdo a los mismos roles y capacidades que el tipo de post estándar. Por ejemplo, un «author» podrá publicar, editar y borrar posts propios; un editor podrá además gestionar posts de otros autores, revisar posts pendientes de colaboradores y gestionar posts privados y futuros.
Si queremos asignar las mismas capacidades asociadas al tipo de post page
, bastaría con establecer capablitity_type
igual a page
. Y lo mismo podemos hacer para asignar capacidades personalizadas. Por ejemplo, para asignar capacidades personalizadas tipo book
:
add_action( 'init', 'cyb_register_book_cpt' );
function cyb_register_book_cpt() {
$args = [
'public' => true,
'label' => __( 'Books', 'cyb-textdomain' ),
'capability_type' => 'book'
];
register_post_type( 'book', $args );
}
Pero, a diferencia de la capacidad tipo post
o page
, el tipo de capacidad book
no existe. Tenemos que definir y mapear las capacidades de este «tipo de capacidad» para que WordPress las entienda. Y eso es lo que vamos a aprender en este tutorial.
capability_type, capabilities y map_meta_cap
Estos tres argumentos para la función register_post_type()
son los que generan las capacidades de usuario para los tipos de posts. Básicamente, esto es lo que hacen:
- capability_type
- (string o array) (opcional) Es un string que se utiliza para construir las capacidades de lectura (
read
), edición (edit
), publicación (publish
) y borrado (delete
). El valor predeterminado espost
y se construyen las capacidadesread_post
,edit_post
, etc. Si establecemos otro valor, por ejemplo,book
, las capacidades construidas por WordPress seránread_book
,edit_book
,publish_books
, …. - Fíjate que
capability_type
esbook
pero hay capacidades que utilizanbooks
, en plural. Las
adicional es añadida automáticamente por WordPress. Si el plural construido de esta forma es erróneo, puedes utilizar un array para especificar el singular y plural que se deben utilizar. Por ejemplo: $args = [ 'capability_type' => [ 'automovil', 'automoviles' ]; // si utilizamos esto: // 'capability_type' => 'automovil', // WordPress utilizaría 'automovils' como plural ];
- De esta forma se construyen 7 capacidades (fíjate, una vez más, en los sufijos
_book
y_books
): 'capabilities' => [ 'edit_post' => 'edit_book', 'read_post' => 'read_book', 'delete_post' => 'delete_book', 'edit_posts' => 'edit_books', 'edit_others_posts' => 'edit_others_books', 'publish_posts' => 'publish_books', 'read_private_posts' => 'read_private_books', // ATENCIÓN aqui. Fíjate que create_posts se hace igual // que edit_books. Si quieres separar ambas capacidades // hay que hacerlo manualmente en el argumento capabilities 'create_posts' => 'edit_books', ];
Estas capacidades construidas automáticamente serán las utilizadas por WordPress para otorgar o no acceso a los usuarios a las acciones asociadas, excepto si se establecen otras capacidades en el parámetro
capabilities
.- capabilities
- (array) (opcional) Si no se establece nada aquí, se utilizan las capacidades construidas por WordPress descritas anteriormente. El array acepta las mismas 7 claves que el array que vimos antes:
// Estas capacidades no serían necesarias establecerlas ya // que serían iguales a las generadas automáticamente // por WordPress para capability_type => 'book' 'capabilities' => [ 'edit_post' => 'edit_book', 'read_post' => 'read_book', 'delete_post' => 'delete_book', 'edit_posts' => 'edit_books', 'edit_others_posts' => 'edit_others_books', 'publish_posts' => 'publish_books', 'read_private_posts' => 'read_private_books', 'create_posts' => 'edit_books', ];
Por poner un ejemplo, podríamos querer que la capacidad
edit_others_posts
fueramanage_stock
:'capabilities' => [ 'edit_others_posts' => 'manage_stock', ];
En pocas palabras, si queremos capacidades personalizadas para nuestro tipo de post personalizado, las definimos en el argumento
capabilities
.A tener en cuenta
- Las 3 capacidades en singular,
read_post
,edit_post
ydelete_post
, se conocen como meta capabilities y nunca deben asignarse directamente a un usuario o a un rol. Si un usuario puede «leer un post», necesariamente tenemos que saber de que post se trata, ¿no?. - WordPress utiliza estas capacidades post a post individualmente. Por ejemplo,
current_user_can( 'edit_post', 854 )
comprueba si el usuario actual puede editar el post854
, lo cual será cierto si tiene la capacidadedit_posts
y es el autor del post 854 o si tiene la capacidadedit_others_posts
. - Las otras 4 de las 7 capacidades mencionadas se conocen como capacidades primitivas y son:
edit_posts
,edit_others_posts
,publish_posts
yread_private_posts
. - Esto puede ser un poco lioso, de momento quedaros con dos cosas claras: las capacidades meta se comprueban post a post y se mapean a sus correspondientes capacidades primitivas como vimos un par de párrafos arriba;
current_user_can( 'edit_post', 854 )
era mapeado finalmente aedit_posts
oedit_others_posts
. Las capacidades primitivas se pueden asignar directamente a usuarios y a roles, las capacidades meta no. - map_meta_cap
- (bool) (opcional). Predeterminado
null
. Si se establece entrue
, WordPress realiza el mapeo de capacidades meta a capacidades primitivas que mencionábamos antes utilizando la funciónmap_meta_cap()
. Esta función utiliza otras 8 capacidades primitivas. - En total, hay 15 capacidades. 3 meta + 7 primitivas, más otras 8 primitivas generadas en
map_meta_cap()
: [cap] => stdClass Object ( // Capacidades meta [edit_post] => "edit_{$capability_type}" [read_post] => "read_{$capability_type}" [delete_post] => "delete_{$capability_type}" // Capacidades primitivas utilizadas fuera de map_meta_cap(): [edit_posts] => "edit_{$capability_type}s" [edit_others_posts] => "edit_others_{$capability_type}s" [publish_posts] => "publish_{$capability_type}s" [read_private_posts] => "read_private_{$capability_type}s" // Capacidades primitivas utilizandas en map_meta_cap(): [read] => "read", [delete_posts] => "delete_{$capability_type}s" [delete_private_posts] => "delete_private_{$capability_type}s" [delete_published_posts] => "delete_published_{$capability_type}s" [delete_others_posts] => "delete_others_{$capability_type}s" [edit_private_posts] => "edit_private_{$capability_type}s" [edit_published_posts] => "edit_published_{$capability_type}s" [create_posts] => "edit_{$capability_type}s" )
Si el argumento
map_meta_cap
esfalse
onull
, tenemos que definir una función propia para mapear las capaciades meta hasta capacidades primitivas. Para ello se utiliza el filtromap_meta_cap
:add_filter( 'map_meta_cap', 'cyb_map_meta_cap', 10, 4 ); function cyb_map_meta_cap( $caps, $cap, $user_id, $args ) { // Si es una capacidad meta tipo "book", // obtener el post y el objeto del post type // que se utilizará más adelante if ( 'edit_book' == $cap || 'delete_book' == $cap || 'read_book' == $cap ) { $post = get_post( $args[0] ); $post_type = get_post_type_object( $post->post_type ); // vaciar $caps $caps = array(); } // Si se va a editar un "book", asignar las capacidades correspondientes if ( 'edit_book' == $cap ) { if ( $user_id == $post->post_author ) { // Si el usuario es el autor del post, // comprobar si el usuario tiene la capaciad edit_posts $caps[] = $post_type->cap->edit_posts; } else { // Si el usuario no es el autor del post, // comprobar si el usuario tiene la capacidad edit_others_posts $caps[] = $post_type->cap->edit_others_posts; } } // Si se va a borrar un book if ( 'delete_book' == $cap ) { if ( $user_id == $post->post_author ) { $caps[] = $post_type->cap->delete_posts; } else { $caps[] = $post_type->cap->delete_others_posts; } } // Si se va a leer un book privado if ( 'read_book' == $cap ) { if ( 'private' != $post->post_status ) { $caps[] = 'read'; } elseif ( $user_id == $post->post_author ) { $caps[] = 'read'; } else { $caps[] = $post_type->cap->read_private_posts; } } // Devolver las capacidades que debe tener el usuario return $caps; }
Esto del mapeo de capacidades meta puede parecer muy engorroso pero permite que no tengamos que comprobar cosntantemente quien es el usuario y que capacidades tiene y cual es el post y cuales es su estado. Ya lo hace la función de mapeo y así nos podemos centrar en comprobar capacidades.
La verdad es que la mayoría de la mayoría de veces querreis utilizar
map_meta_cap => true
y dejar que WordPress que se encargue de ello. Las necesidades tienen que ser muy particulares para querer encargarse uno mismo del mapeo.
Asignar las capacidades a roles de usuario
Bien, ya tenemos un CPT y las capacidades personalizadas asociadas. Ya solo nos queda asignar estas capacidades a los roles de usuario deseados. Esta es la parte más sencilla. Basta utilizar el método WP_Role::add_cap
.
Hay que tener en cuenta que añadir una capacidad a un rol de usuario guarda la información en la base de datos, por lo que es necesario realizarlo solo una vez para evitar operaciones innecesarias. Generalmente se hace durante la activación del plugin. Igualmente, tendremos que eliminar las capacidades añadidas durante la desactivación del plugin.
Ejemplo
Vamos a registrar el tipo de post book
con capacidad tipo book
y vamos a permitir que los usuarios de rol contributor
puedan crear, editar y borrar books, aunque no publicarlos, ni editarlos o borrarlos si ya están publicados:
add_action( 'init', 'cyb_register_book_cpt' );
function cyb_register_book_cpt() {
$args = [
'public' => true,
'label' => __( 'Books', 'cyb-textdomain' ),
'capability_type' => 'book',
'map_meta_cap' => true
];
register_post_type( 'book', $args );
}
// Asignar las capacidades adecuadas a cada role
// durante la activación del plugin
register_activation_hook( __FILE__, function () {
$contributor = get_role('contributor');
$contributor_caps = [
'delete_books',
// create_books es igual a edit_books de forma predeterminada
'edit_books',
];
foreach( $contributor_caps as $cap ) {
$contributor->add_cap( $cap );
}
$author = get_role( 'author' );
$author_caps = [
'delete_books',
'delete_published_books',
'edit_books',
'edit_published_books',
'publish_books',
];
foreach( $author_caps as $cap ) {
$author->add_cap( $cap );
}
$editor = get_role( 'editor' );
$administrator = get_role( 'administrator' );
$editor_and_admin_caps = [
'delete_books',
'delete_others_books',
'delete_private_books',
'delete_published_books',
'edit_books',
'edit_others_books',
'edit_private_books',
'edit_published_books',
'publish_books',
'read_private_books'
];
foreach( $editor_and_admin_caps as $cap ) {
$editor->add_cap( $cap );
$administrator->add_cap( $cap );
}
} );
// Al desactivar el plugin, eliminar las capacidades
// que se asignaron durante la activación
register_deactivation_hook( __FILE__, function () {
$contributor = get_role( 'contributor' );
$contributor_caps = [
'delete_books',
'edit_books',
];
foreach( $contributor_caps as $cap ) {
$contributor->remove_cap( $cap );
}
$author = get_role( 'author' );
$author_caps = [
'delete_books',
'delete_published_books',
'edit_books',
'edit_published_books',
'publish_books',
];
foreach( $author_caps as $cap ) {
$author->remove_cap( $cap );
}
$editor = get_role( 'editor' );
$administrator = get_role( 'administrator' );
$editor_and_admin_caps = [
'delete_books',
'delete_others_books',
'delete_private_books',
'delete_published_books',
'edit_books',
'edit_others_books',
'edit_private_books',
'edit_published_books',
'publish_books',
'read_private_books'
];
foreach( $editor_and_admin_caps as $cap ) {
$editor->remove_cap( $cap );
$administrator->remove_cap( $cap );
}
} );
Como véis, la implementación parece bastante más sencilla que toda la teoría. Pero dedicadle un poco a entender también la teoría, es muy importante saber exactamente lo que se está haciendo, recordad que estamos trabajando con permisos de usuario.