Order Posts by Taxonomy and Meta Value

In your code, there is

if ( isset( $wp_query->query['orderby'] ) && $taxonomy == $wp_query->query['orderby'] 

that make your code not work with multiple order fields, because if you set

'orderby' => 'taxonomy_cat meta_value'

then $taxonomy == $wp_query->query['orderby'] is never true.

In addition, I find that looping all taxonomy only with the aim to find the taxonomy to order by is low performant and not very reliable.

So I edited your code a little: when you want to order for a taxonomy your arg shouldbe something like:

'orderby' => 'taxonomy.taxonomy_cat'

thanks to the 'taxonomy.' is easy recognize the taxonomy and there is no need to loop through all taxonomies.

After that, WordPress allow to pass only one argument as 'order' argument, so when you pass multiple 'orderby' arguments, the result can be not the expected one.

So I edited your code to accept an additional argument: 'ordertax'. Sure this is optional, so if you not pass it, the ‘order’ argument is used. It is also ignored if 'orderby' does not contain any taxonomy argument.
If you use multiple 'orderby' arguemnts without passing 'ordertax', all the arguments are order based on 'order' argument (or default ‘DESC’), so using

'orderby' => 'taxonomy.taxonomy_cat meta_value',
'order' => 'ASC'

means "ORDERBY taxonomy_cat ASC, meta_value ASC", using

'orderby' => 'taxonomy.taxonomy_cat meta_value',
'ordertax' => 'DESC'
'order' => 'ASC'

means "ORDERBY taxonomy_cat DESC, meta_value ASC".

Now here the code:

function orderby_tax_clauses( $clauses, $wp_query ) {
  $orderby_arg = $wp_query->get('orderby');
  if ( ! empty( $orderby_arg ) && substr_count( $orderby_arg, 'taxonomy.' ) ) {
    global $wpdb;
    $bytax = "GROUP_CONCAT({$wpdb->terms}.name ORDER BY name ASC)";
    $array = explode( ' ', $orderby_arg ); 
    if ( ! isset( $array[1] ) ) {
      $array = array( $bytax, "{$wpdb->posts}.post_date" );
      $taxonomy = str_replace( 'taxonomy.', '', $orderby_arg );
    } else {
      foreach ( $array as $i => $t ) {
        if ( substr_count( $t, 'taxonomy.' ) )  {
          $taxonomy = str_replace( 'taxonomy.', '', $t );
          $array[$i] = $bytax;
        } elseif ( $t === 'meta_value' || $t === 'meta_value_num' ) {
          $cast = ( $t === 'meta_value_num' ) ? 'SIGNED' : 'CHAR';
          $array[$i] = "CAST( {$wpdb->postmeta}.meta_value AS {$cast} )";
        } else {
          $array[$i] = "{$wpdb->posts}.{$t}";
        }
      }
    }
    $order = strtoupper( $wp_query->get('order') ) === 'ASC' ? ' ASC' : ' DESC';
    $ot = strtoupper( $wp_query->get('ordertax') );
    $ordertax = $ot === 'DESC' || $ot === 'ASC' ? " $ot" : " $order";
    $clauses['orderby'] = implode(', ',
      array_map( function($a) use ( $ordertax, $order ) {
        return ( strpos($a, 'GROUP_CONCAT') === 0 ) ? $a . $ordertax : $a . $order;
      }, $array )
    );
    $clauses['join'] .= " LEFT OUTER JOIN {$wpdb->term_relationships} ";
    $clauses['join'] .= "ON {$wpdb->posts}.ID = {$wpdb->term_relationships}.object_id";
    $clauses['join'] .= " LEFT OUTER JOIN {$wpdb->term_taxonomy} ";
    $clauses['join'] .= "USING (term_taxonomy_id)";
    $clauses['join'] .= " LEFT OUTER JOIN {$wpdb->terms} USING (term_id)";
    $clauses['groupby'] = "object_id";
    $clauses['where'] .= " AND (taxonomy = '{$taxonomy}' OR taxonomy IS NULL)";
  }
  return $clauses;
}

Use it like this:

$args = array(
  'post_type'      => 'custom_post_type',
  'posts_per_page' => -1,
  'meta_key'       => '_EventStartDate',
  'orderby'        => 'taxonomy.taxonomy_cat meta_value',
  'ordertax'       => 'ASC',
  'order'          => 'DESC'
);

add_filter( 'posts_clauses', 'orderby_tax_clauses', 10, 2 );
$the_query = new WP_Query( $args );
remove_filter( 'posts_clauses', 'orderby_tax_clauses', 10, 2 );

Note that use my code (just like the yours) with a WP_Query containing a taxonomy query may break things… for this reason is important remove the filter after having used it.

Leave a Comment