Cómo añadir campos personalizados a las revisiones en WordPress

Por David Muñoz
Developer en OpenSistemas

Antes de empezar el artículo quiero aclarar que esta es una solución que he aplicado personalmente, después de investigar creo que es la correcta, pero no lo puedo confirmar. Por ello, invito a quien conozca una manera mejor de realizarlo que lo indique y entre todos consigamos estandarizar esta funcionalidad, que a priori parece poco documentada.

Para empezar, vamos a hablar de las revisiones y de su funcionamiento por defecto en WordPress. Para el que no conozca WordPress, recuerdo que, por defecto, tiene implementado un sistema de revisiones que permite un control de versiones de los post. Dicho sistema ofrece un historial de cada cambio y la posibilidad de volver a una versión anterior del artículo, post o publicación.

Por defecto, estas revisiones están activadas y no tienen límite. Además, existe una opción de autoguardado que añade una foto a este conjunto de revisiones.

Si queremos controlar estos parámetros podemos modificar las constantes AUTOSAVE_INTERVAL y WP_POST_REVISIONS, con las cuales podemos controlar el intervalo de tiempo del autosave y el número máximo de revisiones por post o si las queremos tener activas.

Y, ¿cuándo se guardan las revisiones? Podríamos pensar que existe un check para indicar que, cuando le demos a actualizar o a publicar el post, la foto actual se va a guardar como una revisión del post. No obstante, esto no ocurre, ya que WordPress maneja las revisiones de tal forma que si guardamos como borrador o publicamos un post, si alguno de sus “campos por defecto” ha sido modificado, automáticamente, y solo si las tenemos activadas, crea una nueva revisión y la añade a nuestro listado de revisiones.

Lo que guarda de esta manera es útil, aunque limitado, pero lo que me parece más limitado y aquí es donde quería llegar cuando hablaba de campos por defecto, es que solo esté observando el title, el content y el excerpt para tener en cuenta modificaciones. Y a partir de aquí, mi problema y el motivo de este post: ¿qué pasa con los post meta y las taxonomías asociadas a un post? ¿Qué hacer si las quieres versionar?

Pues tranquilo, WordPress nos facilita herramientas para poder indicarle qué post meta te gustaría versionar y añadir al seguimiento.

A modo de receta, para esta pequeña implementación vamos a necesitar los siguientes hook: edit_post, save_post, wp_restore_post_revision, _wp_post_revision_fields, _wp_post_revision_field_{nuestro_meta}.

– edit post es un action que se ejecutará cuando se edite un post que ya está creado.
– save_post es un action que se ejecutará cuando creemos o acualicemos un post.
– wp_restore_post_revision es un action que se ejecutará cuando revirtamos a una versión anterior de nuestro post.
– _wp_post_revision_fields es un filtro en el que le indicamos al sistema qué “campos” o “post meta” queremos que tenga en cuenta para comparar entre la nueva versión y la antigua del post.
– _wp_post_revision_field_{nuestro_meta} es un filtro que nos permitirá darle formato a nuestro meta para que sea legible por la clase wp_diff.

Es necesario formatear todos los campos que no sean texto, ya que esta clase solo compara textos y, si no lo hacemos, en la pantalla de revisión nos mostrará el siguiente mensaje: “Sorry, something went wrong. The requested comparison could not be loaded.” También puede darse el caso de que no se pinte ninguna de las columnas de comparación.

Bien, dichos los ingredientes, antes de empezar a meter código de ejemplo es bueno saber el flujo de lo que sucede cuando damos al botón Publicar.

En primer lugar, se ejecuta el action edit_post, después la comparación de campos del post con los de la última revisión. Si encuentra algún campo diferente, crea una nueva revisión y, por último, se ejecuta el action save_post.

Y, ¿por qué cuento esto? Porque si nosotros estamos ‘seteando’ nuestros campos en el save_post, cuando hace la comparación en el paso 2, el valor con el que compara la revisión actual es nulo, es decir, compara nulo con lo que hubiera en la última. Lo peor de todo es que se crea una revisión con valores erróneos.

Además, es importante marcar -y lo difícil de comprender, pero la clave- que en action edit_post, si preguntamos por la última revisión, se trata de la anterior a guardar el post. Si en el save_post se ha detectado cambio en algún campo, la última revisión es la creada con nuestros nuevos valores, es decir, almacena los cambios o las diferencias que hemos añadido nuevas con respecto al estado anterior.

Dicho esto, lo primero que tenemos que hacer es guardar los datos de los post meta en el action edit_post.

function save_postdata($post_id, $post, $update = null) {
if (!current_user_can('edit_post', $post_id))
return $post_id;

$mymeta = sanitize_text_field($_POST['mymeta']);
update_post_meta($post_id, 'mymeta', $mymeta);
}
add_action("edit_post", 'save_postdata');

En el proceso interno del WordPress comparará los campos y ya los verá actualizados, por lo que al verlos diferentes creará una nueva revisión. En esta nueva revisión es donde setearemos los nuevos valores.

function os_revision_save_post( $post_id ) {
    if (!current_user_can('edit_post', $post_id))
        return $post_id;

    //Obtenemos la última revisión vigente
    if ( $revisions = wp_get_post_revisions( $post_id ) ) {
        // grab the last revision, but not an autosave
        foreach ( $revisions as $revision ) {
        if ( false !== strpos( $revision->post_name, "{$revision->post_parent}-revision" ) ) {
            $last_revision = $revision;
            break;
        }
    }
}

//Obtenemos el meta actual del post
$mymeta = get_post_meta($post_id, 'mymeta', true);
//Se lo seteamos a la última revisión
update_metadata( 'post', $revision->ID, 'mymeta', $mymeta );

}
add_action( 'save_post', 'os_revision_save_post');

Una vez añadidos los campos a la última revisión, es muy posible que en futuro queramos recuperar este tipo de valores, pues bien para ello le tenemos que indicar a WordPress cómo hacerlo:

//Reverting to the correct revision of the meta field when a post is reverted
function os_revision_restore_revision( $post_id, $revision_id ) {

$post     = get_post( $post_id );
$revision = get_post( $revision_id );

//Se obtiene el meta de la revisión elegida
$mymeta  = get_metadata( 'post', $revision->ID, 'mymeta', true );

//Se 'setea' el meta para el post
if ( false !== $mymeta )
update_post_meta( $post_id, 'mymeta', $mymeta );
else
delete_post_meta( $post_id, 'mymeta' );

}
add_action( 'wp_restore_post_revision', 'os_revision_restore_revision', 10, 2 );

¿Bueno, pero cómo se le dice a WordPress en qué campos tiene que mirar? Pues con el siguiente filtro:

//Displaying the meta field on the revisions screen
function os_revision_revision_fields( $fields ) {

//Filtro key=>value con el key del meta y el nombre a desplegar en la página de comparación.
$fields['mymeta'] = 'Title mymeta';

return $fields;

}
add_filter( '_wp_post_revision_fields', 'os_revision_revision_fields' );

Y, por último, ¿qué pasa si mi meta es un array y no es un texto? Me gustaría que también lo comparase. Pues, para ello, necesitamos hacer dos cosas: primero (y lo que menos me gusta), tocar el core, que no se debería hacer pero en este caso me he visto obligado para que los campos los convierta en un tipo serializado. De esta manera, podremos comparar arrays como string.

Para ello, vamos a wp-includes/revision.php sobre la línea 131 y cambiamos:

- if ( normalize_whitespace( $post->$field ) != normalize_whitespace( $last_revision->$field ) ) {

por

+ if ( normalize_whitespace( serialize($post->$field) ) != normalize_whitespace( serialize($last_revision->$field) ) ) {

Ya, por último, solo tenemos que dar formato a nuestro meta que nos llegará como un array y transformarlo en string para que sea comparable dentro de la clase wp-diff de WordPress. Esto se puede configurar al gusto de cada uno. En mi caso, al tener un array multidimensional que contiene el title, la url y el size de un fichero, lo filtro de esta manera:

function os_diff_custom_documento($compare) {
if(is_array($compare)) {
$output = '';
foreach ($compare as $document) {
$output .= 'Title:'.$document['title'].' Url:'.$document['url'].' Size:'.$document['size'].'
';
}
return $output;
}else {
return $compare;
}

}
add_filter( '_wp_post_revision_field_custom_documento' , 'os_diff_custom_documento' );

Una vez hecho esto, ya podemos hacer pruebas con nuestro post y jugar con los diferentes campos para ver si se están versionando correctamente.

Ahora bien, ¿qué pasa con las taxonomías? ¿Se pueden versionar? Pues a priori parece que no (que nos vamos a tener que buscar la vida), pero con un pequeño truco sí que es posible. Así que dejo pendiente para un próximo post explicar cómo se pueden versionar taxonomías de una publicación, aunque la idea es basarnos en un sistema similar de metas como el actual.

Espero que os sea de utilidad y si alguien tiene un sistema mejor, por favor, estoy abierto a opiniones y me encantaría escucharlas.

También querría hacer mención a John Blackbourn https://johnblackbourn.com/post-meta-revisions-wordpress del cual copie parte del código y me mostró un poco el camino hacia el sistema actual.