Woocommerce: order by custom taxonomy - wordpress

I'm having the following problem. I have set up a Wordpress site with WooCommerce to serve a webshop with only books.
I have created some product attributes that are based on taxonomies like 'publisher' and 'author' (as multiple products can share an author or a publisher)
I would like to be able to sort my products not only on the Woocommerce default fields like 'title' and 'price' but also on these taxonomy fields.
Say for example:
order by Author ASC or order by publisher DESC
As far as I have discovered there is no way to do this with Wordpress core functions. Some say this is because it makes no sense to sort by taxonomy fields, but with the above example I can't understand why you don't want to sort by for example author.
I have played around with sortby meta_value, but this is just querying the direct postmeta, not the taxonomies.
I have knowledge of php, so any solutions involving additional code in my functions.php file will do.

I found out a way how to solve this:
https://gist.github.com/jayarnielsen/12f3a586900aa6759639
I slightly modified the code so it equals the way Woocommerce uses to sort by the system fields "title" and "price" which is adding a url query-param named "sortby" a value structured like this: "field"-"direction"
So to sort by a product property called "pa_tax1" ascending you'll need to add to the url: sortby=pa_tax1-asc
add_filter('posts_clauses', 'posts_clauses_with_tax', 10, 2);
function posts_clauses_with_tax( $clauses, $wp_query ) {
global $wpdb;
// Array of taxonomies you want to sort on
// in case of Woocommerce Properties, be sure to add the pa_ prefix
$taxonomies = array('pa_tax1', 'pa_tax2', 'pa_tax3');
// If no orderby query param is found, do nothing
if( !isset($wp_query->query['orderby']) ) {
return $clauses;
}
// Explode the orderby query-param
$orderQuery = explode('-', $wp_query->query['orderby']);
$orderBy = [];
$orderBy['field'] = $orderQuery[0];
$orderBy['direction'] = (isset($orderQuery[1])) ? strtoupper($orderQuery[1]) : 'ASC';
// Only add clauses, if the sortby field is in our array
if( in_array($orderBy['field'], $taxonomies) ) {
$clauses['join'] .= "
LEFT OUTER JOIN {$wpdb->term_relationships} AS rel2 ON {$wpdb->posts}.ID = rel2.object_id
LEFT OUTER JOIN {$wpdb->term_taxonomy} AS tax2 ON rel2.term_taxonomy_id = tax2.term_taxonomy_id
LEFT OUTER JOIN {$wpdb->terms} USING (term_id)
";
$clauses['where'] .= " AND (taxonomy = '".$orderBy['field']."' OR taxonomy IS NULL)";
$clauses['groupby'] = "rel2.object_id";
$clauses['orderby'] = "GROUP_CONCAT({$wpdb->terms}.name ORDER BY name ASC) ";
$clauses['orderby'] .= ( 'ASC' == strtoupper( $orderBy['direction'] ) ) ? 'ASC' : 'DESC';
return $clauses;
}
else {
return $clauses;
}
}

Related

WooCommerce make Categories column sortable on Product admin page

I want to make the Categories column sortable on the Product admin page that is here:
/wp-admin/edit.php?post_type=product
I had a real hard time figuring this out... there was not a lot of information on how to do it!
Simply adding add_filter manage_edit-product_sortable_columns was not sufficient.
See my answer below for solution.
First thing we do is make the Categories header at the top of the columns clickable for sorting using the filter manage_edit-product_sortable_columns
Then we need to actually sort the categories in the columns using the filter posts_clauses. This is actually pretty complicated and the answer in this linked post was very helpful (the code is basically copied and pasted with a few changes): Make Featured column sortable in Woocommerce admin products list
// make header of column clickable for sort
add_filter('manage_edit-product_sortable_columns', 'abcxyz_sortable_product_columns_header');
function abcxyz_sortable_product_columns_header( $sortable_columns ) {
$sortable_columns['product_cat'] = 'product_cat';
return $sortable_columns;
}
// sort the categories column when clicked
add_filter('posts_clauses', 'abcxyz_sortable_product_column_product_cat', 11, 2 );
function abcxyz_sortable_product_column_product_cat( $clauses, $wp_query ) {
global $wpdb;
$taxonomy = 'product_cat';
$term = 'product_cat';
if ( isset( $wp_query->query['orderby'] ) && $term == $wp_query->query['orderby'] ) {
$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 = '{$taxonomy}' OR taxonomy IS NULL)";
$clauses['groupby'] = "object_id";
$clauses['orderby'] = "GROUP_CONCAT({$wpdb->terms}.name ORDER BY name ASC) ";
$clauses['orderby'] .= ( 'ASC' == strtoupper( $wp_query->get('order') ) ) ? 'ASC' : 'DESC';
}
return $clauses;
}

How to manipulate the WooCommerce order key by removing the "wc_" prefix?

I would like to manipulate the order key by removing wc_ from it.
Problem is, no matter what I do, wc_ is not removed.
Is there any way to do this?
add_filter( 'woocommerce_generate_order_key', 'woocommerce_generate_custom_order_key', 10, 1 );
function woocommerce_generate_custom_order_key($order_key){
$order_key = str_replace( 'wc_', '', $order_key );
return $order_key;
}
The _order_key (Order Key) and post_id (Order ID) fields are different.
The order number you see in the WooCommerce backend is the post_id
field of the wp_posts table of the Wordpress database.
The order key instead refers to the _order_key (meta_key) field of the wp_postmeta table of the Wordpress database.
The woocommerce_generate_order_key filter is documented here and as you can see you cannot use it to remove the wc_ prefix from the _order_key field of the order. Here is the extract of the source code:
/**
* Generate an order key with prefix.
*
* #since 3.5.4
* #param string $key Order key without a prefix. By default generates a 13 digit secret.
* #return string The order key.
*/
function wc_generate_order_key( $key = '' ) {
if ( '' === $key ) {
$key = wp_generate_password( 13, false );
}
return 'wc_' . apply_filters( 'woocommerce_generate_order_key', 'order_' . $key );
}
If you want to manipulate the WooCommerce order key you have to use the set_order_key() method of the WC_Order class. Here you find the documentation.
You will also need to update the order post_password field in the wp_posts table.
So, for example, if you wanted to set a custom order key (removing the wc_ prefix) for each new order you could use this function:
// removes the "wc_" prefix from the WooCommerce order key field for each new order
add_action( 'woocommerce_new_order', 'set_custom_order_key', 99, 2 );
function set_custom_order_key( $order_id, $order ) {
// get the order object
$order = wc_get_order( $order_id );
// gets the current order key
$order_key = $order->get_order_key();
// remove the "wc_" prefix
$new_order_key = str_replace( 'wc_', '', $order_key );
// updates the "_order_key" field of the "wp_postmeta" table
$order->set_order_key( $new_order_key );
$order->save();
// updates the "post_password" field of the "wp_posts" table
$post = get_post( $order_id );
$post->post_password = $new_order_key;
wp_update_post( $post );
}
The code has been tested and works. Add it to your active theme's functions.php.
If you want to manipulate the WooCommerce order id you have to use the woocommerce_order_number filter. The documentation can be found here.
You can find some examples here:
Adding suffix and prefix to WooCommerce order number without using a plugin
Woocommerce for Wordpress: How to modify the order number/id?
Add in a custom prefix and suffix to WooCommerce Order Number

WP REST API orderby meta_value

Need to be able to sort the results of a REST API custom post query by a meta value.
Having difficulty doing so.
I have made my post type available to the REST API and can order by the Date, Title, etc...
But when I try the Post Meta it doesn't work.
I have added the following code to try and enable the functionality but defaults to ordering by date.
function my_add_meta_vars ($current_vars) {
$current_vars = array_merge ($current_vars, array('meta_key', 'meta_value'));
return $current_vars;
}
add_filter ('query_vars', 'my_add_meta_vars');
add_filter ('rest_query_vars', 'my_add_meta_vars');
My REST API query is
mysite.com/wp-json/wp/v2/hh_equipment?filter[orderby]=meta_value_num&meta_key=equipment_price&order=desc
I have tried following the instructions here to no avail.
Running WordPress 4.8 and tried testing on 4.7 to no avail
I've got it working with the rest_' . $post_type . '_collection_params filter and rest_' . $post_type . '_query filter like so (change $post_type to needed post type slug):
// Add meta your meta field to the allowed values of the REST API orderby parameter
add_filter(
'rest_' . $post_type . '_collection_params',
function( $params ) {
$params['orderby']['enum'][] = 'YOUR_META_KEY';
return $params;
},
10,
1
);
// Manipulate query
add_filter(
'rest_' . $post_type . '_query',
function ( $args, $request ) {
$order_by = $request->get_param( 'orderby' );
if ( isset( $order_by ) && 'YOUR_META_KEY' === $order_by ) {
$args['meta_key'] = $order_by;
$args['orderby'] = 'meta_value'; // user 'meta_value_num' for numerical fields
}
return $args;
},
10,
2
);
The first filter adds your meta field to the possible values of the ordeby parameters, as by default REST API supports only: author, date, id, include, modified, parent, relevance, slug, include_slugs, title (check the ordeby param in the WP REST API handbook)
The second filter allows you to manipulate the query that returns the results when you have your meta key inside the orderby. Here we need to reset orderby to 'meta_value' or 'meta_value_num' (read more about this in WP Query class description) and set the meta key to your custom field key.
Refer below method,
I modified the existing routes to add a new args entry which validates the meta_key values which are permitted. No need to modify the rest query vars this way either.
add_filter('rest_endpoints', function ($routes) {
// I'm modifying multiple types here, you won't need the loop if you're just doing posts
foreach (['some', 'types'] as $type) {
if (!($route =& $routes['/wp/v2/' . $type])) {
continue;
}
// Allow ordering by my meta value
$route[0]['args']['orderby']['enum'][] = 'meta_value_num';
// Allow only the meta keys that I want
$route[0]['args']['meta_key'] = array(
'description' => 'The meta key to query.',
'type' => 'string',
'enum' => ['my_meta_key', 'another key'],
'validate_callback' => 'rest_validate_request_arg',
);
}
return $routes;
});
REF: https://github.com/WP-API/WP-API/issues/2308

Woocommerce order by custom callback function

I added a new sorting option to Woocommerce, which would sort by the lowest price. All my prices are stored in a custom attribute, along with some other serialized data. What I want is to have a callback function which would unserialize this data, check for the lowest price and sort by that price.
Every post I saw about custom ordering options, makes use of the woocommerce_get_catalog_ordering_args filter to add orderby arguments to WP_Query, but I haven't seen any way to sort the results of the returned query via a callback function.
Is there any way to achieve this? I was looking at woocommerce_after_product_ordering based on the name, but couldn't find much info about it. Any help would be much appreciated!
function my_woocommerce_product_sorting($orderby) {
$orderby['price_asc'] = "Sort by cheapest";
$orderby['price_desc'] = "Sort by most expensive";
return $orderby;
}
add_filter("woocommerce_catalog_orderby", "my_woocommerce_product_sorting", 20);
I think it will be best solution when you store custom price with separate new meta key like wooocommerce store product price in post meta name _price and value with numeric price.
mysql order by serialized data?
When you store separated then it will change wp query args like :
function my_woocommerce_get_catalog_ordering_args( $args ){
$order_by = isset( $_GET['orderby'] ) ? wc_clean( $_GET['orderby'] ) : $args['orderby'];
if( $order_by == 'price_desc' || $order_by == 'price_asc' ){ // Check match to custom filter
$args['orderby'] = "meta_value_num ID";
$args['order'] = $order_by == 'price_desc' ? 'DESC' : 'ASC';
$args['meta_key'] = '_my_custom_price'; // Put your price custom post meta price key name
}
return $args;
}
add_filter( 'woocommerce_get_catalog_ordering_args', 'my_woocommerce_get_catalog_ordering_args' );

sort wordpress posts by title, ignore articles like “the”, “a”, “an”

I'm sorting my posts alphabetically by Title, like so:
<?php
{
$posts = get_posts($query_string .
'&orderby=title&order=asc&posts_per_page=-1');
}
get_template_part( 'loop', 'category' );
?>
I'd like to exclude articles such as "the", "a", and "an" from the sort.
What would be the best way to accomplish this?
Thanks!
I don't know any simple way to do that but you can do this,
For achieving this you need to add a custom meta field to the post. Name it mytitle (say).
For the new posts you add, it is simple, you have to add your modified title(removing a, an, the from the title) in the mytitle custom field in the add posts page.
For old posts it is a bit tricky, you have to write a php code to retrieve the titles of the post remove 'a','an','the' from them using php preg_replace and add it to the postmeta table of your wordpress database using something like this:
<?php //inside loop
$query=INSERT INTO xyz_postmeta (post_id, meta_key, meta_value) VALUES ($postid, 'mytitle' $title);
$wpdb->query('$query'); ?>
where $postid is the post id inside the loop and $title is your modified title.
Now you have updated all the previous posts with custom mytitle field.
Now to display, you have to use a custom loop (not the loop included in the theme).
Here is how you can make a basic custom loop to display posts sorted in order of mytitle.
$querystr = "
SELECT wposts.*
FROM $wpdb->posts wposts, $wpdb->postmeta wpostmeta
WHERE wposts.ID = wpostmeta.post_id
AND wpostmeta.meta_key = 'mytitle'
AND wposts.post_type = 'post'
AND wposts.post_status = 'publish'
ORDER BY wpostmeta.meta_value ASC
";
Now you can execute the query by any means you want. Wordpres provides various methods to do so. Here's a link
For example you can do something like this
$pageposts = $wpdb->get_results($querystr, OBJECT);
foreach ( $pageposts as $pagepost )
{
echo $pagepost->post_title;
//do other stuff to display content, meta etc..
}
You can actually achieve this by manipulating the ORDERBY clause using the posts_orderby_request filter, and using TRIM to ignore articles "the", "a", and "an". Here's an example:
<?php
add_filter( 'posts_orderby_request', 'myproject_title_sort', 10, 2 );
/**
* Ignore opening articles when sorting by Title.
*
* #param string $orderby Order by parameter.
* #param WP_Query $query WP Query object.
*
* #return string
*/
function myproject_title_sort( $orderby, $query ) {
global $wpdb;
// Be sure to add a condition that matches your criteria (post type, archive, etc).
// In this example, we bail early if it's an Admin query, or not a main query.
if ( is_admin() || ! $query->is_main_query() ) {
return $orderby;
}
// This is another check to see if we're on a particular post type archive.
if ( ! $query->is_post_type_archive( 'my-post-type' ) ) {
return $orderby;
}
$title_col = $wpdb->posts . '.post_title';
// Check if we are sorting by post_title.
// You may need to use a separate `pre_get_posts` filter to sort by "title" if not already doing so.
if ( false === strpos( $orderby, $title_col ) ) {
return $orderby;
}
$ignore = "TRIM( LEADING 'the ' FROM LOWER( TRIM( LEADING 'a ' FROM LOWER( TRIM( LEADING 'an ' FROM LOWER( $title_col ) ) ) ) ) )";
$orderby = str_replace( $title_col, $ignore, $orderby );
return $orderby;
}

Resources