Cómo ordenar los contenidos dentro de una taxonomía en WordPress

Publicada en WordPress.

Mira nuestro vídeo

Existe una versión mejor de tu web

Comparte este artículo

Desde Nelio hemos publicado varios plugins en el repositorio de WordPress.org. Algunos de ellos son completamente gratuitos y ofrecen soluciones elegantes a problemas habituales, como, por ejemplo, Nelio Maps, que permite insertar un mapa de Google en tus páginas o entradas creadas con Gutenberg, o Nelio Compare Images, que permite insertar un bloque con el que comparar dos imágenes. Pero también tenemos un par de plugins publicados con una versión premium y que son los que nos dan de comer a fin de mes: Nelio Content y Nelio A/B Testing.

Si echas un vistazo a nuestra web, verás que son precisamente estos dos plugins los que ocupan un lugar destacado, y es que tanto para el uno como para el otro tenemos varias páginas principales, páginas para suscribirte a la versión premium y páginas de documentación. Pues bien, hoy me gustaría explicarte cómo creamos las páginas de documentación de Nelio A/B Testing y Nelio Content y qué hemos hecho para poder ordenar y organizar la ayuda libremente.

Cómo crear un nuevo tipo de contenido

Lo primero que vamos a hacer es crear un nuevo tipo de contenido en WordPress con el que definir cada una de las «entradas» que tendremos en nuestra base de conocimiento. En la siguiente captura, por ejemplo, puedes ver el tipo Testing’s Help, el cual contiene todas las preguntas y respuestas asociadas a la documentación de nuestro plugin Nelio A/B Testing:

Preguntas de Nelio A/B Testing
Ejemplo de una pregunta cualquiera en la documentación de Nelio A/B Testing.

Para crear un tipo de contenido personalizado, lo único que tienes que hacer es usar la función register_post_type de WordPress:

function nelio_add_help_type() {
  $labels = array(
    'name'          => __( 'Questions', 'nelio-help' ),
    'singular_name' => __( 'Question', 'nelio-help' ),
    'menu_name'     => __( 'Testing’s Help', 'nelio-help' ),
    'all_items'     => __( 'All Questions', 'nelio_help' ),
    'add_new_item'     => __( 'Add New Question', 'nelio_help' ),
    'edit_item'     => __( 'Edit Question', 'nelio_help' ),
    'view_item'     => __( 'View Question', 'nelio_help' ),
  );

  register_post_type( 'nab_help', array(
    'capability_type' => 'post',
    'labels'          => $labels,
    'map_meta_cap'    => true,
    'menu_icon'       => 'dashicons-welcome-learn-more',
    'public'          => true,
    'show_in_rest'    => true,
    'supports'        => [ 'title', 'editor', 'author' ],
  ) );
}
add_action( 'init', 'nelio_add_help_type' );

y voilà!, ya lo tienes disponible. Existen plugins como Advanced Custom Fields que te ayudan a crear y personalizar la interfaz de un tipo de contenido personalizado… pero en este caso estamos haciendo algo tan fácil que no merece la pena verlo.

Por cierto, ya sabes que si quieres probar esto en un sitio tuyo, deberás meterlo en un plugin propio tal y como te conté en esta otra entrada.

Cómo crear una nueva taxonomía para organizar contenidos

Todas las preguntas y respuestas que hay en nuestra documentación están organizadas por «Temas» (o Topics en inglés), tal y como puedes ver en la siguiente captura de pantalla:

Escritorio de WordPress con la lista de preguntas de Nelio A/B Testing
Escritorio de WordPress con la lista de preguntas de Nelio A/B Testing.

De hecho, si te fijas bien en la captura anterior, verás que, en realidad, hay dos taxonomías diferentes: Topics y Keywords, las cuales vienen a ser el equivalente a las Categorías y Etiquetas que tienes por defecto en las entradas de WordPress. Pero vaya, que eso es irrelevante ahora mismo.

Estos «Temas» son parte una taxonomía personalizada que hemos creado para nuestro tipo de contenido personalizado. Para añadir una nueva taxonomía a un tipo de contenido de WordPress debes usar la función register_taxonomy de la siguiente forma:

function nelio_add_help_taxonomy() {
  $labels = array(
    'name'          => __( 'Topics', 'nelio-help' ),
    'singular_name' => __( 'Topic', 'nelio-help' ),
    'menu_name'     => __( 'Topics', 'nelio-help' ),
    'all_items'     => __( 'All Topics', 'nelio_help' ),
    /* ... */
  );

  register_taxonomy( 'nab_topic', [ 'nab_help' ], array(
    'hierarchical'      => true,
    'label'             => __( 'Topic', 'nelio-help' ),
    'query_var'         => true,
    'show_admin_column' => false,
    'show_ui'           => true,
    'show_in_rest'      => true,
  ) );
}
add_action( 'init', 'nelio_add_help_taxonomy' );

En esta función especificas, en primer lugar, el nombre de la taxonomía (en el ejemplo, nab_topic), seguido de el o los tipos de contenido a los que afecta (aquí, el que hemos creado antes: nab_help) y, finalmente, la lista de argumentos que sirven para configurar sus propiedades.

El resultado es que ahora tenemos la opción Topics en nuestro tipo de contenido personalizado desde la que podremos crear y gestionar los diferentes temas con los que organizar nuestra ayuda:

Taxonomía personalizada en el escritorio de WordPress
Captura de pantalla de los «temas», nuestra taxonomía personalizada visible desde el escritorio de WordPress.

Cómo ordenar los contenidos de una taxonomía

Y ahora, por fin, llegamos al punto que te ha traído hasta esta entrada. ¿Qué hay que hacer para que los contenidos que tenemos dentro de una taxonomía aparezcan en el orden que nosotros queramos?

Por ejemplo, para la ayuda de Nelio A/B Testing tenemos una pregunta, «¿Qué son los Tests A/B y los Multivariantes?», que aparece en segundo lugar en el tema «Preguntas generales» y en primer lugar en el tema «Creación de tests». ¿Cómo hemos conseguido especificar el orden concreto de cada pregunta dentro de cada tema?

Echando un vistazo a la base de datos…

Las taxonomías en WordPress y sus relaciones con los contenidos de tu web es una información que está distribuida y almacenada en cuatro tablas diferentes de tu base de datos:

  • wp_terms. Incluye una lista con todos los «términos» que has generado dentro de cada taxonomía. Es decir, todas tus categorías y etiquetas están ahí. También están todos los topics y keywords que hemos generado en el ejemplo de hoy (como, por ejemplo, los temas «Preguntas generales» o «Creación de tests», ¿recuerdas?)
  • wp_termmeta. Sirve para añadir información adicional a cada uno de los términos que tenemos en WordPress. Quédate con la idea de que esta es una tabla similar a wp_postmeta, pero ahora mismo no nos interesa para nada.
  • wp_term_taxonomy. Es una tabla que relaciona cada término de wp_terms con la taxonomía a la que pertenece. Es aquí, por ejemplo, donde sabemos que el término «Preguntas generales» con identificador (me lo invento) 12 es de tipo nab_topic.
  • wp_term_relationships. Esta tabla relaciona los términos de nuestras taxonomías (campo term_taxonomy_id) con los contenidos de nuestra web (campo object_id). Lo interesante de esta tabla es que incluye un tercer campo, term_order, el cual parece el candidato perfecto para lo que queremos hacer.

La versión 2.5 de WordPress añadió en la tabla wp_term_relationships el campo numérico term_order, el cual nos permite especificar la posición que un cierto elemento ocupa dentro de una taxonomía. ¿El problema? No parece que WordPress ofrezca ningún mecanismo para (1) definir el valor del campo term_order y (2) usarlo y que los elementos se muestren según ese orden. ¡Vamos a solucionar esto!

Cómo usar la propiedad term_order para ordenar los contenidos de una taxonomía

Supongamos que hemos sido capaces de poner algunos valores «lógicos» en el campo term_order de las preguntas relacionadas con un cierto Tema para, así, definir el orden en que queramos que aparezcan. ¿Cómo le decimos a WordPress que queremos que liste las preguntas ordenándolas por el valor de ese campo?

Captura de pantalla del orden que vemos en la base de datos y el que aparece en la web
El orden que vemos en la base de datos (y que es el que esperaríamos ver) es diferente que el que obtenemos en nuestra web.

Como puedes ver, WordPress, por defecto, no respeta el orden que hemos definido. Por suerte, «obligarle» a ello es extremadamente fácil si usamos el filtro adecuado: posts_orderby permite extender la query que se lanza a la base de datos de WordPress añadiéndole un criterio de ordenación.

add_filter( 'posts_orderby', 'nelio_sort_questions_in_topic', 99, 2 );
function nelio_sort_questions_in_topic( $orderby, $query ) {
  if ( ! nelio_is_topic_tax_query( $query ) ) return;
  global $wpdb;
  return "{$wpdb->term_relationships}.term_order ASC";
}

function nelio_is_topic_tax_query( $query ) {
  if ( empty( $query->tax_query ) ) return;
  if ( empty( $query->tax_query->queries ) ) return;
  return in_array(
    $query->tax_query->queries[0]['taxonomy'],
    [ 'nab_topic' ],
    true
  );
}

Como puedes ver, lo único que hace la función anterior es comprobar (mediante una función auxiliar de ayuda) que la query actual está listando los contenidos de una cierta taxonomía (en nuestro caso, nab_topic) y, si es así, indica que hay que ordenar los resultados por el atributo term_order de la tabla $wpdb->term_relationships. ¿El resultado? Justo lo que queríamos:

Captura de pantalla de los resultados (ordenados) de una taxonomía
Captura de pantalla de los resultados (ordenados) de una taxonomía.

Implementación de una interfaz de usuario para ordenar los contenidos de una taxonomía

Finalmente, sólo nos queda una cosa por hacer. En el apartado anterior te he comentado que íbamos a suponer que habíamos sido capaces de ordenar los contenidos de una cierta taxonomía, sin entrar en detalles de cómo lo hemos conseguido. Quizás editamos directamente el valor de term_order en la base de datos o quizás metimos un campo adicional en el editor que nos permite establecer dicho valor.

En mi opinión, la mejor solución a este problema sería disponer de una interfaz de usuario que me permitiera seleccionar la taxonomía que quiero ordenar y, a continuación, arrastrar y soltar todas las preguntas para establecer su orden. Algo tal que así, vaya:

Interfaz de usuario para ordenar fácilmente los contenidos que tenemos en una taxonomía cualquiera
Interfaz de usuario para ordenar fácilmente los contenidos que tenemos en una taxonomía cualquiera.

Para conseguir el efecto deseado deberemos seguir los siguientes pasos:

  • Registrar una nueva página en el escritorio de WordPress
  • Implementar el método que pintará por pantalla el selector de taxonomía y los contenidos que hay dentro de cada taxonomía
  • Añadir la funcionalidad JavaScript que permita arrastrar y soltar los contenidos para ordenarlos
  • Crear un callback para que, mediante AJAX, podamos guardar la nueva ordenación

Esta interfaz la puedes hacer tan complicada como quieras, apoyándote, si quieres, en el nuevo stack de desarrollo de interfaces de WordPress. Permíteme, pues, que esboce los puntos principales de una implementación sencilla y rápida.

Como te comentaba, lo primero es registrar la página donde meteremos la interfaz. Esto lo podemos hacer fácilmente con add_submenu_page durante la acción admin_menu:

add_action( 'admin_menu', function() {
  add_submenu_page(
    'edit.php?post_type=nab_help',
    'Sort',
    'Sort',
    'edit_others_posts',
    'nab-help-question-sorter',
    __NAMESPACE__ . '\render_question_sorter'
  )
} );

El método de renderizado render_question_sorter es también extremadamente sencillo:

function render_question_sorter() {
  echo '<div class="wrap"><h1>Sort Questions</h1>';

  $terms = get_terms( 'nab_topic' );
  render_select( $terms );
  foreach ( $terms as $term ) {
    render_questions_in_term( $term );
  }
  render_script();

  echo '</div>';
}

Básicamente, lo único que hacemos es recuperar todos los términos que tenemos en la taxonomía nab_topic y luego nos apoyamos en tres funciones auxiliares para (1) renderizar el selector, (2) renderizar las preguntas que hay en cada categoría y (3) añadir un pequeño script para que todo sea dinámico.

Renderizar el selector es tan fácil como iterar sobre cada término y pintar una option:

function render_select( $terms ) {
  echo '<select id="topic">';
  foreach ( $terms as $term ) {
    printf(
      '<option value="%s">%s</option>',
      esc_attr( $term->slug ),
      esc_html( $term->name )
    );
  }
  echo '</select>';
}

Para renderizar las preguntas que tenemos en cada término de la taxonomía tenemos que lanzar una query a la base de datos y pintarlas en un bucle:

function render_questions_in_term( $term ) {
  printf(
    '<div id="%s" class="term-block" data-term-id="%d">',
    esc_attr( $term->slug ),
    esc_attr( $term->term_id )
  );

  echo '<div class="sortable">'
  $query = new WP_Query( array(
    'post_type'     => 'nab_help',
    'post_per_page' => -1,
    'tax_query'     => array(
      array(
        'taxonomy' => 'nab_topic',
        'field'    => 'term_id',
        'terms'    => $term->term_id,
        'orderby'  => 'term_order',
      ),
    ),
  ) );
  while ( $query->have_posts() ) {
    $query->the_post();
    global $post;
    printf(
      '<div class="question" data-question-id="%d">%s</div>',
      esc_attr( $post->ID ),
      esc_html( $post->post_title )
    );
  }
  echo '</div>';

  printf(
    '<input type="button" data-term-id="%s" value="%s" />',
    esc_attr( $term->term_id ),
    esc_attr( 'Save' )
  );
  echo '</div>';
}

Fíjate que esta función renderiza un div que tiene como identificador el slug del $term que estamos pintando y que contiene, por un lado, la lista de preguntas agrupadas en otro div, junto a un botón para guardar que incluye el identificador del $term actual.

Con esto, ya tenemos todo listo para añadirle dinamismo a la página encolando varios scripts: jquery y jquery-ui para poder arrastrar y soltar las preguntas (de ahí que las tengamos en un div con la clase sortable) y metiendo un poco de lógica para mostrar unas preguntas u otras según qué tengamos seleccionado en el select inicial:

( function() {
  // MAKE QUESTIONS SORTABLE
  $( '.sortable' ).sortable();


  // TERM SELECTOR
  const select = document.getElementById( 'topic' );
  const termBlocks = [ ...document.querySelectorAll( '.term-block' ) ];
  select.addEventListener( 'change', showSelectedTermBlock );

  // Helper function
  function showSelectedTermBlock() { /* ... */ }


  // SAVE BUTTONS
  [ ...document.querySelectorAll( '.term-block input[type="button"]' ) ].forEach( addButtonLogic );


  // Helper function
  function addButtonLogic( button ) {
    const termId = button.getAttribute( 'data-term-id' );
    button.addEventListener( 'click', () => {
      button.disabled = true;
      const selector = `div[data-term-id="${ termId }"] .question`;
      const ids = [ ...document.querySelectorAll( selector ) ]
        .map( ( question ) => question.getAttribute( 'data-question-id' );
      $.ajax( {
        url: ajaxurl,
        method: 'POST',
        data: {
          action: 'nelio_save_tax_sorting',
          objectIds: ids,
          termId,
        },
      } ).always( () => button.disable = false );
    }
  }
} )()

Como ves, el script consta de tres partes: primero hace que las preguntas sean ordenables usando la función sortable de jQuery, luego añade un listener al selector para que, cuando cambie su valor, se muestren el conjunto de preguntas que nos interesan (he omitido la función auxiliar por brevedad, pero seguro que sabes cómo va) y, finalmente, añade un listener a los botones de guardado para que se envíe y guarde el orden establecido en la base de datos.

Lo interesante aquí es que este guardado se hace a través de una llamada AJAX, con lo que necesitamos implementar el callback también en PHP:

add_action( 'wp_ajax_nelio_save_tax_sorting', __NAMESPACE__ . '\save_tax_sorting' );
function save_tax_sorting() {
  $term_id = isset( $_POST['termId'] ) ? absint( $_POST['termId'] : 0 );
  if ( ! $term_id ) die();

  $object_ids = isset( $_POST['objectIds'] ) ? $_POST['objectIds'] : [];
  if ( ! is_array( $object_ids ) || empty( $object_ids ) ) die();
  $object_ids = array_values( array_map( 'absint', $object_ids ) );

  global $wpdb;
  foreach ( $object_ids as $order => $object_id ) {
    $wpdb->update(
      $wpdb->term_relationships,
      array( 'term_order' => $order + 1 ),
      array(
        'object_id'        => $object_id,
        'term_taxonomy_id' => $term_id,
      ),
    );
  }
  die();
}

En resumen

Si bien es cierto que WordPress no ofrece un mecanismo por defecto para ordenar como queramos los contenidos de una taxonomía, podemos hacerlo si añadimos un poco de código a nuestra web.

En la entrada de hoy hemos visto que lo único que tenemos que hacer es meter en el atributo term_order de la tabla term_relationships el orden en el que queremos que un contenido aparezca y luego extender WordPress con el filtro posts_orderby para que salgan en el orden especificado en ese campo.

Finalmente, hemos esbozado una pequeña interfaz de usuario que nos permite poblar el campo term_order con una interacción tan fácil como arrastrar y soltar contenidos con el ratón.

Espero que te haya gustado el tutorial de hoy y, si es así, ya sabes: ¡compártelo con tus amigos y pásaselo a tus compañeros! Y, como siempre, si te queda alguna duda, déjamela en los comentarios y te ayudaré.

Imagen destacada de Steve Johnson en Unsplash.

Deja una respuesta

No publicaremos tu correo electrónico. Los campos obligatorios están marcados con: •

He leído y acepto la Política de privacidad de Nelio Software

Al marcar la casilla de aceptación estás dando tu legítimo consentimiento para que tu información personal se almacene en SiteGround y sea usada por Nelio Software con el propósito único de publicar aquí este comentario. Contáctanos para corregir, limitar, eliminar o acceder a tu información.