Listar posts de dos categorías en WordPress

¿Qué pasa si queremos mostrar los posts que pertenezcan a dos categorías a la vez? por ejemplo, mostrar los posts que sean de “PHP” y de “Noticias” o “PHP” y “Ejemplos”.

La forma de hacerlo es fácil, primero debemos crearnos la estructura del permalink y que WordPress lo reconozco, en este caso será /categories/slug-category-A/slug-category-B. Para ello tenemos que dar de alta esta estructura el el $wp_rewrite y añadir la variable cats en los parámetros tratados por WordPress.

// Crea la estructura para el rewrite
function categories_rewrite_rules() {
  global $wp_rewrite;
  $rewrite_tag = '%categories%';
  // En la variable cats se almacenará 'slug-category-A/slug-category-B' que luego trataremos
  $wp_rewrite->add_rewrite_tag( $rewrite_tag, 'categories/([^/]+/[^/]+)', 'cats=' );
  $rewrite_keywords_structure = $wp_rewrite->root . "$rewrite_tag/";
  $new_rule = $wp_rewrite->generate_rewrite_rules( $rewrite_keywords_structure );
  $wp_rewrite->rules = $new_rule + $wp_rewrite->rules;
  return $wp_rewrite->rules;
}

// Permitimos que la variable cats se tenga en cuenta en las queries (URL) de WordPress
function categories_variables( $public_query_vars ) {
  $public_query_vars[] = 'cats';
  return $public_query_vars;
}

// Hacemos un flush para que tenga en cuenta la nueva regla en el rewrite
function me_flush_rewrite_rules() {
  global $wp_rewrite;
  $wp_rewrite->flush_rules();
}

add_action( 'init', 'me_flush_rewrite_rules' );
add_action( 'generate_rewrite_rules', 'categories_rewrite_rules' );
add_filter( 'query_vars', 'categories_variables' );

Vale, ya tenemos creada la estructura, ahora solo falta modificar la query para que tenga en cuenta la nueva condición: que el post tenga la categoría A y la categoría B, para lo cual modificaremos el join de la query añadiendo un left join para cada categoría

add_filter( 'posts_join' , 'categories_join' );

function categories_join( $join ) {
  global $wpdb, $wp_query;
  if (get_query_var('cats')) {
    // Recuperamos los slugs de las categorias
    $cats =  explode('/', $wp_query->query_vars['cats']);
    $ids = array();
    // Por cada slug recuperamos la categoría y añadimos left join
    foreach($cats as $i=>$c) {
      $obj = get_category_by_slug($c);
      if ($obj) {
        $ids[] = $obj->term_id;
        $join .= " JOIN {$wpdb->term_relationships} r{$i} on r{$i}.object_id = {$wpdb->posts}.ID and r{$i}.term_taxonomy_id = {$obj->term_id} ";
      }
    }
  }

  return $join;
}

Y ya está, si accedieramos a nuestro blog http://miblog.com/categories/php/noticias veríamos los posts que están dentro de PHP y Noticias, con su paginación y demás.

Si por un casual queremos utilizar una template en particular para esto, tan solo deberemos añadir lo siguiente a nuestro functions.php:

function categories_template() {
    if (get_query_var('cats')) {
        include (TEMPLATEPATH . '/category-videos.php');
        exit;
    }
}
add_action('template_redirect', 'categories_template');

O si queremos obtener el título de las categorías, podremos crearnos una función que nos lo devuelva:

function get_cats_names() {

  if (get_query_var('cats')) {
    $cats =  explode('/', get_query_var('cats'));
    $names = array();
    foreach($cats as $i=>$c) {
      $obj = get_category_by_slug($c);
      if ($obj) {
        $names[] = $obj->name;
      }
    }
    return implode(' | ', $names);
  }
  return '';
}