Cerrar
Image Alt

AJAX en WordPress con WP REST API

Este artículo es una continuación del anterior, Usando Javascript en WordPress de forma correcta, y en el vamos a ver cómo podemos enviar o recibir datos desde Javascript a tu WordPress utilizando AJAX y el nuevo WP REST API.

Como sabéis, AJAX es una técnica que nos permite enviar y recibir datos de la base de datos de tu web utilizando Javascript y así cambiar contenidos de tu web sin tener que refrescar la página, lo cual incrementa la usabilidad de la misma y su facilidad y rapidez de uso.

Antes de la versión 4.4 de WordPress se usaba admin.ajax.php para enviar y recibir datos, tanto en el área de administración como en la parte frontal de la web. Aunque este método aún funciona, desde WordPress 4.4 tenemos un método mejor para utilizar AJAX en WordPress utilizando el WP REST API, del que ya hablamos hace tiempo en este blog cuando aún no estaba integrado en WordPress y era un plugin aparte. Este API utiliza mejores prácticas, es más sencillo de utilizar y necesita menos código por tu parte y por último, es perfecto para pasar datos a aplicaciones externas de WordPress, como por ejemplo una aplicación móvil.

Y para ver su funcionamiento vamos a utilizar un ejemplo práctico: vamos a crear un plugin que permitirá a los usuarios de nuestro blog votar un artículo. Una vez el usuario vote se enviará y grabará el voto vía Ajax, cambiando el número de votos sin refrescar la página.

Creando un Endpoint en WP REST API

Lo primero que tenemos que hacer es crear un Endpoint en WP REST API. WP REST API no sólo ofrece una serie de rutas por defecto para interactuar con nuestro WordPress. Además nos ofrece una serie de herramientas para que creemos nuestras «rutas» y «endpoints».

Los «Endpoints» son funciones disponibles a través del API. Pueden ser funciones que nos permitan actualizar un artículo, o borrar un comentario. Los «Endpoints» realizan una función específica y suelen tomar un número determinado de parámetros y devuelven información al cliente.

Una «ruta» es el «nombre» que utilizas para acceder a un «endpoint» concreto, y es una URL.

Para nuestro ejemplo: /wp-json/votaciones/v1/votos. La primera parte (wp-json) es común a todas las URLs que generemos con el WP REST API. La siguiente parte («votaciones») es una cadena de texto única para nuestros «endpoints». Necesita ser única y que no esté repetida por cualquier otro plugin creando «endpoints» por lo que hay que tener cuidado con poner palabras genéricas (si, como la que hemos puesto de «votaciones». Quizás debería de haber puesto emn_votaciones, por ejemplo). A continuación ponemos el número de versión de nuestro «endpoint», en nuestro caso «v1» o versión 1. Si añadimos nuevas características o cambiamos algo de nuestro «endpoint» y lo pasamos a versión 2, pondríamos «v2». El nombre del «endpoint» y la versión juntos forman el namespace de nuestros «endpoints», es decir, un nombre de referencia que nos permitirá referirnos a él más adelante. La última parte de la url («votos»), es una función específica que nos permitirá, en nuestro caso, obtener los votos actuales de un artículo, sumar uno y actualizar en base de datos. Esta última parte («votos») es lo que en la terminología del WP REST API se llama la ruta.

Cuando creamos un «endpoint» necesitamos definir qué método o métodos vamos a utilizar. Normalmente podrán ser GET (para leer el valor de algo, como por ejemplo los votos que tenemos, como veremos más adelante), POST (para añadir algo nuevo, como un nuevo voto), PUT (para actualizar datos) o DELETE (para borrar datos). Sabiendo esto vamos a ver cómo registramos nuestro primer «endpoint» en WordPress para que podamos usarlo con Javascript para recibir y enviar datos. Para ello utilizamos la función register_rest_route():

El primer parámetro de la función es el «namespace», que ya vimos antes que en nuestro caso es «votaciones/v1». El segundo parámetro es nuestra «ruta» o la función específica que estamos registrando, que en nuestro caso es «/votos/». El tercer parámetro son los otros argumentos que necesita nuestro «endpoint»: en este caso especificamos los métodos permitidos (POST, para añadir un nuevo voto) y la función a la que llamaremos cuando llamemos a nuestro «endpoint» a través de Javascript (que en nuestro caso hemos llamado «votaciones_nuevo_voto»). Hay otros argumentos necesarios para validar y limpiar los datos, pero los veremos más adelante, con el fin de ir poco a poco.

Llamamos a register_rest_route() cuando se ha lanzado la acción rest_api_init. Por ello, nuestra función completa quedará así:

De esta forma, lanzamos la función votaciones_registrar_endpoints() en el momento oportuno (indicado en el primer parámetro «rest_api_init»). Necesitaremos también crear la función votaciones_nuevo_voto() a la que llamamos desde la ruta creada. Si ponemos este código en un plugin o en el archivo functions.php de nuestro tema necesitaremos refrescar los «permalinks» o enlaces permanentes para que funcione. En el caso de que estamos creando un plugin lo mejor será refrescar los enlaces permanentes cuando se active el plugin. Para ello añadiríamos esta función:

Utilizando nuestro Endpoint

Vamos a desarrollar la función votaciones_nuevo_voto() que estaba en el parámetro callback de nuestro «endpoint». Cuando llamamos al «endpoint» se ejecutará esa función. Cuando esto sucede estamos pasando un objeto de WP_REST_Request como parámetro que contiene datos de la llamada (request) junto con cualquier dato que acompañe a dicha llamada:

Podemos acceder a los datos de $request de varias formas. Por ejemplo, imaginémonos que uno de los datos es el ID de un post. Podemos acceder como un array o utilizando la función get_param(). También podemos obtener todos los parámetros con la función get_params(). Podéis probar cualquiera de los tres y veréis que nos devuelve los valores en formato JSON.

Visto esto quitamos (o comentamos para futuras pruebas) lo que hemos puesto y nos ponemos manos a la obra con la función. Esta función tiene que recibir el ID de un post e incrementar su voto en uno. Como los votos los vamos a almacenar como un post_meta utilizaremos la función get_post_meta() para obtener los votos (que llamaremos ‘votos’) que un determinado artículo ID (obtenido como vimos con $request->get_param('id');) tiene en la actualidad. El último parámetro (true) permitirá que nos devuelva un objeto en vez de un array. Luego actualizamos en la base de datos los votos añadiendo uno con la función update_post_meta(). Como medida de precaución podemos comprobar si esta actualización de votos nos ha dado algún problema (comprobamos si nos ha devuelto un FALSE), en cuyo caso devolvemos un mensaje de error. Si todo va bien devolvemos el número de votos incrementado en uno:

Perfecto, ya tenemos mucho hecho. Pero como habréis visto no hemos validado ni limpiado los datos que se reciben, y esto es lo que vamos a hacer a continuación.

Validando y Limpiando los datos

Como os podéis imaginar esta sección es muy importante cuando estamos pasando datos desde Javascript a nuestro WordPress, ya que está en juego la seguridad de nuestra web. Con la validación nos aseguramos de que los datos que estamos obteniendo son lo que estábamos esperando. Es decir, si esperamos el ID de un artículo está claro que tenemos que recibir un número mayor que cero y sin decimales, además de pertenecer a un artículo existente. Con la limpieza de los datos (en inglés «Sanitization») lo que haremos será convertir en seguros los datos antes de utilizarlos.

Vamos a volver al «endpoint» que creamos anteriormente vara ver cómo podemos validar y limpiar los datos de forma muy sencilla. Añadimos unos nuevos argumentos (que llamaremos ‘args’) donde estarán los argumentos que nuestro «endpoint» esperará recibir. En nuestro caso el ID del artículo. Para este argumento podemos especificar otro array donde estará nuestra validación y limpieza:

Vemos que dentro de ‘args’ hemos añadido para el ‘id’ en primer lugar que el ‘id’ es requerido, de tal manera que ni no se recibe el ‘id’ saldrá un mensaje de error. En segundo lugar, mediante ‘validate_callback’, podemos llamar a una función para validar el ‘id’. En este caso hemos creado una función anónima sin utilizar una función externa (como hicimos cuatro líneas antes con ‘votaciones_nuevo_voto’). Esta función recibe como parámetros el valor del propio parámetro, el objeto $request y el ‘key’ que en nuestro caso es ‘id’. Dentro de la función comprobaremos si el parámetro es un número y si el ID pertenece a un artículo existente (con get_post($param)). En tercer lugar, mediante ‘sanitize_callback’ podemos llamar a una función para limpiar el parámetro recibido. En nuestro caso simplemente nos aseguramos que tiene el formato ‘absint’ para asegurarnos que el valor es un número absoluto mayor de cero.

Bueno, ya sabemos que los datos son seguros. Pero, ¿qué sucede si queremos que sólo voten los usuarios registrados en nuestro blog? Para ello tenemos que asegurarnos que el usuario ha entrado en su cuenta. Lo vemos a continuación.

Autenticación en WP REST API

La Autenticación en WP REST API es uno de los elementos más farragosos de este API, especialmente si estamos contactando con nuestro WordPress desde fuera del mismo, por ejemplo con una aplicación móvil.

Por defecto no hay ningún sistema de autenticación en el WP REST API, por lo que cualquiera podrá acceder a tus «endpoints». Si quieres restringir el acceso a los usuarios registrados en tu web y el acceso se realiza desde un plugin o desde un tema de tu WordPress, es decir, desde la propia web, entonces el sistema de autenticación será vía cookies. Si el acceso es desde fuera del tema o de los plugins de la web, es decir, es un acceso desde fuera (por ejemplo una app móvil) entonces el sistema de autenticación hay que hacerlo utilizando OAuth y es bastante más complejo.

Como en nuestro ejemplo estamos creando un sistema de votaciones interno de nuestro WordPress utilizaremos el sistema de cookies.

Dentro del sistema de autenticación por cookies utilizaremos el llamado «WordPress nonce«, que significa «number used once» (número utilizado sólo una vez). WordPress Nonce nos permitirá verificar si la petición la está haciendo un usuario real. Hemos de tener en cuenta que en WordPress podrán ser usados más de una vez dentro de un periodo temporal determinado, por lo que no deberíamos confiar sólo en él ya que cualquiera podría detectarlo y usarlo más de una vez antes de que expire. Por ello conviene combinarlo con funciones de WordPress como is_user_logged_in() o current_user_can().

Para crear un «nonce» dentro del WP REST API utilizaremos wp_create_nonce( 'wp_rest' );. Para pasar el «nonce» en la petición utilizaremos wp_localize_script() para pasar al Javascript el valor del «nonce» para usarlo en la llamada al «endpoint» (ya vimos como usar wp_localize_script() en Usando Javascript en WordPress de forma correcta). Desde el Javascript pasaremos el valor del nonce en la cabecera de la petición a nuestro «endpoint», tal y como veremos en el siguiente apartado «Llamando al «endpoint».

En el plugin que estamos creando (más tarde lo pondré entero) he añadido la función para añadir el archivo de Javascript que mandará la petición al «endpoint» cada vez que un usuario vote:

Como podéis ver añadimos el archivo «votaciones.js», que estará en una carpeta llamada «js» de nuestro plugin. Este archivo necesita jQuery y con el «true» lo situamos en el pie de la página. Con wp_localize_script pasamos una serie de datos al archivo Javascript con el objeto «votaciones_data»: la url del «endpoint», el mensaje de éxito, el mensaje de error y el «nonce».

Ahora vamos a ver qué tendríamos que hacer si queremos que sólo voten los usuarios registrados y que hayan entrado previamente en su cuenta. Volvemos a la función que utilizamos para registrar nuestro «endpoint» y añadimos con «permission_callback» una función para verificar permisos para el «endpoint». Vamos a añadir una función anónima a la que llamaremos para verificar los permisos y que deberá devolver un resultado TRUE a la función is_user_logged_in() de WordPress para permitir usar este «endpoint».

Llamando al «endpoint»

Vamos a hacer la llamada al «endpoint» desde el archivo «votaciones.js» que añadimos a nuestro WordPress en el apartado anterior.

El archivo Javascript, votaciones.js, sería:

Vamos a ver línea a línea:

  • En la línea 3: En nuestro HTML vamos a tener un enlace para votar con la clase «votaciones-vota» (ver más adelante, cuando creemos el botón de votar). Aquí lo que hacemos es que cuando se hace click una vez en el enlace (sólo queremos que se haga una vez, por eso usamos «one») procedemos con el código que viene a continuación.
  • En la línea 5: Con esta línea nos aseguramos de que al hacer click en el enlace no se vaya a la página.
  • En la línea 7: Guardamos el valor que nos encontraremos en el atributo «data-post-id» del enlace para votar (ver más adelante) en el que el usuario ha hecho click.
  • En la linea 8: Guardamos en una variable el elemento en que el usuario ha hecho click para usarlo más adelante en el script.
  • En la línea 10: Abrimos la llamada que vamos a hacer vía Ajax.
  • En la línea 11: Obtenemos la url base del «endpoint» que hemos pasado al script desde wp_localize_script() y añadimos la ruta final, «votos».
  • En la línea 12: Decimos que el método va a ser POST.
  • Líneas 13, 14 y 15: Para pasar el valor del «nonce» que nos da wp_localize_script() para que lo use el WP REST API necesitamos añadirlo a la cabecera de esta petición. Para ello utilizamos el parámetro «beforeSend» de jQuery Ajax y en él hacemos una llamada a xhr.setRequestHeader (más información). Podéis probar a poner cualquier valor en vez del valor del «nonce» y veréis el error «Cookie nonce is invalid» en la consola de Chrome o de Firefox en formato JSON.
  • Líneas 16, 17 y 18: En la información que vamos a enviar está el ID del artículo que está votando el usuario, con la variable creada en la línea 7.

Aún no hemos terminado con el script, ya que nos falta ver qué hacemos si tenemos éxito guardando el voto, o si no lo tenemos. Pero lo completaremos más adelante. Ahora vamos a crear el botón para votar en cada artículo de nuestro blog imaginario. Lo podemos añadir de varias formas, yo aquí propongo una que es ponerlo al final del contenido desde el propio plugin.

Para ponerlo al final del contenido utilizamos el filtro «the_content» y añadimos el código al $content que recibimos como parámetro de nuestra función. Primero comprobamos si el artículo es del tipo «post» (esto lo podéis cambiar en el caso de que tengáis posts personalizados) y si estamos en el «main query» (para que no nos cambie otras cosas como menús, etc..). A continuación añadimos el html del número de votos (con un span con la clase «numero_votos» que utilizaremos para actualizar el número de votos y que obtiene el número de votos actual con get_post_meta()) y del enlace para votar (este tiene la clase «votaciones_vota» que ya vimos que usábamos en votaciones.js, así como el atributo «data-post-id» con el ID del artículo que también utilizaremos en votaciones.js).

No os voy a incluir ningún estilo para el botón de votar, pero lo podéis añadir vosotros fácilmente en el plugin según vuestras necesidades.

Ahora mismo ya podemos votar, pero nos queda refrescar el número de votos en la página una vez se ha votado. Para ello tenemos que hacer algunos añadidos en votaciones.js:

Como veis hemos añadido la función done() que se ejecutará cuando el ajax se haya ejecutado de forma correcta y sin errores. En dicha función añadimos una función anónima con el parámetro «data», que será lo que nos venga de vuelta que en nuestro caso es el número de votos actualizado. En esta función reemplazamos el html del enlace por el mensaje de éxito cuyo contenido nos manda wp_localize_script() y actualizamos el número de votos con el dato que hemos obtenido de vuelta.

También hemos añadido la función fail() para el caso de que haya un error. Comprobamos si hay un error generado por WordPress, por ejemplo al intentar actualizar el número de votos, y si no, ponemos nuestro mensaje de error genérico que nos pasa wp_localize_script().

Añadiendo más casos y más «endpoints»

Como hemos visto, en nuestro «endpoint» tenemos sólo el ‘id’ del artículo:

Pero, ¿y si necesitamos pasar más datos? Imaginaros por ejemplo que necesitamos pasar una puntuación del 1 al diez en nuestro sistema de votaciones. En ese caso podríamos añadir algo así:

O vamos a suponer que estamos pidiendo al usuario un campo de texto para que comente su valoración:

Hasta ahora hemos usado POST. Vamos a usar GET en un ejemplo: añadiendo un nuevo método para obtener todos los votos obtenidos y que sólo puedan acceder a ella los usuarios registrados. Necesitaremos introducir nuestros métodos dentro de un array, porque ya tenemos más de uno:

También podemos crear rutas adicionales. Vamos a crear, por ejemplo, una ruta para obtener los votos de un artículo concreto a través de su ID. Usaremos el mismo «namespace», que es «votaciones/v1», y luego /votos/ seguido del formato en el que obtendremos el ID del artículo (indicamos que el parámetro es «id» y luego ponemos una expresión regular para decir que serán números los valores a utilizar). Luego especificamos que el método es GET, el nombre de la función «callback» a la que llamaremos para obtener todos los votos del id (que la he añadido a continuación) y los permisos necesarios (en este caso con return TRUE decimos que cualquiera puede acceder al mismo). También validamos que el id es numérico y que existe un artículo con ese ID.

De esta forma si escribimos en nuestro navegador el dominio de nuestra web seguido de «/votaciones/v1/votos/-id-del-articulo» donde «id-del-articulo» es un número (el id del artículo del que queremos obtener el número de votos) obtendremos en formato JSON el número de votos del artículo.

Si queréis el código completo podéis bajaros el plugin aquí: Plugin Votaciones.

Optimización para tráfico alto

Es muy importante tener en cuenta que cada vez que un usuario visita un artículo estamos haciendo una llamada a la base de datos para obtener los votos de ese artículo. Luego, cada vez que vota hacemos otra llamada para obtener los votos y luego guardamos los nuevos votos. Esto no nos va a dar ningún problema si tenemos pocas visitas con pocos votos. Pero ¿os imagináis una web con muchísimo tráfico y miles de votos en espacios de tiempo muy corto o incluso simultáneos? Este código nos tumbaría la web. Tenemos que buscar la manera de: 1) cachear los votos 2) No guardarlos cada vez que se vote, sino irlos acumulando de alguna manera para guardarlos en grupo cada X tiempo.

El primer punto es sencillo. Podemos utilizar los transients de WordPress, de los que ya hemos hablado en este blog. De esta forma podemos mostrar los votos actuales de los artículos sin tener que ir a la base de datos (siempre que tengamos también un plugin de caché instalado).

En cuanto a la manera de almacenar y luego guardar los votos, podemos utilizar por ejemplo una cola de Amazon donde los vamos almacenando y los iremos obteniendo desde ahí cada X tiempo para guardarlos en bloque. Así es como lo he hecho en alguna revista de tráfico muy alto y está funcionando muy bien.

Otras opciones pueden ser utilizar sesiones para guardar los votos (que en WordPress es un poco lío), o Local Storage de Javascript.

Si tenéis más propuestas sobre cómo hacerlo podéis aportarlas en los comentarios!

Bueno, esto es todo. Espero que no se os haya hecho muy pesado!

Lecturas recomendadas

Comparing WordPress REST API Performance to admin-ajax.php

Comentarios

  • Roland

    Gracias por compartir, me será de gran ayuda. Un saludo.

    10 agosto, 2016
    contestar
  • Juan

    Una pasada la explicación. Llevo tiempo leyendo en blogs y creo que es de las primeras veces que el artículo me obliga a leerlo al completo.
    Por otro lado, una duda, en las validaciones que realizas por ej:

    A la función le pasas $request y $key sin embargo esos parámetros no los utilizas luego en la validación. Porque?

    Muchas gracias!
    Un Web Developer Aprentice

    2 marzo, 2017
    contestar
  • Gracias por compartir. Estoy seguro que me ayudará con mis proyectos.

    23 febrero, 2018
    contestar

Escribe un comentario