<?php use Automattic\WooCommerce\Internal\ProductAttributesLookup\Filterer; defined('ABSPATH') || exit; class WC_Query { public $query_vars = array(); private static $product_query; private static $chosen_attributes; private $filterer; public function __construct() { $this->filterer = wc_get_container()->get(Filterer::class); add_action('init', array($this, 'add_endpoints')); if (!is_admin()) { add_action('wp_loaded', array($this, 'get_errors'), 20); add_filter('query_vars', array($this, 'add_query_vars'), 0); add_action('parse_request', array($this, 'parse_request'), 0); add_action('pre_get_posts', array($this, 'pre_get_posts')); add_filter('get_pagenum_link', array($this, 'remove_add_to_cart_pagination'), 10, 1); } $this->init_query_vars(); } public static function reset_chosen_attributes() { self::$chosen_attributes = null; } public function get_errors() { $error = !empty($_GET['wc_error']) ? sanitize_text_field(wp_unslash($_GET['wc_error'])) : ''; if ($error && !wc_has_notice($error, 'error')) { wc_add_notice($error, 'error'); } } public function init_query_vars() { $this->query_vars = array('order-pay' => get_option('woocommerce_checkout_pay_endpoint', 'order-pay'), 'order-received' => get_option('woocommerce_checkout_order_received_endpoint', 'order-received'), 'orders' => get_option('woocommerce_myaccount_orders_endpoint', 'orders'), 'view-order' => get_option('woocommerce_myaccount_view_order_endpoint', 'view-order'), 'downloads' => get_option('woocommerce_myaccount_downloads_endpoint', 'downloads'), 'edit-account' => get_option('woocommerce_myaccount_edit_account_endpoint', 'edit-account'), 'edit-address' => get_option('woocommerce_myaccount_edit_address_endpoint', 'edit-address'), 'payment-methods' => get_option('woocommerce_myaccount_payment_methods_endpoint', 'payment-methods'), 'lost-password' => get_option('woocommerce_myaccount_lost_password_endpoint', 'lost-password'), 'customer-logout' => get_option('woocommerce_logout_endpoint', 'customer-logout'), 'add-payment-method' => get_option('woocommerce_myaccount_add_payment_method_endpoint', 'add-payment-method'), 'delete-payment-method' => get_option('woocommerce_myaccount_delete_payment_method_endpoint', 'delete-payment-method'), 'set-default-payment-method' => get_option('woocommerce_myaccount_set_default_payment_method_endpoint', 'set-default-payment-method')); } public function get_endpoint_title($endpoint, $action = '') { global $wp; switch ($endpoint) { case 'order-pay': $title = __('Pay for order', 'woocommerce'); break; case 'order-received': $title = __('Order received', 'woocommerce'); break; case 'orders': if (!empty($wp->query_vars['orders'])) { $title = sprintf(__('Orders (page %d)', 'woocommerce'), intval($wp->query_vars['orders'])); } else { $title = __('Orders', 'woocommerce'); } break; case 'view-order': $order = wc_get_order($wp->query_vars['view-order']); $title = $order ? sprintf(__('Order #%s', 'woocommerce'), $order->get_order_number()) : ''; break; case 'downloads': $title = __('Downloads', 'woocommerce'); break; case 'edit-account': $title = __('Account details', 'woocommerce'); break; case 'edit-address': $title = __('Addresses', 'woocommerce'); break; case 'payment-methods': $title = __('Payment methods', 'woocommerce'); break; case 'add-payment-method': $title = __('Add payment method', 'woocommerce'); break; case 'lost-password': if (in_array($action, array('rp', 'resetpass', 'newaccount'), true)) { $title = __('Set password', 'woocommerce'); } else { $title = __('Lost password', 'woocommerce'); } break; default: $title = ''; break; } return apply_filters('woocommerce_endpoint_' . $endpoint . '_title', $title, $endpoint, $action); } public function get_endpoints_mask() { if ('page' === get_option('show_on_front')) { $page_on_front = get_option('page_on_front'); $myaccount_page_id = get_option('woocommerce_myaccount_page_id'); $checkout_page_id = get_option('woocommerce_checkout_page_id'); if (in_array($page_on_front, array($myaccount_page_id, $checkout_page_id), true)) { return EP_ROOT | EP_PAGES; } } return EP_PAGES; } public function add_endpoints() { $mask = $this->get_endpoints_mask(); foreach ($this->get_query_vars() as $key => $var) { if (!empty($var)) { add_rewrite_endpoint($var, $mask); } } } public function add_query_vars($vars) { foreach ($this->get_query_vars() as $key => $var) { $vars[] = $key; } return $vars; } public function get_query_vars() { return apply_filters('woocommerce_get_query_vars', $this->query_vars); } public function get_current_endpoint() { global $wp; foreach ($this->get_query_vars() as $key => $value) { if (isset($wp->query_vars[$key])) { return $key; } } return ''; } public function parse_request() { global $wp; foreach ($this->get_query_vars() as $key => $var) { if (isset($_GET[$var])) { $wp->query_vars[$key] = sanitize_text_field(wp_unslash($_GET[$var])); } elseif (isset($wp->query_vars[$var])) { $wp->query_vars[$key] = $wp->query_vars[$var]; } } } private function is_showing_page_on_front($q) { return $q->is_home() && !$q->is_posts_page && 'page' === get_option('show_on_front'); } private function page_on_front_is($page_id) { return absint(get_option('page_on_front')) === absint($page_id); } public function pre_get_posts($q) { if (!$q->is_main_query()) { return; } if ($this->is_showing_page_on_front($q)) { if (!$this->page_on_front_is($q->get('page_id'))) { $_query = wp_parse_args($q->query); if (!empty($_query) && array_intersect(array_keys($_query), array_keys($this->get_query_vars()))) { $q->is_page = true; $q->is_home = false; $q->is_singular = true; $q->set('page_id', (int) get_option('page_on_front')); add_filter('redirect_canonical', '__return_false'); } } if ($this->page_on_front_is(wc_get_page_id('shop'))) { $_query = wp_parse_args($q->query); if (empty($_query) || !array_diff(array_keys($_query), array('preview', 'page', 'paged', 'cpage', 'orderby'))) { $q->set('page_id', (int) get_option('page_on_front')); $q->is_page = true; $q->is_home = false; if (current_theme_supports('woocommerce')) { $q->set('post_type', 'product'); } else { $q->is_singular = true; } } } elseif (!empty($_GET['orderby'])) { $q->set('page_id', (int) get_option('page_on_front')); $q->is_page = true; $q->is_home = false; $q->is_singular = true; } } if ($q->is_feed() && $q->is_post_type_archive('product')) { $q->is_comment_feed = false; } if (current_theme_supports('woocommerce') && $q->is_page() && 'page' === get_option('show_on_front') && absint($q->get('page_id')) === wc_get_page_id('shop')) { $q->set('post_type', 'product'); $q->set('page_id', ''); if (isset($q->query['paged'])) { $q->set('paged', $q->query['paged']); } wc_maybe_define_constant('SHOP_IS_ON_FRONT', true); global $wp_post_types; $shop_page = get_post(wc_get_page_id('shop')); $wp_post_types['product']->ID = $shop_page->ID; $wp_post_types['product']->post_title = $shop_page->post_title; $wp_post_types['product']->post_name = $shop_page->post_name; $wp_post_types['product']->post_type = $shop_page->post_type; $wp_post_types['product']->ancestors = get_ancestors($shop_page->ID, $shop_page->post_type); $q->is_singular = false; $q->is_post_type_archive = true; $q->is_archive = true; $q->is_page = true; add_filter('post_type_archive_title', '__return_empty_string', 5); if (class_exists('WPSEO_Meta')) { add_filter('wpseo_metadesc', array($this, 'wpseo_metadesc')); add_filter('wpseo_metakey', array($this, 'wpseo_metakey')); } } elseif (!$q->is_post_type_archive('product') && !$q->is_tax(get_object_taxonomies('product'))) { return; } $this->product_query($q); } public function handle_get_posts($posts, $query) { if ('product_query' !== $query->get('wc_query')) { return $posts; } $this->remove_product_query_filters($posts); return $posts; } public function remove_product_query_filters($posts) { $this->remove_ordering_args(); remove_filter('posts_clauses', array($this, 'price_filter_post_clauses'), 10, 2); return $posts; } public function adjust_posts_count($count, $query) { return $count; } protected function get_layered_nav_chosen_attributes_inst() { return self::get_layered_nav_chosen_attributes(); } protected function get_current_posts() { return $GLOBALS['wp_query']->posts; } public function wpseo_metadesc() { return WPSEO_Meta::get_value('metadesc', wc_get_page_id('shop')); } public function wpseo_metakey() { return WPSEO_Meta::get_value('metakey', wc_get_page_id('shop')); } public function product_query($q) { if (!is_feed()) { $ordering = $this->get_catalog_ordering_args(); $q->set('orderby', $ordering['orderby']); $q->set('order', $ordering['order']); if (isset($ordering['meta_key'])) { $q->set('meta_key', $ordering['meta_key']); } } $q->set('meta_query', $this->get_meta_query($q->get('meta_query'), true)); $q->set('tax_query', $this->get_tax_query($q->get('tax_query'), true)); $q->set('wc_query', 'product_query'); $q->set('post__in', array_unique((array) apply_filters('loop_shop_post_in', array()))); $q->set('posts_per_page', $q->get('posts_per_page') ? $q->get('posts_per_page') : apply_filters('loop_shop_per_page', wc_get_default_products_per_row() * wc_get_default_product_rows_per_page())); self::$product_query = $q; add_filter('posts_clauses', function ($args, $wp_query) { return $this->product_query_post_clauses($args, $wp_query); }, 10, 2); add_filter('the_posts', array($this, 'handle_get_posts'), 10, 2); do_action('woocommerce_product_query', $q, $this); } private function product_query_post_clauses($args, $wp_query) { $args = $this->price_filter_post_clauses($args, $wp_query); $args = $this->filterer->filter_by_attribute_post_clauses($args, $wp_query, $this->get_layered_nav_chosen_attributes()); return $args; } public function remove_product_query() { remove_action('pre_get_posts', array($this, 'pre_get_posts')); } public function remove_ordering_args() { remove_filter('posts_clauses', array($this, 'order_by_price_asc_post_clauses')); remove_filter('posts_clauses', array($this, 'order_by_price_desc_post_clauses')); remove_filter('posts_clauses', array($this, 'order_by_popularity_post_clauses')); remove_filter('posts_clauses', array($this, 'order_by_rating_post_clauses')); } public function get_catalog_ordering_args($orderby = '', $order = '') { if (!$orderby) { $orderby_value = isset($_GET['orderby']) ? wc_clean((string) wp_unslash($_GET['orderby'])) : wc_clean(get_query_var('orderby')); if (!$orderby_value) { if (is_search()) { $orderby_value = 'relevance'; } else { $orderby_value = apply_filters('woocommerce_default_catalog_orderby', get_option('woocommerce_default_catalog_orderby', 'menu_order')); } } $orderby_value = is_array($orderby_value) ? $orderby_value : explode('-', $orderby_value); $orderby = esc_attr($orderby_value[0]); $order = !empty($orderby_value[1]) ? $orderby_value[1] : $order; } $orderby = strtolower(is_array($orderby) ? (string) current($orderby) : (string) $orderby); $order = strtoupper(is_array($order) ? (string) current($order) : (string) $order); $args = array('orderby' => $orderby, 'order' => 'DESC' === $order ? 'DESC' : 'ASC', 'meta_key' => ''); switch ($orderby) { case 'id': $args['orderby'] = 'ID'; break; case 'menu_order': $args['orderby'] = 'menu_order title'; break; case 'title': $args['orderby'] = 'title'; $args['order'] = 'DESC' === $order ? 'DESC' : 'ASC'; break; case 'relevance': $args['orderby'] = 'relevance'; $args['order'] = 'DESC'; break; case 'rand': $args['orderby'] = 'rand'; break; case 'date': $args['orderby'] = 'date ID'; $args['order'] = 'ASC' === $order ? 'ASC' : 'DESC'; break; case 'price': $callback = 'DESC' === $order ? 'order_by_price_desc_post_clauses' : 'order_by_price_asc_post_clauses'; add_filter('posts_clauses', array($this, $callback)); break; case 'popularity': add_filter('posts_clauses', array($this, 'order_by_popularity_post_clauses')); break; case 'rating': add_filter('posts_clauses', array($this, 'order_by_rating_post_clauses')); break; } return apply_filters('woocommerce_get_catalog_ordering_args', $args, $orderby, $order); } public function price_filter_post_clauses($args, $wp_query) { global $wpdb; if (!$wp_query->is_main_query() || !isset($_GET['max_price']) && !isset($_GET['min_price'])) { return $args; } $current_min_price = isset($_GET['min_price']) ? floatval(wp_unslash($_GET['min_price'])) : 0; $current_max_price = isset($_GET['max_price']) ? floatval(wp_unslash($_GET['max_price'])) : PHP_INT_MAX; if (wc_tax_enabled() && 'incl' === get_option('woocommerce_tax_display_shop') && !wc_prices_include_tax()) { $tax_class = apply_filters('woocommerce_price_filter_widget_tax_class', ''); $tax_rates = WC_Tax::get_rates($tax_class); if ($tax_rates) { $current_min_price -= WC_Tax::get_tax_total(WC_Tax::calc_inclusive_tax($current_min_price, $tax_rates)); $current_max_price -= WC_Tax::get_tax_total(WC_Tax::calc_inclusive_tax($current_max_price, $tax_rates)); } } $args['join'] = $this->append_product_sorting_table_join($args['join']); $args['where'] .= $wpdb->prepare(' AND NOT (%f<wc_product_meta_lookup.min_price OR %f>wc_product_meta_lookup.max_price ) ', $current_max_price, $current_min_price); return $args; } public function order_by_price_asc_post_clauses($args) { $args['join'] = $this->append_product_sorting_table_join($args['join']); $args['orderby'] = ' wc_product_meta_lookup.min_price ASC, wc_product_meta_lookup.product_id ASC '; return $args; } public function order_by_price_desc_post_clauses($args) { $args['join'] = $this->append_product_sorting_table_join($args['join']); $args['orderby'] = ' wc_product_meta_lookup.max_price DESC, wc_product_meta_lookup.product_id DESC '; return $args; } public function order_by_popularity_post_clauses($args) { $args['join'] = $this->append_product_sorting_table_join($args['join']); $args['orderby'] = ' wc_product_meta_lookup.total_sales DESC, wc_product_meta_lookup.product_id DESC '; return $args; } public function order_by_rating_post_clauses($args) { $args['join'] = $this->append_product_sorting_table_join($args['join']); $args['orderby'] = ' wc_product_meta_lookup.average_rating DESC, wc_product_meta_lookup.rating_count DESC, wc_product_meta_lookup.product_id DESC '; return $args; } private function append_product_sorting_table_join($sql) { global $wpdb; if (!strstr($sql, 'wc_product_meta_lookup')) { $sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON {$wpdb->posts}.ID = wc_product_meta_lookup.product_id "; } return $sql; } public function get_meta_query($meta_query = array(), $main_query = false) { if (!is_array($meta_query)) { $meta_query = array(); } return array_filter(apply_filters('woocommerce_product_query_meta_query', $meta_query, $this)); } public function get_tax_query($tax_query = array(), $main_query = false) { if (!is_array($tax_query)) { $tax_query = array('relation' => 'AND'); } if ($main_query && !$this->filterer->filtering_via_lookup_table_is_active()) { foreach ($this->get_layered_nav_chosen_attributes() as $taxonomy => $data) { $tax_query[] = array('taxonomy' => $taxonomy, 'field' => 'slug', 'terms' => $data['terms'], 'operator' => 'and' === $data['query_type'] ? 'AND' : 'IN', 'include_children' => false); } } $product_visibility_terms = wc_get_product_visibility_term_ids(); $product_visibility_not_in = array(is_search() && $main_query ? $product_visibility_terms['exclude-from-search'] : $product_visibility_terms['exclude-from-catalog']); if ('yes' === get_option('woocommerce_hide_out_of_stock_items')) { $product_visibility_not_in[] = $product_visibility_terms['outofstock']; } if (isset($_GET['rating_filter'])) { $rating_filter = array_filter(array_map('absint', explode(',', wp_unslash($_GET['rating_filter'])))); $rating_terms = array(); for ($i = 1; $i <= 5; $i++) { if (in_array($i, $rating_filter, true) && isset($product_visibility_terms['rated-' . $i])) { $rating_terms[] = $product_visibility_terms['rated-' . $i]; } } if (!empty($rating_terms)) { $tax_query[] = array('taxonomy' => 'product_visibility', 'field' => 'term_taxonomy_id', 'terms' => $rating_terms, 'operator' => 'IN', 'rating_filter' => true); } } if (!empty($product_visibility_not_in)) { $tax_query[] = array('taxonomy' => 'product_visibility', 'field' => 'term_taxonomy_id', 'terms' => $product_visibility_not_in, 'operator' => 'NOT IN'); } return array_filter(apply_filters('woocommerce_product_query_tax_query', $tax_query, $this)); } public static function get_main_query() { return self::$product_query; } public static function get_main_tax_query() { $tax_query = isset(self::$product_query->tax_query, self::$product_query->tax_query->queries) ? self::$product_query->tax_query->queries : array(); return $tax_query; } public static function get_main_meta_query() { $args = self::$product_query->query_vars; $meta_query = isset($args['meta_query']) ? $args['meta_query'] : array(); return $meta_query; } public static function get_main_search_query_sql() { global $wpdb; $args = self::$product_query->query_vars; $search_terms = isset($args['search_terms']) ? $args['search_terms'] : array(); $sql = array(); foreach ($search_terms as $term) { $include = '-' !== substr($term, 0, 1); if ($include) { $like_op = 'LIKE'; $andor_op = 'OR'; } else { $like_op = 'NOT LIKE'; $andor_op = 'AND'; $term = substr($term, 1); } $like = '%' . $wpdb->esc_like($term) . '%'; $sql[] = $wpdb->prepare("(({$wpdb->posts}.post_title {$like_op} %s) {$andor_op} ({$wpdb->posts}.post_excerpt {$like_op} %s) {$andor_op} ({$wpdb->posts}.post_content {$like_op} %s))", $like, $like, $like); } if (!empty($sql) && !is_user_logged_in()) { $sql[] = "({$wpdb->posts}.post_password = '')"; } return implode(' AND ', $sql); } public static function get_layered_nav_chosen_attributes() { if (!is_array(self::$chosen_attributes)) { self::$chosen_attributes = array(); if (!empty($_GET)) { foreach ($_GET as $key => $value) { if (0 === strpos($key, 'filter_')) { $attribute = wc_sanitize_taxonomy_name(str_replace('filter_', '', $key)); $taxonomy = wc_attribute_taxonomy_name($attribute); $filter_terms = !empty($value) ? explode(',', wc_clean(wp_unslash($value))) : array(); if (empty($filter_terms) || !taxonomy_exists($taxonomy) || !wc_attribute_taxonomy_id_by_name($attribute)) { continue; } $query_type = !empty($_GET['query_type_' . $attribute]) && in_array($_GET['query_type_' . $attribute], array('and', 'or'), true) ? wc_clean(wp_unslash($_GET['query_type_' . $attribute])) : ''; self::$chosen_attributes[$taxonomy]['terms'] = array_map('sanitize_title', $filter_terms); self::$chosen_attributes[$taxonomy]['query_type'] = $query_type ? $query_type : apply_filters('woocommerce_layered_nav_default_query_type', 'and'); } } } } return self::$chosen_attributes; } public function remove_add_to_cart_pagination($url) { return remove_query_arg('add-to-cart', $url); } public function rating_filter_meta_query() { return array(); } public function visibility_meta_query($compare = 'IN') { return array(); } public function stock_status_meta_query($status = 'instock') { return array(); } public function layered_nav_init() { wc_deprecated_function('layered_nav_init', '2.6'); } public function get_products_in_view() { wc_deprecated_function('get_products_in_view', '2.6'); } public function layered_nav_query($deprecated) { wc_deprecated_function('layered_nav_query', '2.6'); } public function search_post_excerpt($where = '') { wc_deprecated_function('WC_Query::search_post_excerpt', '3.2.0', 'Excerpt added to search query by default since WordPress 4.5.'); return $where; } public function remove_posts_where() { wc_deprecated_function('WC_Query::remove_posts_where', '3.2.0', 'Nothing to remove anymore because search_post_excerpt() is deprecated.'); } }