<?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.');
}
}