businessdad

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

Jan 22nd, 2021
986
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. /**
  2.  * Sorts products by price, using their currency-specific prices.
  3.  *
  4.  * HOW TO USE THIS CODE
  5.  * This code shows how to override the "sort by price" logic used by WooCommerce, to ensure that products
  6.  * are sorted using their currency-specific price.
  7.  *
  8.  * DISCLAIMER
  9.  * THE USE OF THIS CODE IS AT YOUR OWN RISK. You remain fully liable for compliance with tax laws.
  10.  * This code is offered free of charge and there is no warranty for it, to the extent permitted by applicable law.
  11.  * Except when otherwise stated in writing the copyright holders and/or other parties provide the program "as is"
  12.  * without warranty of any kind, either expressed or implied, including, but not limited to, the implied warranties of
  13.  * merchantability and fitness for a particular purpose. The entire risk as to the quality and performance of the program
  14.  * is with you. Should the program prove defective, you assume the cost of all necessary servicing, repair or correction.
  15.  *
  16.  * 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
  17.  * in relation to it. Should you need a consultation, or assistance to customise this code, you can contact us to avail
  18.  * of our paid consultation services: https://aelia.co/hire_us
  19.  */
  20. class Aelia_Live_Product_Sort {
  21.     protected static $query_order = '';
  22.     protected static $query_limits = array();
  23.  
  24.     /**
  25.      * Fetches the prices of all the products in the catalogue.
  26.      *
  27.      * @param string currency
  28.      * @return array
  29.      */
  30.     protected static function fetch_product_prices(string $currency = null) {
  31.         $transient_id = implode('_', array(
  32.             'wc_aelia_get_all_product_prices',
  33.             $currency ?? get_woocommerce_currency(),
  34.             WC_Cache_Helper::get_transient_version('product'),
  35.         ));
  36.         $product_prices = get_transient($transient_id);
  37.         if($product_prices === false) {
  38.             global $wpdb;
  39.  
  40.             $sql = "
  41.                 SELECT DISTINCT product_id
  42.                 FROM {$wpdb->wc_product_meta_lookup}
  43.             ";
  44.  
  45.             $product_prices = array();
  46.             foreach(wp_list_pluck($wpdb->get_results($sql), 'product_id') as $product_id) {
  47.                 $product = wc_get_product($product_id);
  48.  
  49.                 // Skip invalid products and variations
  50.                 if(!$product instanceof \WC_Product || $product instanceof \WC_Product_Variation) {
  51.                     continue;
  52.                 }
  53.  
  54.                 $product_prices[$product_id] = array();
  55.  
  56.                 if($product instanceof \WC_Product_Variable) {
  57.                     $children_prices = array();
  58.                     // Fetch the price of each variation
  59.                     foreach($product->get_visible_children() as $child_product_id) {
  60.                         $children_prices[] = apply_filters('wc_aelia_cs_get_product_base_price_in_currency', get_post_meta('_price', $child_product_id, true), $child_product_id);
  61.                     }
  62.  
  63.                     $product_prices[$product_id]['min'] = !empty($children_prices) ? min($children_prices) : '';
  64.                     $product_prices[$product_id]['max'] = !empty($children_prices) ? max($children_prices) : '';
  65.                 }
  66.                 else {
  67.                     $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);
  68.                 }
  69.             }
  70.             if(is_array($product_prices)) {
  71.                 // Cache the products, for performance
  72.                 set_transient($transient_id, $product_prices, HOUR_IN_SECONDS);
  73.             }
  74.         }
  75.  
  76.         return $product_prices;
  77.     }
  78.  
  79.     /**
  80.      * Given a "LIMIT x, y" clause, returns an array with the start and length.
  81.      *
  82.      * @param string $limits
  83.      * @return array
  84.      */
  85.     protected static function extract_query_limits(string $limits): array {
  86.         $limit_parts = explode(',', str_replace('LIMIT ', '', $limits));
  87.         $limit_parts = array_filter(array_map(function($value) {
  88.             $value = trim($value);
  89.             return is_numeric($value) ? (int)$value : '';
  90.         }, $limit_parts));
  91.  
  92.         if(empty($limit_parts)) {
  93.             return array(
  94.                 'start' => 0,
  95.                 'length' => null,
  96.             );
  97.         }
  98.  
  99.         return array(
  100.             'start' => (count($limit_parts) > 1) ? array_shift($limit_parts) : 0,
  101.             'length' => array_shift($limit_parts),
  102.         );
  103.     }
  104.  
  105.     /**
  106.      * Overrides the limits set against a WP_Query, so that it always returns
  107.      * all the products.
  108.      *
  109.      * @param string $limits
  110.      * @param WP_Query $query
  111.      * @return string
  112.      */
  113.     public static function post_limits_request($limits, $query): string {
  114.         self::$query_limits = self::extract_query_limits($limits);
  115.  
  116.         remove_filter('post_limits_request', array(__CLASS__, 'post_limits_request'), 10, 2);
  117.  
  118.         return '';
  119.     }
  120.  
  121.     /**
  122.      * Filters the list of posts returned by a WP_Query, doing the following:
  123.      * - Sorts them by the applicable price in the active currency.
  124.      * - Returns only the posts that fall into the LIMITS set for the query.
  125.      *
  126.      * @param array $posts
  127.      * @param WP_Query $wp_query
  128.      * @return array
  129.      */
  130.     public static function posts_results($posts, $wp_query) {
  131.         remove_filter('posts_results', array(__CLASS__, 'posts_results'), 99, 2);
  132.  
  133.         $price_type = (self::$query_order === 'DESC') ? 'max' : 'min';
  134.  
  135.         // Fetch all the product prices
  136.         $product_prices = wp_list_pluck(self::fetch_product_prices(), $price_type);
  137.         // Extract the numeric prices
  138.         $numeric_prices = array_filter($product_prices, 'is_numeric');
  139.  
  140.         // Sort the numeric prices in ascending or descending order
  141.         if(self::$query_order === 'DESC') {
  142.             arsort($numeric_prices, SORT_NUMERIC);
  143.         }
  144.         else {
  145.             asort($numeric_prices, SORT_NUMERIC);
  146.         }
  147.  
  148.         // Return a list of post, sorted as follows
  149.         // - All the products with a numeric price, in ascending or descending order
  150.         // - All the product with a non-numeric price, in any order
  151.         $ordered_product_ids = array_flip(array_keys($numeric_prices + array_diff_key($product_prices, $numeric_prices)));
  152.  
  153.         uasort($posts, function($a, $b) use ($ordered_product_ids) {
  154.             $a_pos = $ordered_product_ids[$a->ID] ?? -1;
  155.             $b_pos = $ordered_product_ids[$b->ID] ?? -1;
  156.  
  157.             return $a_pos <=> $b_pos;
  158.         });
  159.  
  160.         return array_slice($posts, self::$query_limits['start'], self::$query_limits['length']);
  161.     }
  162.  
  163.     /**
  164.      * Intercepts the ordering arguments set against a WC_Query, to enable the sorting
  165.      * by price in active currency.
  166.      *
  167.      * @param array $args
  168.      * @param string $orderby
  169.      * @param string $order
  170.      * @return array
  171.      */
  172.     public static function woocommerce_get_catalog_ordering_args($args, $orderby, $order) {
  173.         if($orderby === 'price') {
  174.             self::$query_order = $order;
  175.  
  176.             add_filter('post_limits_request', array(__CLASS__, 'post_limits_request'), 10, 2);
  177.             add_filter('posts_results', array(__CLASS__, 'posts_results'), 99, 2);
  178.         }
  179.         return $args;
  180.     }
  181.  
  182.     public static function init() {
  183.         add_filter('woocommerce_get_catalog_ordering_args', array(__CLASS__, 'woocommerce_get_catalog_ordering_args'), 99, 3);
  184.     }
  185. }
  186.  
  187. Aelia_Live_Product_Sort::init();
RAW Paste Data