Contents
Introducción
Este artículo explica con todo lujo de detalles cómo crear un sistema de permisos granular en WordPress usando metacapabilities. Verás qué son las metacapabilities, cómo mapearlas a capacidades primitivas mediante el filtro map_meta_cap, cómo registrar roles y capacidades, cómo integrar comprobaciones en el REST API y en AJAX, y qué medidas de seguridad y rendimiento debes considerar. Incluyo ejemplos completos en PHP listos para insertar en tu plugin o tema.
Conceptos clave
Qué es una metacapability
En WordPress existen dos tipos de capacidades: las llamadas capabilities primitivas (por ejemplo, edit_posts, publish_posts) que representan permisos atómicos, y las metacapabilities (por ejemplo, edit_post, delete_post) que dependen del contexto del objeto (post, user, term…). Las metacapabilities se mapean a primitivas según la lógica de negocio (por ejemplo, si el usuario es el autor, permitir edit_post con la primitiva edit_posts o una capacidad específica como edit_others_posts).
Por qué usar metacapabilities
- Permiten lógica contextual (dueño, estado, grupo, meta del post).
- Fácil implementación de controles finos: un usuario X puede editar posts del tipo Y sólo si….
- Se integran con la API interna de current_user_can() y con checks automáticos de WordPress.
Fácil vs granular
Las capacidades primitivas sirven para roles simples las metacapabilities permiten reglas complejas (grupos, miembros, atributos de metadatos, expiraciones, etc.). Para sistemas multiusuario o contenido sensible, las metacapabilities son la mejor opción.
Arquitectura propuesta
Ejemplo práctico: construiremos un sistema de permisos para un Custom Post Type llamado document, donde:
- Cada documento tiene un autor (post_author).
- Además puede tener una lista de colaboradores guardada en post meta: _document_collaborators (array de IDs de usuario).
- Soportaremos roles administrativos: document_manager (capacidad manage_documents) y usuarios con capacidades granulares como edit_document, read_document, delete_document.
1) Registrar el Custom Post Type y capacidades primitivas
Cuando registres el CPT deberás declarar el parámetro capability_type y las capacidades personalizadas. Ejemplo de registro:
edit_document, read_post => read_document, delete_post => delete_document, edit_posts => edit_documents, edit_others_posts => edit_others_documents, publish_posts => publish_documents, read_private_posts => read_private_documents, ) args = array( label => Documents, public => true, supports => array(title, editor, author), capability_type => array(document, documents), map_meta_cap => true, // Crucial para usar map_meta_cap capabilities => caps, ) register_post_type(document, args) }) ?>
2) Crear roles y asignar capacidades
Crea un rol específico para gestores de documentos y asigna las capacidades primitivas necesarias.
true, manage_documents => true, // capacidades específicas edit_documents => true, edit_others_documents => true, publish_documents => true, read_private_documents => true, )) // Opcional: otorgar capacidades a administrador admin = get_role(administrator) if (admin) { admin->add_cap(manage_documents) admin->add_cap(edit_documents) admin->add_cap(edit_others_documents) } }) ?>
3) Mapear metacapabilities usando map_meta_cap
El paso esencial: enganchar una función al filtro map_meta_cap. Esa función recibirá la metacapability solicitada, el ID del usuario, y argumentos adicionales (p. ej. ID del post). Debes retornar un array de capacidades primitivas requeridas para autorizar la operación. Aquí implementaremos reglas:
- El autor del documento puede leer/editar su documento si tiene edit_documents o read_documents según el caso.
- Los colaboradores listados en post meta _document_collaborators pueden editar si el post está en determinado estado.
- Los managers (capacidad manage_documents) pueden todo.
post_type !== document) { return array(do_not_allow) } // Si el usuario es administrador o tiene manage_documents, dejar que WP valide con esa primitiva if (user_can(user_id, manage_documents)) { return array(manage_documents) } // Propietario del documento if ((int)post->post_author === (int)user_id) { switch (cap) { case read_document: return array(read_documents) case edit_document: return array(edit_documents) case delete_document: return array(delete_documents) } } // Colaboradores almacenados en post meta (array de IDs) collabs = get_post_meta(post_id, _document_collaborators, true) if (!empty(collabs) is_array(collabs) in_array((int)user_id, array_map(intval, collabs), true)) { // Los colaboradores pueden leer y editar, pero no eliminar, por ejemplo if (cap === read_document) { return array(read_documents) } elseif (cap === edit_document) { return array(edit_documents) } else { return array(do_not_allow) } } // Por defecto, usuario necesita capacidades de others para editar/eliminar si no es autor if (cap === edit_document) { return array(edit_others_documents) } elseif (cap === delete_document) { return array(delete_others_documents) } elseif (cap === read_document) { // Si el post es público, permitir leer mediante capability read if (post->post_status === publish) { return array(read) } return array(do_not_allow) } return array(do_not_allow) }, 10, 4) ?>
Notas sobre la implementación
- Usamos map_meta_cap para convertir una sola metacapability en una o más capacidades primitivas.
- Retornar do_not_allow es la forma correcta de denegar.
- Evitar llamadas costosas (consultas pesadas) dentro del hook cachea si es necesario.
4) Añadir/gestionar colaboradores en la UI (ejemplo simple)
Puedes usar campos personalizados en la pantalla de edición para añadir IDs de colaboradores, o crear una metabox para seleccionar usuarios. Aquí un ejemplo mínimo para guardar un array de IDs.
5) Verificaciones en REST API y endpoints personalizados
Si expones el CPT en el REST API o creas endpoints personalizados, debes usar la misma lógica de permisos en la propiedad permission_callback de tus rutas o al definir el endpoint del CPT. Ejemplo de endpoint REST personalizado que usa current_user_can con la metacapability:
d ), array( methods => GET, callback => function(request) { id = (int) request[id] post = get_post(id) if (! post) { return new WP_Error(not_found, Documento no encontrado, array(status => 404)) } return rest_ensure_response(array( id => post->ID, title => post->post_title, )) }, permission_callback => function(request) { id = (int) request[id] return current_user_can(read_document, id) } )) }) ?>
6) Comprobaciones en frontend y en AJAX
En el frontend usa current_user_can(edit_document, post_id) antes de mostrar botones u operaciones sensibles. En AJAX asegúrate de sanitizar inputs y verificar nonces y capacidades en handlers.
7) Casos avanzados: grupos, expiraciones y capacidades temporales
Para escenarios más complejos (grupos de usuarios, permisos temporales, roles jerárquicos) sigue estas recomendaciones:
- Almacena relaciones en tablas o meta bien indexadas. Evita serializaciones grandes en opciones.
- Cuando verifiques pertenencia a grupos, usa transients para cachear comprobaciones costosas por unos segundos/minutos.
- Para permisos temporales, añade metadatos con fecha de expiración en map_meta_cap comprueba fecha y devuelve do_not_allow si expiró.
- Si necesitas capacidades calculadas a gran escala, considera almacenar capacidades agregadas por usuario (sincronizadas) para evitar cálculos on-the-fly en cada request.
8) Seguridad y buenas prácticas
- Principio de menor privilegio: otorga sólo las capacidades mínimas necesarias a roles.
- Evitar la escalada: no confíes en entradas del cliente siempre verifica capacidades en el servidor (REST/AJAX/Servidor).
- Saneamiento y validación: sanitiza IDs, cadenas y metas antes de guardarlas o procesarlas.
- Usa nonces en formularios y AJAX para evitar CSRF.
- Evita consultas dentro de map_meta_cap que sean muy costosas cachea resultados o precómputalos.
- Registro de auditoría: para acciones críticas (borrado, cambios de permisos), registra quién lo hizo y cuándo.
9) Rendimiento y caching
map_meta_cap se llama con frecuencia (cada current_user_can). Para evitar sobrecargar la base de datos:
- Cachea listas de colaboradores por post en un transient o en un objeto cache persistente con expiración corta.
- Si haces checks de pertenencia a grupos, precarga la información del usuario una vez por request.
- Usa object cache (Redis/Memcached) en entornos con alto tráfico.
10) Tests y validación
Implementa tests unitarios con WP_UnitTestCase para validar las reglas de map_meta_cap: casos de autor, colaborador, manager, usuario anónimo y expiraciones. Ejemplo de test que debes implementar en tu suite de pruebas (esquema):
factory->user->create() wp_set_current_user(user_id) post_id = this->factory->post->create(array( post_type => document, post_author => user_id, )) this->assertTrue(current_user_can(edit_document, post_id)) } } ?>
Tabla resumen: metacapabilities y primitivas (ejemplo)
Metacapability | Posible mapping | Comentario |
---|---|---|
edit_document | edit_documents / edit_others_documents / do_not_allow | Autor usa edit_documents no autor requiere edit_others_documents o derecho especial |
read_document | read / read_documents / do_not_allow | Documento publicado => read privado => read_documents o permiso especial |
delete_document | delete_documents / delete_others_documents / do_not_allow | Similar a editar pero habitualmente más restrictivo |
Consejos finales
Implementar un sistema de permisos granular con metacapabilities te dará control y flexibilidad sobre quién puede hacer qué en tu aplicación WordPress. Las reglas deben ser explícitas, auditables y optimizadas. Empieza por modelar claramente los actores (autor, colaborador, manager), define capacidades primitivas coherentes y documenta el comportamiento de map_meta_cap. Siempre prueba exhaustivamente y aplica caching cuando detectes cuellos de botella.
Recursos útiles
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |