wordpress y angular

Cómo preparar un api rest en wordpress para trabajarla con angular... o lo que sea

Steve Wheeler

archivado en: WordPress / 7 marzo, 2016

WordPress es una maravilla, una herramienta muy potente que permite publicar contenidos a cientos de miles de personas por todo el planeta gracias a su fácil instalación, sus mecanismos de seguridad y la ergonomía de sus interfaces. Sin embargo, en ocasiones siento la tentación de pasarme a otro tipo de plataformas para publicar mis blogs, como jekyll, sobre todo por tres razones:

  1. WP es muy lento.
  2. Es un poco engorroso preparar páginas especiales con cosas atípicas, como D3. (Me refiero a páginas integradas con el resto del sitio).
  3. Eso de ir saltando de página en página para llegar a los contenidos se me hace un poco... ¿pesado?

En fin, que ya sea por tontería o por algo más razonable, el caso es que desde hacía tiempo quería combinar wp para el back con angular y polymer para el front y desacoplarlo totalmente del back. Había empezado a currarme un api rest con slim, lo que me estaba costando más de la cuenta después de años sin tocar php, cuando descubrí un plugin que me terminó de resolver el problema en un periquete... tras un par de días picando código a toda pastilla dejé preparada una estructura que espero ir mejorando con el tiempo. El resultado está en The Bit Jazz Lab, una réplica experimental de este sitio alojada en las páginas de git.

Primero explicaré cómo preparar la parte de php y luego, la de angular. Como veréis, es más sencillo que un botijo, aunque sin una base en angular y wordpress, no creo que lo siguiente sea de utilidad. Bueno, pues sin más preámbulos, al lío.

El api-rest

Lo primero que debemos hacer es instalar un plugin fantástico, que debería formar parte del core de wordpress, denominado WordPress REST API (v2) de Ryan McCueRachel Baker, Daniel Bachhuber y Joe Hoyle. Gracias a este plugin, sin necesidad de mayor esfuerzo, tenemos ya una montonera de endpoints con casi todo lo que podamos necesitar. Las uris se encuentran en el nombre del dominio seguido del nombre del plugin, el sufijo wp y la versión. Por último, se añade el recurso que se está solicitando. Por ejemplo, así sacaríamos el json con los últimos post del sitio www.foo.com.

http://www.foo.com/wp-json/wp/v2/posts

Como decía, hay endpoints para casi todo. Los más importantes son:

  • posts: devuelve los posts
  • pages: las páginas
  • comments: los comentarios
  • categories: las categorías
  • tags: los tags.

Además, en la query-string podemos precisar la consulta concatenando campos. Por ejemplo, así obtendríamos el post cuyo id fuera 18.

http://www.foo.com/wp-json/wp/v2/posts?18

Juan Díaz Bustamante explica bien toda esta parte y el api está muy bien documentada, así que no me enrollo mucho más.

Crear un endpoint

Si los endpoints que trae de fábrica se nos quedan cortos, podemos extenderlos creando un plugin que guardaremos en wp-content/plugins/. En este plugin jugamos con el método get_posts() para armar lo que necesitemos. Por ejemplo, para mostrar los post de la portada necesito de todos los posts algunos campos en concreto, por lo que me preparé un hook. Está también bien documentado en la página del plugin cómo hacerlo, pero vamos, que no es más que un get_posts() que se solicita desde el método register_rest_route(), en el cual definimos la nueva ruta que estamos creando.

En esencia, la jugada es esta.

  1. Escribimos una función en la que recogeremos los posts. Esta función lanza la consulta, la recorre guardando los datos que nos interesan  de cada ítem en un array temporal que devolvemos al final.
  2. Registramos una nueva ruta con el método  register_rest_route() pasándole un array con la ruta, el método y el nombre de la función que recoge los posts.

En mi caso, por ejemplo, el plugin me quedó de esta guisa.

<?php

/*Plugin Name: The Bit Jazz Band Rest

Description: Custom end point

Author: mmfilesi.com

Version: 1.0

Author URI: http://www.mmfilesi.com

*/

function tbjbAllPosts($data) {

$arrayDef = [];

$item = [];

$temp = [];

$args = array(

'numberposts' => -1,

'category' => '-44',

'orderby' => 'post_date',

'order' => 'DESC'

);

$posts = get_posts($args);

if ( empty($posts) ) {

return null;

}

foreach ($posts as $value) {

$itemTemp['id'] = $value->ID;

$itemTemp['date'] = $value->post_date;

$itemTemp['title'] = $value->post_title;

$itemTemp['sumary'] = $value->post_excerpt;

$itemTemp['name'] = $value->post_name;

$itemTemp['wp-link'] = $value->guid;

$itemTemp['category'] = get_the_category($value->ID, 'thumbnail');

$itemTemp['tags'] = wp_get_post_terms($value->ID, 'post_tag', 'fields=all');

$itemTemp['workshop'] = wp_get_post_terms($value->ID, 'series', 'fields=all');

$temp = get_the_post_thumbnail($value->ID, 'thumbnail');

$temp = explode(" ", $temp);

$patt = '/http.{1,}jpg/i';

$temp = preg_match($patt, $temp[3], $m );

$itemTemp['thumbnail'] = $m[0];

$idThumbnail = get_post_thumbnail_id($value->ID);

$itemTemp['thumbnailCaption'] = get_post_meta($idThumbnail, '_wp_attachment_image_alt', true);

array_push($arrayDef, $itemTemp);

}

return $arrayDef;

}

add_action( 'rest_api_init', function () {

register_rest_route( 'tbjb/v1', '/posts/', array(

'methods' => 'GET',

'callback' => 'tbjbAllPosts',

));

});

Y ya tenemos un api rest lista para consumir desde cualquier lado. Lo suyo, sin embargo, es cachear esta consulta, que es bastante pesada. Estuve pegándome un rato con esto con un hook que se dispara cuando se publica un post denominado publish_post. La idea es que cada vez que se publique un post, se lea el end-point y se escriba el resultado de la consulta en algún lado. Pero, me topé con problemas de permisos y, como no le podía dedicar más tiempo al tema, ahí lo dejé para más adelante.

Vamos ya con angular.

La estructura base

Sospecho que no tiene sentido explicar en detalle cómo he montado todo el tinglado. Sería pesado para quien lo escribe y para quien lo lee y ahí está el código en git disponible para consultarlo, por lo que me limito a comentar un par de ideas clave sobre la estructura.

Claro está, haremos tantos pares de controladores-vista como tipos de visualizaciones tengamos. Lo normal en wordpress es una para la portada, otra para los posts, una más para la recopilación de artículos bajo la misma categoría, tag, lo que sea y una para las página, que yo no tengo porque no uso. Además, crearemos una directiva por cada elemento que compartan estos controladores, como el header, el footer o la barra lateral.

angularwp00

Hasta aquí no debería haber duda alguna. Lo único complicado es la parte rest. La idea es tener al menos dos factorías. Desde una lanzaremos todas las llamadas rest y la otra, el modelo, actuará de intermediario almacenando las respuestas. De esa manera, solo se llaman una sola vez las cosas.

El jaleo se produce si no entendemos cómo funcionan las promesas de angular, ya que para que funcione el apaño tenemos que enlazarlo todo con promesas:

a) La factoría rest devuelve una promesa natural, la que viene de fábrica y en el objeto $http.

b) El modelo recoge esta promesa y lanza a su vez otra mediante el objeto $q.

Por ejemplo, en una factoría que podemos llamar wordpress o como sea lanzamos la llamada rest que recoge los posts:

angular.module('bjLab')

.factory('wordpress', function($http) {

urlBase = 'https://www.mmfilesi.com/wp-json/';

return {

getPosts: function() {

return $http.get(urlBase + 'tbjb/v1/posts');

}

// más llamadas rest...

}

});

Y no llamamos a ese método directamente desde los controladores, sino que creamos una factoría intermedia, el modelo, donde guardamos el resultado de la llamada.

angular.module('bjLab')
.factory('allData', function($q, wordpress) {

var allData = {};

/* cacheamos en self el objeto para no volvernos tarumbas con el this */

var self = allData;

/* Inicializamos los datos que vamos a almacenar */

allData.allPost = 'pending';

/* Todos estos objetos devuelven promesas, que a su vez se alimentan de las promesas que trae de fábrica el objeto $http de angular */

allData.getPosts = function() {

/* Creamos la promesa */

var deferred = $q.defer();

/* Si ya hemos cargado los posts, definimos la promesa como resuelta */

if ( self.allPost !== 'pending' ) {

deferred.resolve(self.allPost);

/* En el caso contrario, los solicitamos a la factoría */

} else {

wordpress.getPosts().then(

function successCallback(response) {

self.allPost = response.data;

deferred.resolve(self.allPost);

}, function errorCallback(response) {

deferred.reject(response);

});

}

/* Devolvemos la promesa */

return deferred.promise;

};

return allData;

});

Y ya desde el controlador llamamos a este modelo intermedio.

angular.module('bjLab')

.controller('mainCtrl', ['$scope', '$state', 'configBasic', 'allData', 'utils',

function($scope, $state, configBasic, allData, utils) {

var allPostData = 'loading';

var rest = {

getPosts: function() {

allData.getPosts().then(

function successCallback(data) {

allPostData = data;

}, function errorCallback(response) {

// todo :: implementar interceptores

});

}

};

var init = {

initAll: function() {

rest.getPosts();

}

};

init.initAll();

}]);

Bueno, como decía, creo que esta es la única parte complicada de todo el apaño. Lo demás espero que se entienda sin más problemas mirando el código en caso de que sea necesario.

s2.

|| Tags: , , ,

valoración de los lectores sobre wordpress y angular

  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • estrellica valoración positiva
  • 5 sobre 5 (1 votos)

¿Te ha parecido útil o interesante esta entrada?
dormido, valoración 1 nadapensativo, valoración 2 un poco sonrisa, valoración 3 a medias guiño, valoración 4 bastante aplauso, valoración 5 mucho

Tú opinión es muy importante, gracias por compartirla!

2 respuestas a “wordpress y angular

  1. Ojito con las inyecciones de código, que WordPress es muy puñetero y la gente las pasa muy putas despues….

  2. marcos el dijo:

    Sí, May, tienes más razón que un santo, que diría mi abuela : ). En teoría el plugin es muy seguro y para hacer operaciones POST y DELETE tienes que estar autentificado, pero le estoy dando vueltas al tema para ver cómo reforzar la seguridad. Gracias por el aporte.