businessdad icon

Aelia Currency Switcher - Sort products using their price in active currency

businessdad | PRO | 01/22/21 06:17:47 PM UTC | 0 ⭐ | 1951 👁️ | Never ⏰ | []
PHP |

6.67 KB

|

None

|

0 👍

/

0 👎

/**
 * Sorts products by price, using their currency-specific prices.
 * 
 * HOW TO USE THIS CODE
 * This code shows how to override the "sort by price" logic used by WooCommerce, to ensure that products
 * are sorted using their currency-specific price.
 *
 * DISCLAIMER
 * THE USE OF THIS CODE IS AT YOUR OWN RISK. You remain fully liable for compliance with tax laws.
 * This code is offered free of charge and there is no warranty for it, to the extent permitted by applicable law.
 * Except when otherwise stated in writing the copyright holders and/or other parties provide the program "as is"
 * without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the program
 * is with you. Should the program prove defective, you assume the cost of all necessary servicing, repair or correction.
 *
 * The code is provided as an example and it's not covered by our support service. We won't be able to offer free support
 * in relation to it. Should you need a consultation, or assistance to customise this code, you can contact us to avail
 * of our paid consultation services: https://aelia.co/hire_us 
 */
class Aelia_Live_Product_Sort {
    protected static $query_order = '';
    protected static $query_limits = array();
 
    /**
     * Fetches the prices of all the products in the catalogue.
     *
     * @param string currency
     * @return array
     */
    protected static function fetch_product_prices(string $currency = null) {
        $transient_id = implode('_', array(
            'wc_aelia_get_all_product_prices',
            $currency ?? get_woocommerce_currency(),
            WC_Cache_Helper::get_transient_version('product'),
        ));
        $product_prices = get_transient($transient_id);
        if($product_prices === false) {
            global $wpdb;
 
            $sql = "
                SELECT DISTINCT product_id
                FROM {$wpdb->wc_product_meta_lookup}
            ";
 
            $product_prices = array();
            foreach(wp_list_pluck($wpdb->get_results($sql), 'product_id') as $product_id) {
                $product = wc_get_product($product_id);
 
                // Skip invalid products and variations
                if(!$product instanceof \WC_Product || $product instanceof \WC_Product_Variation) {
                    continue;
                }
 
                $product_prices[$product_id] = array();
 
                if($product instanceof \WC_Product_Variable) {
                    $children_prices = array();
                    // Fetch the price of each variation
                    foreach($product->get_visible_children() as $child_product_id) {
                        $children_prices[] = apply_filters('wc_aelia_cs_get_product_base_price_in_currency', get_post_meta('_price', $child_product_id, true), $child_product_id);
                    }
 
                    $product_prices[$product_id]['min'] = !empty($children_prices) ? min($children_prices) : '';
                    $product_prices[$product_id]['max'] = !empty($children_prices) ? max($children_prices) : '';
                }
                else {
                    $product_prices[$product_id]['max'] = $product_prices[$product_id]['min'] = apply_filters('wc_aelia_cs_get_product_base_price_in_currency', get_post_meta('_price', $product_id, true), $product_id);
                }
            }
            if(is_array($product_prices)) {
                // Cache the products, for performance
                set_transient($transient_id, $product_prices, HOUR_IN_SECONDS);
            }
        }
 
        return $product_prices;
    }
 
    /**
     * Given a "LIMIT x, y" clause, returns an array with the start and length.
     *
     * @param string $limits
     * @return array
     */
    protected static function extract_query_limits(string $limits): array {
        $limit_parts = explode(',', str_replace('LIMIT ', '', $limits));
        $limit_parts = array_filter(array_map(function($value) {
            $value = trim($value);
            return is_numeric($value) ? (int)$value : '';
        }, $limit_parts));
 
        if(empty($limit_parts)) {
            return array(
                'start' => 0,
                'length' => null,
            );
        }
 
        return array(
            'start' => (count($limit_parts) > 1) ? array_shift($limit_parts) : 0,
            'length' => array_shift($limit_parts),
        );
    }
 
    /**
     * Overrides the limits set against a WP_Query, so that it always returns
     * all the products.
     *
     * @param string $limits
     * @param WP_Query $query
     * @return string
     */
    public static function post_limits_request($limits, $query): string {
        self::$query_limits = self::extract_query_limits($limits);
 
        remove_filter('post_limits_request', array(__CLASS__, 'post_limits_request'), 10, 2);
 
        return '';
    }
 
    /**
     * Filters the list of posts returned by a WP_Query, doing the following:
     * - Sorts them by the applicable price in the active currency.
     * - Returns only the posts that fall into the LIMITS set for the query.
     *
     * @param array $posts
     * @param WP_Query $wp_query
     * @return array
     */
    public static function posts_results($posts, $wp_query) {
        remove_filter('posts_results', array(__CLASS__, 'posts_results'), 99, 2);
 
        $price_type = (self::$query_order === 'DESC') ? 'max' : 'min';
 
        // Fetch all the product prices
        $product_prices = wp_list_pluck(self::fetch_product_prices(), $price_type);
        // Extract the numeric prices
        $numeric_prices = array_filter($product_prices, 'is_numeric');
 
        // Sort the numeric prices in ascending or descending order
        if(self::$query_order === 'DESC') {
            arsort($numeric_prices, SORT_NUMERIC);
        }
        else {
            asort($numeric_prices, SORT_NUMERIC);
        }
 
        // Return a list of post, sorted as follows
        // - All the products with a numeric price, in ascending or descending order
        // - All the product with a non-numeric price, in any order
        $ordered_product_ids = array_flip(array_keys($numeric_prices + array_diff_key($product_prices, $numeric_prices)));
 
        uasort($posts, function($a, $b) use ($ordered_product_ids) {
            $a_pos = $ordered_product_ids[$a->ID] ?? -1;
            $b_pos = $ordered_product_ids[$b->ID] ?? -1;
 
            return $a_pos <=> $b_pos;
        });
 
        return array_slice($posts, self::$query_limits['start'], self::$query_limits['length']);
    }
 
    /**
     * Intercepts the ordering arguments set against a WC_Query, to enable the sorting
     * by price in active currency.
     *
     * @param array $args
     * @param string $orderby
     * @param string $order
     * @return array
     */
    public static function woocommerce_get_catalog_ordering_args($args, $orderby, $order) {
        if($orderby === 'price') {
            self::$query_order = $order;
 
            add_filter('post_limits_request', array(__CLASS__, 'post_limits_request'), 10, 2);
            add_filter('posts_results', array(__CLASS__, 'posts_results'), 99, 2);
        }
        return $args;
    }
 
    public static function init() {
        add_filter('woocommerce_get_catalog_ordering_args', array(__CLASS__, 'woocommerce_get_catalog_ordering_args'), 99, 3);
    }
}
 
Aelia_Live_Product_Sort::init();

Comments