Let's say there's a product attribute "brand" with the following terms:
- Brand A
- Brand B
- Brand C
Let's also say the menu order of these terms in products > attributes > size have been ordered as such:
- Brand C
- Brand A
- Brand B
How can I sort the products on the shop page by the attribute menu order? So for example:
- Brand C product
- Brand A product
- Brand B product
Assume each product only has a single brand attribute term.
So far I've figured the code will likely involve modifying the query using the pre_get_posts
hook, but I'm having difficulty customizing the order in such a way.
CodePudding user response:
Since get_terms() is returning the order we have set for our attributes we can get those ids and use them as sorting.
//Change taxonomy if you need.
$terms = get_terms( array('taxonomy' => 'pa_brand','fields' => 'ids'));
From there we can use woocommerce_product_query
to modify our query.
And finaly set the order of our query to be by term_id
ASC or DESC.
Here is my solution
function sort_products_by_brand( $q ) {
$terms = get_terms( array('taxonomy' => 'pa_brand','fields' => 'ids'));
$tax_query = $q->get('tax_query');
$tax_query[] = array(
'taxonomy' => 'pa_brand',
'field' => 'term_id',
'terms' => $terms,
);
$q->set( 'tax_query', $tax_query);
$q->set( 'orderby', 'term_id' );
// $q->set( 'order', 'DESC' ); // by default is ASC so uncomment if you want DESC.
}
add_action( 'woocommerce_product_query', 'sort_products_by_brand' );
CodePudding user response:
Honk31's answer worked well for me. I just had to modify it a little bit.
add_action( 'pre_get_posts', 'jwd_modify_product_query' );
function jwd_modify_product_query($query) {
if ( !is_admin() && $query->is_main_query() && is_post_type_archive( 'product' ) ) {
$query->set( 'orderby', 'pa_brand' );
}
}
// https://wordpress.stackexchange.com/a/363654/94213
// Answer by honk31
add_filter('posts_clauses', 'jwd_orderby_tax_clauses', 10, 2 );
function jwd_orderby_tax_clauses($clauses, $wp_query) {
global $wpdb;
$orderby = isset($wp_query->query_vars['orderby']) ? $wp_query->query_vars['orderby'] : false;
if ($orderby && $orderby === 'pa_brand') {
$clauses['join'] .= <<<SQL
LEFT OUTER JOIN {$wpdb->term_relationships} ON {$wpdb->posts}.ID={$wpdb->term_relationships}.object_id
LEFT OUTER JOIN {$wpdb->term_taxonomy} USING (term_taxonomy_id)
LEFT OUTER JOIN {$wpdb->terms} USING (term_id)
SQL;
$clauses['where'] .= " AND (taxonomy = '{$orderby}' OR taxonomy IS NULL)";
$clauses['groupby'] = "object_id";
$clauses['orderby'] = "{$wpdb->terms}.term_order ASC";
$clauses['orderby'] .= ", {$wpdb->posts}.post_name ASC";
}
return $clauses;
}
Another step that is needed is to use the Simple Custom Post Order plugin. The plugin adds a term_order
column to the terms table that the code uses to sort by. Drag and drop to change the terms order in products > attributes > brand. I actually had to move all of the terms around to get the order to sort correctly.
WooCommerce attributes do have a drag & drop functionality already, though. Perhaps it is possible to do something similar without that plugin.