Woocommerce Sort by Attribute Value - php

In my store I have a attribute called "pieces" that phpmyadmin lists as "pa_pieces" in wp_postmeta.
My store lists toys so I want to be able to sort it by the amount of pieces/the attribute. So by this I mean instead of sorting by price from low to high I want to add sort by number of pieces. It should be available on every shop page.
I found the code below online (actually very recent) and added it to functions.php and modified it for my taxonomy name. The name of my sort options shows up, but when I try to sort by it, it just shows "No products were found matching your selection."
I updated my products and every item contains at least 1 piece.
I know woocommerce/wordpress has a filter where I can filter to only show selected numbers of pieces. But with ~500 different values it isn't really an option.
I am thankful for any help.
/**
* Save product attributes to post metadata when a product is saved.
*
* #param int $post_id The post ID.
* #param post $post The post object.
* #param bool $update Whether this is an existing post being updated or not.
*
* Refrence: https://codex.wordpress.org/Plugin_API/Action_Reference/save_post
*/
function wh_save_product_custom_meta($post_id, $post, $update) {
$post_type = get_post_type($post_id);
// If this isn't a 'product' post, don't update it.
if ($post_type != 'product')
return;
if (!empty($_POST['attribute_names']) && !empty($_POST['attribute_values'])) {
$attribute_names = $_POST['attribute_names'];
$attribute_values = $_POST['attribute_values'];
foreach ($attribute_names as $key => $attribute_name) {
switch ($attribute_name) {
//for lenght (int)
case 'pa_length':
if (!empty($attribute_values[$key][0])) {
update_post_meta($post_id, 'pa_length', $attribute_values[$key][0]);
}
break;
default:
break;
}
}
}
}
add_action( 'save_post', 'wh_save_product_custom_meta', 10, 3);
/**
* Main ordering logic for orderby attribute
* Refrence: https://docs.woocommerce.com/document/custom-sorting-options-ascdesc/
*/
add_filter('woocommerce_get_catalog_ordering_args', 'wh_catalog_ordering_args');
function wh_catalog_ordering_args($args) {
global $wp_query;
if (isset($_GET['orderby'])) {
switch ($_GET['orderby']) {
//for attribute/taxonomy=pa_length
case 'pa_length_asc' :
$args['order'] = 'ASC';
$args['meta_key'] = 'pa_length';
$args['orderby'] = 'meta_value_num';
break;
case 'pa_length_desc' :
$args['order'] = 'DESC';
$args['meta_key'] = 'pa_length';
$args['orderby'] = 'meta_value_num';
break;
}
}
return $args;
}
/**
* Lets add the created sorting order to the dropdown list.
* Refrence: http://hookr.io/filters/woocommerce_catalog_orderby/
*/
//To under Default Product Sorting in Dashboard > WooCommerce > Settings > Products > Display.
add_filter( 'woocommerce_default_catalog_orderby_options', 'wh_catalog_orderby' );
add_filter('woocommerce_catalog_orderby', 'wh_catalog_orderby');
function wh_catalog_orderby($sortby) {
$sortby['pa_length_asc'] = 'Sort by pieces: Low - High';
$sortby['pa_length_desc'] = 'Sort by pieces: High - Low';
return $sortby;
}

Related

Export custom meta from Woocommerce to Shipstation

I have a problem with exporting products to shipstation, the fields that are generated using the Product Add-on Ultimate plugin are not exported to shipstation
How can I put together the correct function for exporting additional fields??
// Add this code to your theme functions.php file or a custom plugin
add_filter( 'woocommerce_shipstation_export_custom_field_2', 'shipstation_custom_field_2' );
function shipstation_custom_field_2() {
return '\_meta_key'; // Replace this with the key of your custom field
}
// This is for custom field 3
add_filter( 'woocommerce_shipstation_export_custom_field_3', 'shipstation_custom_field_3' );
function shipstation_custom_field_3() {
return '\_meta_key_2'; // Replace this with the key of your custom field
}
I found this solution in plugin documentation but it doesn't work for me
<?php
/**
* Get add-on metadata from each line item in the order
* #param $order_id
* #param $metakey The add-ons metakey (field label), usually prefixed by an underscore
*/
function prefix_get_addons_metadata_by_key( $order_id, $metakey=false ) {
$order = wc_get_order( $order_id );
$order_line_items = $order->get_items();
foreach( $order_line_items as $line_item ) {
// Here, we can iterate through all the meta for this line item
$all_meta = $line_item->get_meta_data();
if( $all_meta ) {
foreach( $all_meta as $meta ) {
$meta_id = $meta->id;
$meta_key = $meta->key;
$meta_value = $meta->value;
}
}
// Here, we can get the value by a specific metakey
if( $metakey ) {
$meta_value = $line_item->get_meta( $metakey );
}
}
}
This is from the plugin side.

Custom sorting option with filter doesn't work

So, I added those filters listed below for new products sorting option in Woocommerce - by sort status. It works well without any other query parameters ?orderby=abailability but when I add filter to the query it shows no results
(?orderby=availability&type=some_custom_type)
add_filter( 'woocommerce_get_catalog_ordering_args', 'custom_woocommerce_get_catalog_ordering_args' );
function custom_woocommerce_get_catalog_ordering_args($args) {
$orderByValue = isset($_GET['orderby']) ? wc_clean($_GET['orderby']) : apply_filters('woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby'));
if ('availability' === $orderByValue) {
$args['orderby'] = 'meta_value';
$args['order'] = 'ASC';
$args['meta_key'] = '_stock_status';
}
return $args;
}
add_filter('woocommerce_catalog_orderby', 'custom_woocommerce_catalog_orderby');
function custom_woocommerce_catalog_orderby($sortBy) {
$sortBy['availability'] = 'Availability';
return $sortBy;
}
What am I missing here?

WooCommerce Orders: Is it possible to add another column and populate it with custom meta fields from Users?

So short version is that I am trying to add an extra column in the orders page and have it populated with the custom field I have created for the users on the site (The name for the custom field is account_manager)
What I'm trying to achieve is for it display the customers account manager in a column in the orders section. Bit of a strange request I know!
I had a look at a few guides and tutorials regarding columns and meta data but it all seemed to be regarding the orders rather than data from the customer directly.
Any help would be greatly appreciated. I don't mind having a go, I just need a little guidance :)
I've tried this but it's not returning any values
function wc_orders_add_account_manager_column($columns)
{
$new_columns = [];
foreach ( $columns as $column_name => $column_info ) {
$new_columns[ $column_name ] = $column_info;
if ( 'order_status' === $column_name ) { // Change order_status to manage column orders
$new_columns['account_manager'] = 'Account Manager';
}
}
return $new_columns;
}
add_filter( 'manage_edit-shop_order_columns', 'wc_orders_add_account_manager_column', 20 );
/**
* Adds 'account_manager' column content to 'Orders' page
*
* #param string $column name of column being displayed
*/
function wc_orders_add_account_manager_column_content($column)
{
global $post;
$order_id = $post->ID;
// Get an instance of the WC_Order object
$order = wc_get_order($order_id);
// Get the user ID from WC_Order methods
$user_id = $order->get_customer_id(); // or $order->get_customer_id();
$meta = get_user_meta($user_id, 'account_manager', true);
return $meta;
if ( 'account_manager' === $columns ) {
echo $meta ;
} else {
echo "Not Valid!";
}
}
add_action( 'manage_shop_order_posts_custom_column', 'wc_orders_add_account_manager_column_content' );
I'm assuming you have stored this 'account_manager' as an order meta on postmeta table. You need to use manage_edit-shop_order_columns filter hook to add the new column to admin orders page and manage_shop_order_posts_custom_column action hook to populate this column.
/**
* Adds 'account_manager' column header to 'Orders' page immediately after 'Order Status' and before 'Total' column.
*
* #param array $columns
* #return array
*/
function wc_orders_add_account_manager_column($columns)
{
$new_columns = [];
foreach ( $columns as $column_name => $column_info ) {
$new_columns[ $column_name ] = $column_info;
if ( 'order_status' === $column_name ) { // Change order_status to manage column orders
$new_columns['account_manager'] = 'Account Manager';
}
}
return $new_columns;
}
add_filter( 'manage_edit-shop_order_columns', 'wc_orders_add_account_manager_column', 20 );
/**
* Adds 'account_manager' column content to 'Orders' page
*
* #param string $column name of column being displayed
*/
function wc_orders_add_account_manager_column_content($column)
{
global $post;
$order = wc_get_order( $post->ID );
$user_id = $order->get_customer_id(); // or $order->get_customer_id();
$meta = get_user_meta($user_id, 'account_manager', true);
if ( 'account_manager' === $column ) {
echo $meta ? $meta : 'Not Valid!';
}
}
add_action( 'manage_shop_order_posts_custom_column', 'wc_orders_add_account_manager_column_content' );
Tested & It's Working
Note: As I mentioned in the code comments, you can change the order of the columns by changing order_status in this code block:
if ( 'order_status' === $column_name ) {
$new_columns['account_manager'] = 'Account Manager';
}
In this case, our new account_manager column will display after the order_status column

Exclude variations with 2 specific attribute terms from coupon usage in Woocommerce

I need to prevent coupons being used if customer have any specific product variations in their cart with following attribute terms:
attribute_pa_style => swirly
attribute_pa_style => circle
I've looked through the Woocommerce scripts that apply to restricting specific products and specific categories, but can't figure it out with regard to attributes and all coupons.
Any help is appreciated.
This can be done using woocommerce_coupon_is_valid filter hook this way:
add_filter( 'woocommerce_coupon_is_valid', 'check_if_coupons_are_valid', 10, 3 );
function check_if_coupons_are_valid( $is_valid, $coupon, $discount ){
// YOUR ATTRIBUTE SETTINGS BELOW:
$taxonomy = 'pa_style';
$term_slugs = array('swirly', 'circle');
// Loop through cart items and check for backordered items
foreach ( WC()->cart->get_cart() as $cart_item ) {
foreach( $cart_item['variation'] as $attribute => $term_slug ) {
if( $attribute === 'attribute_'.$taxonomy && in_array( $term_slug, $term_slugs ) ) {
$is_valid = false; // attribute found, coupons are not valid
break; // Stop and exit from the loop
}
}
}
return $is_valid;
}
Code goes in function.php file of your active child theme (or active theme). Tested and works.
You could also check each product in the cart and restrict it from the coupon with woocommerce_coupon_is_valid_for_product
/**
* exclude a product from an coupon by attribute value
*/
add_filter('woocommerce_coupon_is_valid_for_product', 'exclude_product_from_coupon_by_attribute', 12, 4);
function exclude_product_from_coupon_by_attribute($valid, $product, $coupon, $values ){
/**
* attribute Settings
*/
$taxonomy = 'pa_saison';
$term_slugs = array('SS22');
/**
* check if the product has the attribute and value
* and if yes restrict this product from the coupon
*/
if(in_array($product->get_attribute($taxonomy), $term_slugs)) {
$valid = false;
/**
* otherwise check if its a variation product
*/
} elseif($product->parent_id) {
/**
* set the parent product
*/
$parent = wc_get_product($product->parent_id);
/**
* check if parent has an attribute with this value
*/
if(in_array($parent->get_attribute($taxonomy), $term_slugs)) {
$valid = false;
}
/**
* for all other products which does not have the attribute with the value
* set the coupon to valid
*/
} else {
$valid = true;
}
return $valid;
}
I have tested it on my site and it works as expected.

How do I get the Product ID in the filter 'woocommerce_product_get_weight'?

The following code in my functions.php file and does indeed change the weight for all products but I would like to isolate it to a specific product.
add_filter('woocommerce_product_get_weight', 'rs_product_get_weight', 10, 1);
function rs_product_get_weight($weight) {
$weight = 45.67;
return $weight;
}
Is there any way to determine the product ID in my filter function?
I am afraid to say it doesn't work this way...
if you look at woocommerce get_weight function
public function get_weight() {
return apply_filters( 'woocommerce_product_get_weight', $this->weight ? $this->weight : '' );
}
Maybe you are referencing to and old version of woocommerce...
So, for example, if you want to change dinamically a cart product weight, you have to hook woocommerce_before_calculate_totals filter
and add this function
public function action_before_calculate( WC_Cart $cart ) {
if ( sizeof( $cart->cart_contents ) > 0 ) {
foreach ( $cart->cart_contents as $cart_item_key => $values ) {
$_product = $values['data'];
{
////we set the weight
$values['data']->weight = our new weight;
}
}
}
}
and so on...
It's a little strange, but product weight seems to come from the get_weight() method which has 2 filters inside of it. The one you are referencing and also woocommerce_product_weight which does indeed have the product ID also passed along.
/**
* Returns the product's weight.
* #todo refactor filters in this class to naming woocommerce_product_METHOD
* #return string
*/
public function get_weight() {
return apply_filters( 'woocommerce_product_weight', apply_filters( 'woocommerce_product_get_weight', $this->weight ? $this->weight : '' ), $this );
}
Therefore you should be able to filter the weight with:
add_filter('woocommerce_product_weight', 'rs_product_get_weight', 10, 2);
function rs_product_get_weight($weight, $product) {
if( $product->id == 999 ){
$weight = 45.67;
}
return $weight;
}

Categories