Contents
Introducción
Este artículo explica paso a paso cómo crear un sistema de likes en WordPress usando la REST API, JavaScript en el cliente y nonces para proteger las operaciones. El objetivo es ofrecer una solución segura, fácil de integrar y escalable para sitios que quieran permitir a los usuarios dar me gusta a entradas (posts). Incluye código de ejemplo para el servidor (PHP) y para el cliente (JavaScript), además de recomendaciones sobre cómo almacenar y validar los likes.
Resumen de la arquitectura
La solución propuesta consta de tres piezas principales:
- Un endpoint de la REST API que recibe las peticiones para añadir/quitar un like.
- Lógica de servidor (PHP) que valida nonces, controla permisos, actualiza almacenamiento (postmeta o tabla propia) y devuelve estado y recuento.
- Un script JS que envía peticiones fetch al endpoint, incluye el nonce en las cabeceras y actualiza la interfaz en tiempo real.
Consideraciones de diseño
Antes de ver el código, ten en cuenta:
- Autenticación y autorización: lo habitual es exigir login para evitar abusos (bots, múltiples likes). Si se quiere permitir invitados, hay que aplicar limitaciones adicionales (cookies, IP, short-lived transients).
- Protección CSRF: se utilizará el nonce de WordPress (wp_create_nonce) y el header X-WP-Nonce para que sólo clientes legítimos puedan hacer cambios.
- Almacenamiento: para proyectos pequeños/medianos se puede usar post meta: un meta con entradas individuales liked_by por usuario (add_post_meta) y/o un meta con contador. Para escala grande, crear una tabla propia es preferible por rendimiento y consultas.
- Condiciones de carrera: para evitar inconsistencias usar operaciones atómicas cuando sea posible el enfoque usando add_post_meta/delete_post_meta para valores por usuario reduce riesgo de colisiones respecto a sobrescribir un array completo.
1) Registro del endpoint REST
En functions.php de tu tema o mejor en un plugin, registra la ruta REST y su controlador. El endpoint comprobará el nonce en la cabecera y que el usuario esté logueado.
lt?php // Archivo: wp-content/plugins/likes-system/likes-rest.php (ejemplo) add_action(rest_api_init, function() { register_rest_route(likes/v1, /toggle, [ methods => POST, callback => likes_toggle_handler, permission_callback => function(WP_REST_Request request) { // Obtén el nonce enviado por la cabecera X-WP-Nonce nonce = request->get_header(x_wp_nonce) ?? if (! wp_verify_nonce(nonce, wp_rest) ) { return new WP_Error(rest_forbidden, Nonce inválido, [status => 403]) } // Requiere que el usuario esté logueado (opcional: permitir invitados) if (! is_user_logged_in()) { return new WP_Error(rest_forbidden, Acceso restringido, [status => 401]) } return true }, ]) }) function likes_toggle_handler(WP_REST_Request request) { post_id = intval( request->get_param(post_id) ) if ( post_id <= 0 ! get_post(post_id) ) { return new WP_Error(invalid_post, ID de post no válido, [status => 400]) } user_id = get_current_user_id() if ( ! user_id ) { return new WP_Error(not_logged_in, Debes iniciar sesión, [status => 401]) } // Comprobar si el usuario ya ha hecho like liked = user_has_liked_post(user_id, post_id) if ( liked ) { // Quitar like delete_post_meta(post_id, liked_by, user_id) liked = false } else { // Añadir like (guardamos entradas independientes para evitar sobrescrituras) add_post_meta(post_id, liked_by, user_id) liked = true } // Actualizar recuento (se puede calcular dinámicamente) count = count( get_post_meta(post_id, liked_by) ) update_post_meta(post_id, likes_count, count) return rest_ensure_response([ liked => (bool) liked, count => (int) count, post_id => post_id, ]) } function user_has_liked_post(user_id, post_id) { // get_post_meta con tercer parámetro false devuelve array con todos los values likes = get_post_meta(post_id, liked_by, false) if ( empty(likes) ) { return false } return in_array((string) user_id, array_map(strval, likes), true) } ?gt
Notas de seguridad y buenas prácticas
- Evita usar arreglos serializados grandes en un solo meta si esperas muchos likes. En su lugar, usa entradas de meta múltiples (como en el ejemplo) o una tabla personalizada.
- Valida siempre post_id y el usuario. No confíes en datos del cliente.
- Usa WP_Error para devolver errores con códigos HTTP apropiados.
2) Encolar el script y pasar datos (nonce y root)
Necesitas inyectar desde PHP al JS la URL raíz de la REST API y el nonce. Un método clásico es wp_localize_script (aunque su nombre sugiere traducción, sirve para pasar variables).
lt?php // En tu plugin o functions.php function likes_enqueue_scripts() { wp_enqueue_script( likes-js, plugin_dir_url(__FILE__) . likes.js, [], 1.0, true ) wp_localize_script(likes-js, wpLikes, [ root => esc_url_raw( rest_url() ), nonce => wp_create_nonce(wp_rest), ]) } add_action(wp_enqueue_scripts, likes_enqueue_scripts) ?gt
3) HTML mínimo para el botón
Inserta en la plantilla la marca HTML para el botón. Asegúrate de imprimir el recuento inicial del servidor (para SEO y accesibilidad).
ltbutton class=post-like-button data-post-id=123 aria-pressed=falsegt ltspan class=like-icongt♥lt/spangt ltspan class=like-countgt0lt/spangt lt/buttongt
4) JavaScript: manejo del clic y petición REST
El script escucha clicks en botones con clase .post-like-button, envía una petición POST al endpoint y actualiza la UI según la respuesta. Usa fetch y el header X-WP-Nonce.
// Archivo: likes.js document.addEventListener(DOMContentLoaded, function () { document.body.addEventListener(click, function (e) { var btn = e.target.closest(.post-like-button) if (!btn) return e.preventDefault() var postId = parseInt(btn.getAttribute(data-post-id), 10) if (!postId) return // Mostrar estado de carga (opcional) btn.disabled = true fetch( wpLikes.root likes/v1/toggle, { method: POST, credentials: same-origin, headers: { Content-Type: application/json, X-WP-Nonce: wpLikes.nonce }, body: JSON.stringify({ post_id: postId }) }) .then(function (res) { if (!res.ok) throw res return res.json() }) .then(function (data) { if (data typeof data.count !== undefined) { var countEl = btn.querySelector(.like-count) if (countEl) countEl.textContent = data.count // Actualizar estado del botón btn.setAttribute(aria-pressed, data.liked ? true : false) if (data.liked) btn.classList.add(liked) else btn.classList.remove(liked) } }) .catch(function (err) { // Puedes manejar errores más finamente leyendo err.json() console.error(Error al togglear like:, err) }) .finally(function () { btn.disabled = false }) }) })
5) Alternativas de almacenamiento
Dependiendo de la dimensión del sitio, considera estas alternativas:
- Post meta multivalor (ejemplo anterior): fácil de implementar, indicado para cargas moderadas.
- Tabla personalizada: mejor para muchas filas y consultas frecuentes permite índices y consultas eficientes por usuario/post.
- Objetos transients/caching: guarda el recuento en memoria y sincroniza periódicamente para reducir lecturas de meta.
6) Soporte para usuarios invitados (opcional)
Si quieres permitir likes sin login, añade controles anti-abuso:
- Guardar una cookie per-post que impida repetir la acción desde el mismo navegador.
- Limitar por IP y usar transients para bloquear solicitudes repetidas.
- Usar un combo cookie IP fingerprinting para reducir fraude.
Recuerda: los nonces protegen contra CSRF, pero no autentican a un bot por eso conviene limitaciones adicionales para invitados.
7) Mejoras y funcionalidades extra
- Endpoint para consultar estado inicial y si el usuario ya ha dado like (GET), de forma que al cargar la página puedas marcar el botón correctamente sin imprimir en server-side.
- Animaciones y accesibilidad: usa aria-pressed y mensajes de estado para lectores de pantalla.
- Logs y métricas: monitoriza el uso para detectar patrones sospechosos.
- Rate limiting: aplica límites por usuario/IP para evitar abusos.
8) Ejemplo completo resumido
A continuación se muestra un resumen de los elementos clave (ya vistos). Integrar todo en un plugin o en functions.php siguiendo buenas prácticas de WordPress.
Comportamiento esperado
- Al cargar la página: el botón muestra el recuento que viene del servidor (meta likes_count).
- Al hacer click: el script envía POST al endpoint con X-WP-Nonce. El servidor valida el nonce y el usuario, alterna el estado y devuelve { liked, count }.
- El cliente actualiza la UI con el nuevo recuento y marca el estado del botón.
Conclusión
Este enfoque combina la flexibilidad de la REST API de WordPress con nonces para proteger operaciones sensibles, y JavaScript moderno para ofrecer una experiencia ágil e interactiva. Para muchos sitios la solución basada en postmeta multivalor es suficiente para sitios con mucho tráfico o millones de likes, valdrá la pena una tabla dedicada y caching inteligente.
Recursos y enlaces útiles
|
Acepto donaciones de BAT's mediante el navegador Brave 🙂 |