Estoy pasando la web de un cliente desde Joomla a WordPress y tiene una sección tipo glosario con navegación alfabética. Estaba utilizando categorías “a, b, c, etc” para obtener los términos de cada letra. ¿Por qué asignar manualmente una categoría “c” para un post cuyo título comienza por “c” y es un término del glosario? Me dije, tiene que haber una mejor opción. Y me topé con esta pregunta en WPSE: How to build a directory with indexes in WordPress. Una de las respuestas, la que utiliza un taxonomía (i.e. categoría) “escondida” me convenció definitivamente.

He tomando esta idea y la he desarrollado un poco. El proceso consiste en crear una taxonomía que no se mostrará en la pantalla de edición del post (está “escondida”) y sus términos (a, b, c, etc) se asignarán de forma automática cuándo se guarda el post. En combinación con un custom post type el resultado es excelente para crear un directorio tipo glosario en el que se listan las entradas alfabéticamente.

1

Registro del custom post type

Creamos un tipo de post para gestionar los términos del glosario alfabético de forma separada y que no se mezcle con el resto de entradas, por ejemplo del blog. He llamado a este tipo de post “Terms” (Términos).

add_action( 'init', 'cybglossary_init' );
function cybglossary_init() {
     
    $labels = array(
        'name'               => _x( 'Terms', 'post type general name', 'cybglossary' ),
        'singular_name'      => _x( 'Term', 'post type singular name', 'cybglossary' ),
        'add_new_item'       => __( 'Add New Term', 'cybglossary' ),
        'new_item'           => __( 'New Term', 'cybglossary' ),
        'edit_item'          => __( 'Edit Term', 'cybglossary' ),
        'view_item'          => __( 'View Term', 'cybglossary' ),
        'all_items'          => __( 'All Term', 'cybglossary' ),
        'search_items'       => __( 'Search Terms', 'cybglossary' ),
        'not_found'          => __( 'No terms found.', 'cybglossary' ),
        'not_found_in_trash' => __( 'No terms found in Trash.', 'cybglossary' )
    );
 
    $args = array(
        'labels'             => $labels,
        'public'             => true,
        'rewrite'            => array( 'slug' => _x('terms', 'post type rewrite slug', 'cybglossary' ) ),
        'capability_type'    => 'post',
        'has_archive'        => true,
        'supports'           => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'comments' )
    );
 
    register_post_type( 'term', $args );
    
}
2

Crear una taxonomía escondida

En este paso creamos una taxonomía “escondida” con el argumento show_ui => false, es decir, que no se va a mostrar un área de administración para la taxonomía ni se va a mostrar el área de selección en la pantalla de edición del post. La asignación de cada entrada del glosario con el elemento de la taxonomía se hará de forma automáctica en el siguiente paso. El siguiente código se añade dentro del action init del paso anterior.

if( ! taxonomy_exists( 'glossary' ) ) {
    $args = array(
        'show_ui'    => false,
        'rewrite'    => _x('glossary', 'taxonomy URL slug', 'cybglossary')
     );
     register_taxonomy( 'glossary', array('term'), $args );
}
3

Asignar la letra del abecedario automáticamente

Aunque pueda parecer que asignar de forma manual el post a una letra del alfabeto es algo trivial, si nos lo ahorramos mejor. Existe una tendencia global de ahorrar clics y elementos a la hora de escribir y publicar contenido; y tiene su lógica. Es realmente agradable que se simplifiquen los pasos y que el proceso editorial se haga cada vez más fácil y simple.

add_action('save_post','cyb_set_first_letter');
function cyb_set_first_letter( $post_id ){
 
    // skip autosave
    if( defined('DOING_AUTOSAVE') && DOING_AUTOSAVE ) {
         return;
    }
 
    // limit to term post type
    if( isset($_POST['post_type']) && 'term' != $_POST['post_type'] ) {
         return;
    }
    
     // Check permissions
    if( !current_user_can('edit_post', $post_id ) ) {
        return;
    }
 
    //Assign first letter of the title as glossary term
    if( isset($_POST['post_title']) ) {
        //TODO: skip no-sense words, characters, etc
    	$glossary_term = mb_strtolower( mb_substr( $_POST['post_title'], 0, 1 ) );
    	wp_set_post_terms(
		$post_id,
		$glossary_term,
		'glossary'
        );
    }
 
    //delete the transient that is storing the alphabet letters
    delete_transient('cyb_archive_alphabet');
 
}
4

Cambiar el orden de las páginas de archivo

Las páginas de archivo, dónde se muestra el listado de posts, el “blog” del tipo de post estándar para que te hagas una día, ordena los posts según la fecha de publicación en orden descendiente, del más nuevo al más antiguo.

En un glosario de términos nos interesa más ordenar alfabéticamente según el título, ¿no?. Así que vamos a utilizar el action pre_get_posts para cambiar el orden del query principal cuándo estemos en el archivo de nuestro custom post type, que le habíamos llamado term. También lo haremos para las páginas de archivo de la taxonomía glossary que hemos asociado al custom post type:

// Set ascendent order by title in "terms" post type
// and glossary taxomony archives
add_action( 'pre_get_posts', function( $query ) {
  if( ! $query->is_admin && $query->is_main_query()
    && (
      $query->is_tax( 'glossary' ) || $query->is_post_type_archive( 'term' )
    )
  ) {
    $query->set( 'order', 'ASC' );
    $query->set( 'orderby', 'title' );
  }
} );
5

Cambiar el orden de los links previo/siguiente

Al igual que en las páginas de archivo, los enlaces hacia el post siguiente y el post anterior se seleccionan en base a la fecha de publicación.

Vamos a cambiarlo también por un orden según el título alfabéticametne. Utilizaremos los filtros get_next_post_sort y get_previous_post_sort junto a los filtros get_next_post_where y get_previous_post_where:

// Get next/prev post sorted by title
// See http://wordpress.stackexchange.com/questions/166932/how-to-get-next-and-previous-post-links-alphabetically-by-title-across-post-ty
add_filter('get_next_post_sort', 'filter_next_post_sort');
function filter_next_post_sort($sort) {
    if ( !is_main_query() || !is_singular('term') ) {
    
        return $sort;
        
    }
    $sort = "ORDER BY p.post_title ASC LIMIT 1";
    return $sort;
    
}
add_filter('get_next_post_where',  'filter_next_post_where');
function filter_next_post_where($where) {
    global $wpdb;
    if ( !is_main_query() || !is_singular('term') ) {
    
      return $where;
      
    }
    
    $the_post = get_post( get_the_ID() );
    $where = $wpdb->prepare("WHERE p.post_title > '%s' AND p.post_type = '". $the_post->post_type ."' AND p.post_status = 'publish'",$the_post->post_title);
    return $where;
}
add_filter('get_previous_post_sort',  'filter_previous_post_sort');
function filter_previous_post_sort($sort) {
    if ( !is_main_query() || !is_singular('term') ) {
    
        return $sort;
        
    }
    $sort = "ORDER BY p.post_title DESC LIMIT 1";
    return $sort;
    
}
add_filter('get_previous_post_where', 'filter_previous_post_where');
function filter_previous_post_where($where) {
    global $wpdb;
    if ( !is_main_query() || !is_singular('term') ) {
    
      return $where;
      
    }
    
    $the_post = get_post( get_the_ID() );
    
    $where = $wpdb->prepare("WHERE p.post_title < '%s' AND p.post_type = '". $the_post->post_type ."' AND p.post_status = 'publish'",$the_post->post_title);
    return $where;
    
}
6

Crear el menú alfabético

Con la siguiente función se obtiene un menú con las letras del abecedario enlazadas al archivo de esa letra en la taxonomía “glossary”, archivo que contendrá un listado de los posts que comienzan con esa letra.

Para evitar mostrar enlaces a letras que no contenga ningún post, se obtienen las que tienen posts y se almacena el resultado en un transient, transient que no se regenerará hasta que no se guarde/actualice una nueva entrada de nuestro glosario alfabético. Esta solución no me convence mucho y estoy estudiando otras opciones.

function cyb_the_glossary_menu() {

    $taxonomy ='glossary';

    // save the terms that have posts in an array as a transient
    if( false === ( $alphabet = get_transient('cyb_archive_alphabet') ) ) {

        // It wasn't there, so regenerate the data and save the transient
        $terms = get_terms($taxonomy);
        $alphabet = array();

        if($terms){
            foreach($terms as $term){
                 $alphabet[]= $term->slug;
            }
        }

        set_transient('cyb_archive_alphabet', $alphabet );
     }
     ?>

     <ul id="alphabet-menu">
         <?php
         foreach(range('a','z') as $i) {
 
             $current = ($i == get_query_var($taxonomy)) ? "current-menu-item" : "menu-item";
             if(in_array( $i, $alphabet )) {
                 printf('<li class="az-char %s"><a href="%s">%s</a></li>', $current, get_term_link( $i, $taxonomy ), $i );
             } else {
                 printf('<li class="az-char %s">%s</li>', $current, $i );
             }
 
          }
          ?>
     </ul>

<?php
}

Ejecuta la función cyb_the_glossary_menu() en la parte de tu tema dónde quieras que aparezca el menú alfabético.

Creo que la solución planteada en este tutorial es excelente para crear glosarios alfabéticos en WordPress, aunque no es válida para todos los casos. Por ejemplo, no es válido si quieres que posts como “El viento del sur” y “Un viento del sur” aparezcan en la “V”, para este caso necesitaría algunos retoques. Lo he puesto todo en forma de plugin que puedes descargar desde Github:

CYB Glossary en Github

Asignar letras del abecedario a posts antiguos

Si tienes antiguos posts que quieres asignar a la nueva taxonomía del glosario, puedes ejecutar este código. Recuerda utilizarlo una sola vez y cambiar el argumento post_type por el valor que necesites.

add_action('init','cyb_run_once');
//create array from existing posts
function cyb_run_once(){

    $taxonomy = 'glossary';

    $alphabet = array();
    //get all posts of "post" type. Change for your needs
    $posts = get_posts( array('numberposts' => -1, 'post_type' => 'post' ) );

    foreach($posts as $p) :  
        //set term as first letter of post title, lower case
        wp_set_post_terms( $p->ID, mb_strtolower(mb_substr($p->post_title, 0, 1)), $taxonomy );
    endforeach;     
}
  • Superpels

    Buenos días Juan.
    Excelente trabajo, pero ¿has eliminado el plugin de GitHub?

    • Ooops!! Hace unos días lo cambié de ubicación y no actualicé el post. Lo hago ahora mismo.

  • JOSE REX

    se puede hacer lo mismo pero para listar solo categorias?

  • Luis

    Ok, ya logré que en el home me muestre el listado alfabético, pero ahora cómo hago para que al dar click en una letra, abra un archivo con la lista de sus posts ?

    • Hola Luis, ¿has visto el punto 6? Ahí se construye un menu alfabético completo con cada letra enlazada al listado de posts correspondiente. Parece que te aburriste leyendo antes de llegar a este punto, jejeje.

  • Pancho

    Hola Cybmeta, primero de todo dartelas graciaspor compartir este código, lo he probado pero solo me funciona la primera vez. Después introduzco nuevos términos pero no aparecen marcadas las letras del abecedario que corresponderían. Es como si el set_trasient no me funcionara. No se si este post es muy antiguo para que me contestes.
    Muchas gracias

    • Por aquí funciona perfectamente. ¿Puedes dar más detalles sobre lo que no funciona? ¿Alguna información de debug?

    • Pancho

      Muchas gracias Juan, al final parece que lo he resuelto.No se que pasaba, empecé por el principio a copiar las funciones y ahora parece que al fin funciona. Pero la verdad que no encontré el fallo, aparentemente hice lo mismo. Es como si no me estuviera acualizando el trasient. Muchas gracias por responder…y por el tutorial!!!!