Completed
Push — master ( b188ad...1e70bc )
by Mike
08:55
created

WC_Query   D

Complexity

Total Complexity 132

Size/Duplication

Total Lines 723
Duplicated Lines 1.94 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 4
Bugs 1 Features 0
Metric Value
c 4
b 1
f 0
dl 14
loc 723
rs 4.4444
wmc 132
lcom 1
cbo 1

33 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 12 2
A get_errors() 0 5 4
A init_query_vars() 0 20 1
C get_endpoint_title() 0 46 13
A get_endpoints_mask() 0 13 3
A add_endpoints() 0 7 2
A add_query_vars() 0 6 2
A get_query_vars() 0 3 1
A get_current_endpoint() 0 9 3
A parse_request() 0 14 4
F pre_get_posts() 0 103 25
A search_post_excerpt() 0 13 3
A wpseo_metadesc() 0 3 1
A wpseo_metakey() 0 3 1
A product_query() 0 18 3
A remove_product_query() 0 3 1
A remove_ordering_args() 0 4 1
A remove_posts_where() 0 3 1
C get_catalog_ordering_args() 0 53 14
A order_by_popularity_post_clauses() 0 5 1
A order_by_rating_post_clauses() 0 14 1
A get_meta_query() 0 12 2
D price_filter_meta_query() 0 33 10
A rating_filter_meta_query() 0 9 3
A visibility_meta_query() 0 7 2
A stock_status_meta_query() 0 7 2
B get_tax_query() 0 20 5
B get_main_tax_query() 14 32 6
A get_main_meta_query() 0 8 2
C get_layered_nav_chosen_attributes() 0 22 10
A layered_nav_init() 0 3 1
A get_products_in_view() 0 3 1
A layered_nav_query() 0 3 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like WC_Query often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use WC_Query, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Contains the query functions for WooCommerce which alter the front-end post queries and loops
4
 *
5
 * @class 		WC_Query
6
 * @version		2.6.0
7
 * @package		WooCommerce/Classes
8
 * @category	Class
9
 * @author 		WooThemes
10
 */
11
12
if ( ! defined( 'ABSPATH' ) ) {
13
	exit;
14
}
15
16
/**
17
 * WC_Query Class.
18
 */
19
class WC_Query {
20
21
	/** @public array Query vars to add to wp */
22
	public $query_vars = array();
23
24
	/**
25
	 * Stores chosen attributes
26
	 * @var array
27
	 */
28
	private static $_chosen_attributes;
29
30
	/**
31
	 * Constructor for the query class. Hooks in methods.
32
	 *
33
	 * @access public
34
	 */
35
	public function __construct() {
36
		add_action( 'init', array( $this, 'add_endpoints' ) );
37
		if ( ! is_admin() ) {
38
			add_action( 'wp_loaded', array( $this, 'get_errors' ), 20 );
39
			add_filter( 'query_vars', array( $this, 'add_query_vars'), 0 );
40
			add_action( 'parse_request', array( $this, 'parse_request'), 0 );
41
			add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
42
			add_action( 'wp', array( $this, 'remove_product_query' ) );
43
			add_action( 'wp', array( $this, 'remove_ordering_args' ) );
44
		}
45
		$this->init_query_vars();
46
	}
47
48
	/**
49
	 * Get any errors from querystring.
50
	 */
51
	public function get_errors() {
52
		if ( ! empty( $_GET['wc_error'] ) && ( $error = sanitize_text_field( $_GET['wc_error'] ) ) && ! wc_has_notice( $error, 'error' ) ) {
53
			wc_add_notice( $error, 'error' );
54
		}
55
	}
56
57
	/**
58
	 * Init query vars by loading options.
59
	 */
60
	public function init_query_vars() {
61
		// Query vars to add to WP.
62
		$this->query_vars = array(
63
			// Checkout actions.
64
			'order-pay'          => get_option( 'woocommerce_checkout_pay_endpoint', 'order-pay' ),
65
			'order-received'     => get_option( 'woocommerce_checkout_order_received_endpoint', 'order-received' ),
66
			// My account actions.
67
			'orders'                     => get_option( 'woocommerce_myaccount_orders_endpoint', 'orders' ),
68
			'view-order'                 => get_option( 'woocommerce_myaccount_view_order_endpoint', 'view-order' ),
69
			'downloads'                  => get_option( 'woocommerce_myaccount_downloads_endpoint', 'downloads' ),
70
			'edit-account'               => get_option( 'woocommerce_myaccount_edit_account_endpoint', 'edit-account' ),
71
			'edit-address'               => get_option( 'woocommerce_myaccount_edit_address_endpoint', 'edit-address' ),
72
			'payment-methods'            => get_option( 'woocommerce_myaccount_payment_methods_endpoint', 'payment-methods' ),
73
			'lost-password'              => get_option( 'woocommerce_myaccount_lost_password_endpoint', 'lost-password' ),
74
			'customer-logout'            => get_option( 'woocommerce_logout_endpoint', 'customer-logout' ),
75
			'add-payment-method'         => get_option( 'woocommerce_myaccount_add_payment_method_endpoint', 'add-payment-method' ),
76
			'delete-payment-method'      => get_option( 'woocommerce_myaccount_delete_payment_method_endpoint', 'delete-payment-method' ),
77
			'set-default-payment-method' => get_option( 'woocommerce_myaccount_set_default_payment_method_endpoint', 'set-default-payment-method' ),
78
		);
79
	}
80
81
	/**
82
	 * Get page title for an endpoint.
83
	 * @param  string
84
	 * @return string
85
	 */
86
	public function get_endpoint_title( $endpoint ) {
87
		global $wp;
88
89
		switch ( $endpoint ) {
90
			case 'order-pay' :
91
				$title = __( 'Pay for Order', 'woocommerce' );
92
			break;
93
			case 'order-received' :
94
				$title = __( 'Order Received', 'woocommerce' );
95
			break;
96
			case 'orders' :
97
				if ( ! empty( $wp->query_vars['orders'] ) ) {
98
					$title = sprintf( __( 'Orders (page %d)', 'woocommerce' ), intval( $wp->query_vars['orders'] ) );
99
				} else {
100
					$title = __( 'Orders', 'woocommerce' );
101
				}
102
			break;
103
			case 'view-order' :
104
				$order = wc_get_order( $wp->query_vars['view-order'] );
105
				$title = ( $order ) ? sprintf( __( 'Order #%s', 'woocommerce' ), $order->get_order_number() ) : '';
106
			break;
107
			case 'downloads' :
108
				$title = __( 'Downloads', 'woocommerce' );
109
			break;
110
			case 'edit-account' :
111
				$title = __( 'Account Details', 'woocommerce' );
112
			break;
113
			case 'edit-address' :
114
				$title = __( 'Addresses', 'woocommerce' );
115
			break;
116
			case 'payment-methods' :
117
				$title = __( 'Payment Methods', 'woocommerce' );
118
			break;
119
			case 'add-payment-method' :
120
				$title = __( 'Add Payment Method', 'woocommerce' );
121
			break;
122
			case 'lost-password' :
123
				$title = __( 'Lost Password', 'woocommerce' );
124
			break;
125
			default :
126
				$title = apply_filters( 'woocommerce_endpoint_' . $endpoint . '_title', '' );
127
			break;
128
		}
129
130
		return $title;
131
	}
132
133
	/**
134
	 * Endpoint mask describing the places the endpoint should be added.
135
	 *
136
	 * @since 2.6.2
137
	 * @return int
138
	 */
139
	protected function get_endpoints_mask() {
140
		if ( 'page' === get_option( 'show_on_front' ) ) {
141
			$page_on_front     = get_option( 'page_on_front' );
142
			$myaccount_page_id = get_option( 'woocommerce_myaccount_page_id' );
143
			$checkout_page_id  = get_option( 'woocommerce_checkout_page_id' );
144
145
			if ( in_array( $page_on_front, array( $myaccount_page_id, $checkout_page_id ) ) ) {
146
				return EP_ROOT | EP_PAGES;
147
			}
148
		}
149
150
		return EP_PAGES;
151
	}
152
153
	/**
154
	 * Add endpoints for query vars.
155
	 */
156
	public function add_endpoints() {
157
		$mask = $this->get_endpoints_mask();
158
159
		foreach ( $this->query_vars as $key => $var ) {
160
			add_rewrite_endpoint( $var, $mask );
161
		}
162
	}
163
164
	/**
165
	 * Add query vars.
166
	 *
167
	 * @access public
168
	 * @param array $vars
169
	 * @return array
170
	 */
171
	public function add_query_vars( $vars ) {
172
		foreach ( $this->query_vars as $key => $var ) {
173
			$vars[] = $key;
174
		}
175
		return $vars;
176
	}
177
178
	/**
179
	 * Get query vars.
180
	 *
181
	 * @return array
182
	 */
183
	public function get_query_vars() {
184
		return $this->query_vars;
185
	}
186
187
	/**
188
	 * Get query current active query var.
189
	 *
190
	 * @return string
191
	 */
192
	public function get_current_endpoint() {
193
		global $wp;
194
		foreach ( $this->get_query_vars() as $key => $value ) {
195
			if ( isset( $wp->query_vars[ $key ] ) ) {
196
				return $key;
197
			}
198
		}
199
		return '';
200
	}
201
202
	/**
203
	 * Parse the request and look for query vars - endpoints may not be supported.
204
	 */
205
	public function parse_request() {
206
		global $wp;
207
208
		// Map query vars to their keys, or get them if endpoints are not supported
209
		foreach ( $this->query_vars as $key => $var ) {
210
			if ( isset( $_GET[ $var ] ) ) {
211
				$wp->query_vars[ $key ] = $_GET[ $var ];
212
			}
213
214
			elseif ( isset( $wp->query_vars[ $var ] ) ) {
215
				$wp->query_vars[ $key ] = $wp->query_vars[ $var ];
216
			}
217
		}
218
	}
219
220
	/**
221
	 * Hook into pre_get_posts to do the main product query.
222
	 *
223
	 * @param mixed $q query object
224
	 */
225
	public function pre_get_posts( $q ) {
226
		// We only want to affect the main query
227
		if ( ! $q->is_main_query() ) {
228
			return;
229
		}
230
231
		// Fix for verbose page rules
232
		if ( $GLOBALS['wp_rewrite']->use_verbose_page_rules && isset( $q->queried_object->ID ) && $q->queried_object->ID === wc_get_page_id( 'shop' ) ) {
233
			$q->set( 'post_type', 'product' );
234
			$q->set( 'page', '' );
235
			$q->set( 'pagename', '' );
236
237
			// Fix conditional Functions
238
			$q->is_archive           = true;
239
			$q->is_post_type_archive = true;
240
			$q->is_singular          = false;
241
			$q->is_page              = false;
242
		}
243
244
		// Fix for endpoints on the homepage
245
		if ( $q->is_home() && 'page' === get_option( 'show_on_front' ) && absint( get_option( 'page_on_front' ) ) !== absint( $q->get( 'page_id' ) ) ) {
246
			$_query = wp_parse_args( $q->query );
247
			if ( ! empty( $_query ) && array_intersect( array_keys( $_query ), array_keys( $this->query_vars ) ) ) {
248
				$q->is_page     = true;
249
				$q->is_home     = false;
250
				$q->is_singular = true;
251
				$q->set( 'page_id', (int) get_option( 'page_on_front' ) );
252
				add_filter( 'redirect_canonical', '__return_false' );
253
			}
254
		}
255
256
		// When orderby is set, WordPress shows posts. Get around that here.
257
		if ( $q->is_home() && 'page' === get_option( 'show_on_front' ) && absint( get_option( 'page_on_front' ) ) === wc_get_page_id( 'shop' ) ) {
258
			$_query = wp_parse_args( $q->query );
259
			if ( empty( $_query ) || ! array_diff( array_keys( $_query ), array( 'preview', 'page', 'paged', 'cpage', 'orderby' ) ) ) {
260
				$q->is_page = true;
261
				$q->is_home = false;
262
				$q->set( 'page_id', (int) get_option( 'page_on_front' ) );
263
				$q->set( 'post_type', 'product' );
264
			}
265
		}
266
267
		// Fix product feeds
268
		if ( $q->is_feed() && $q->is_post_type_archive( 'product' ) ) {
269
			$q->is_comment_feed = false;
270
		}
271
272
		// Special check for shops with the product archive on front
273
		if ( $q->is_page() && 'page' === get_option( 'show_on_front' ) && absint( $q->get( 'page_id' ) ) === wc_get_page_id( 'shop' ) ) {
274
275
			// This is a front-page shop
276
			$q->set( 'post_type', 'product' );
277
			$q->set( 'page_id', '' );
278
279
			if ( isset( $q->query['paged'] ) ) {
280
				$q->set( 'paged', $q->query['paged'] );
281
			}
282
283
			// Define a variable so we know this is the front page shop later on
284
			define( 'SHOP_IS_ON_FRONT', true );
285
286
			// Get the actual WP page to avoid errors and let us use is_front_page()
287
			// This is hacky but works. Awaiting https://core.trac.wordpress.org/ticket/21096
288
			global $wp_post_types;
289
290
			$shop_page 	= get_post( wc_get_page_id( 'shop' ) );
291
292
			$wp_post_types['product']->ID 			= $shop_page->ID;
293
			$wp_post_types['product']->post_title 	= $shop_page->post_title;
294
			$wp_post_types['product']->post_name 	= $shop_page->post_name;
295
			$wp_post_types['product']->post_type    = $shop_page->post_type;
296
			$wp_post_types['product']->ancestors    = get_ancestors( $shop_page->ID, $shop_page->post_type );
297
298
			// Fix conditional Functions like is_front_page
299
			$q->is_singular          = false;
300
			$q->is_post_type_archive = true;
301
			$q->is_archive           = true;
302
			$q->is_page              = true;
303
304
			// Remove post type archive name from front page title tag
305
			add_filter( 'post_type_archive_title', '__return_empty_string', 5 );
306
307
			// Fix WP SEO
308
			if ( class_exists( 'WPSEO_Meta' ) ) {
309
				add_filter( 'wpseo_metadesc', array( $this, 'wpseo_metadesc' ) );
310
				add_filter( 'wpseo_metakey', array( $this, 'wpseo_metakey' ) );
311
			}
312
313
		// Only apply to product categories, the product post archive, the shop page, product tags, and product attribute taxonomies
314
		} elseif ( ! $q->is_post_type_archive( 'product' ) && ! $q->is_tax( get_object_taxonomies( 'product' ) ) ) {
315
			return;
316
		}
317
318
		$this->product_query( $q );
319
320
		if ( is_search() ) {
321
			add_filter( 'posts_where', array( $this, 'search_post_excerpt' ) );
322
			add_filter( 'wp', array( $this, 'remove_posts_where' ) );
323
		}
324
325
		// And remove the pre_get_posts hook
326
		$this->remove_product_query();
327
	}
328
329
	/**
330
	 * Search post excerpt.
331
	 *
332
	 * @access public
333
	 * @param string $where (default: '')
334
	 * @return string (modified where clause)
335
	 */
336
	public function search_post_excerpt( $where = '' ) {
337
		global $wp_the_query;
338
339
		// If this is not a WC Query, do not modify the query
340
		if ( empty( $wp_the_query->query_vars['wc_query'] ) || empty( $wp_the_query->query_vars['s'] ) )
341
			return $where;
342
343
		$where = preg_replace(
344
			"/post_title\s+LIKE\s*(\'\%[^\%]+\%\')/",
345
			"post_title LIKE $1) OR (post_excerpt LIKE $1", $where );
346
347
		return $where;
348
	}
349
350
	/**
351
	 * WP SEO meta description.
352
	 *
353
	 * Hooked into wpseo_ hook already, so no need for function_exist.
354
	 *
355
	 * @access public
356
	 * @return string
357
	 */
358
	public function wpseo_metadesc() {
359
		return WPSEO_Meta::get_value( 'metadesc', wc_get_page_id( 'shop' ) );
360
	}
361
362
	/**
363
	 * WP SEO meta key.
364
	 *
365
	 * Hooked into wpseo_ hook already, so no need for function_exist.
366
	 *
367
	 * @access public
368
	 * @return string
369
	 */
370
	public function wpseo_metakey() {
371
		return WPSEO_Meta::get_value( 'metakey', wc_get_page_id( 'shop' ) );
372
	}
373
374
	/**
375
	 * Query the products, applying sorting/ordering etc. This applies to the main wordpress loop.
376
	 *
377
	 * @param mixed $q
378
	 */
379
	public function product_query( $q ) {
380
		// Ordering query vars
381
		$ordering  = $this->get_catalog_ordering_args();
382
		$q->set( 'orderby', $ordering['orderby'] );
383
		$q->set( 'order', $ordering['order'] );
384
		if ( isset( $ordering['meta_key'] ) ) {
385
			$q->set( 'meta_key', $ordering['meta_key'] );
386
		}
387
388
		// Query vars that affect posts shown
389
		$q->set( 'meta_query', $this->get_meta_query( $q->get( 'meta_query' ) ) );
390
		$q->set( 'tax_query', $this->get_tax_query( $q->get( 'tax_query' ) ) );
391
		$q->set( 'posts_per_page', $q->get( 'posts_per_page' ) ? $q->get( 'posts_per_page' ) : apply_filters( 'loop_shop_per_page', get_option( 'posts_per_page' ) ) );
392
		$q->set( 'wc_query', 'product_query' );
393
		$q->set( 'post__in', array_unique( apply_filters( 'loop_shop_post_in', array() ) ) );
394
395
		do_action( 'woocommerce_product_query', $q, $this );
396
	}
397
398
399
	/**
400
	 * Remove the query.
401
	 */
402
	public function remove_product_query() {
403
		remove_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
404
	}
405
406
	/**
407
	 * Remove ordering queries.
408
	 */
409
	public function remove_ordering_args() {
410
		remove_filter( 'posts_clauses', array( $this, 'order_by_popularity_post_clauses' ) );
411
		remove_filter( 'posts_clauses', array( $this, 'order_by_rating_post_clauses' ) );
412
	}
413
414
	/**
415
	 * Remove the posts_where filter.
416
	 */
417
	public function remove_posts_where() {
418
		remove_filter( 'posts_where', array( $this, 'search_post_excerpt' ) );
419
	}
420
421
	/**
422
	 * Returns an array of arguments for ordering products based on the selected values.
423
	 *
424
	 * @access public
425
	 * @return array
426
	 */
427
	public function get_catalog_ordering_args( $orderby = '', $order = '' ) {
428
		global $wpdb;
429
430
		// Get ordering from query string unless defined
431
		if ( ! $orderby ) {
432
			$orderby_value = isset( $_GET['orderby'] ) ? wc_clean( $_GET['orderby'] ) : apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby' ) );
433
434
			// Get order + orderby args from string
435
			$orderby_value = explode( '-', $orderby_value );
436
			$orderby       = esc_attr( $orderby_value[0] );
437
			$order         = ! empty( $orderby_value[1] ) ? $orderby_value[1] : $order;
438
		}
439
440
		$orderby = strtolower( $orderby );
441
		$order   = strtoupper( $order );
442
		$args    = array();
443
444
		// default - menu_order
445
		$args['orderby']  = 'menu_order title';
446
		$args['order']    = $order == 'DESC' ? 'DESC' : 'ASC';
447
		$args['meta_key'] = '';
448
449
		switch ( $orderby ) {
450
			case 'rand' :
451
				$args['orderby']  = 'rand';
452
			break;
453
			case 'date' :
454
				$args['orderby']  = 'date ID';
455
				$args['order']    = $order == 'ASC' ? 'ASC' : 'DESC';
456
			break;
457
			case 'price' :
458
				$args['orderby']  = "meta_value_num ID";
459
				$args['order']    = $order == 'DESC' ? 'DESC' : 'ASC';
460
				$args['meta_key'] = '_price';
461
			break;
462
			case 'popularity' :
463
				$args['meta_key'] = 'total_sales';
464
465
				// Sorting handled later though a hook
466
				add_filter( 'posts_clauses', array( $this, 'order_by_popularity_post_clauses' ) );
467
			break;
468
			case 'rating' :
469
				// Sorting handled later though a hook
470
				add_filter( 'posts_clauses', array( $this, 'order_by_rating_post_clauses' ) );
471
			break;
472
			case 'title' :
473
				$args['orderby']  = 'title';
474
				$args['order']    = $order == 'DESC' ? 'DESC' : 'ASC';
475
			break;
476
		}
477
478
		return apply_filters( 'woocommerce_get_catalog_ordering_args', $args );
479
	}
480
481
	/**
482
	 * WP Core doens't let us change the sort direction for invidual orderby params - https://core.trac.wordpress.org/ticket/17065.
483
	 *
484
	 * This lets us sort by meta value desc, and have a second orderby param.
485
	 *
486
	 * @access public
487
	 * @param array $args
488
	 * @return array
489
	 */
490
	public function order_by_popularity_post_clauses( $args ) {
491
		global $wpdb;
492
		$args['orderby'] = "$wpdb->postmeta.meta_value+0 DESC, $wpdb->posts.post_date DESC";
493
		return $args;
494
	}
495
496
	/**
497
	 * Order by rating post clauses.
498
	 *
499
	 * @access public
500
	 * @param array $args
501
	 * @return array
502
	 */
503
	public function order_by_rating_post_clauses( $args ) {
504
		global $wpdb;
505
506
		$args['fields'] .= ", AVG( $wpdb->commentmeta.meta_value ) as average_rating ";
507
		$args['where']  .= " AND ( $wpdb->commentmeta.meta_key = 'rating' OR $wpdb->commentmeta.meta_key IS null ) ";
508
		$args['join']   .= "
509
			LEFT OUTER JOIN $wpdb->comments ON($wpdb->posts.ID = $wpdb->comments.comment_post_ID)
510
			LEFT JOIN $wpdb->commentmeta ON($wpdb->comments.comment_ID = $wpdb->commentmeta.comment_id)
511
		";
512
		$args['orderby'] = "average_rating DESC, $wpdb->posts.post_date DESC";
513
		$args['groupby'] = "$wpdb->posts.ID";
514
515
		return $args;
516
	}
517
518
	/**
519
	 * Appends meta queries to an array.
520
	 * @access public
521
	 * @param array $meta_query
522
	 * @return array
523
	 */
524
	public function get_meta_query( $meta_query = array() ) {
525
		if ( ! is_array( $meta_query ) ) {
526
			$meta_query = array();
527
		}
528
529
		$meta_query[] = $this->visibility_meta_query();
530
		$meta_query[] = $this->stock_status_meta_query();
531
		$meta_query[] = $this->price_filter_meta_query();
532
		$meta_query[] = $this->rating_filter_meta_query();
533
534
		return array_filter( apply_filters( 'woocommerce_product_query_meta_query', $meta_query, $this ) );
535
	}
536
537
	/**
538
	 * Return a meta query for filtering by price.
539
	 * @return array
540
	 */
541
	private function price_filter_meta_query() {
542
		if ( isset( $_GET['max_price'] ) || isset( $_GET['min_price'] ) ) {
543
			$min = isset( $_GET['min_price'] ) ? floatval( $_GET['min_price'] ) : 0;
544
			$max = isset( $_GET['max_price'] ) ? floatval( $_GET['max_price'] ) : 9999999999;
545
546
			/**
547
			 * Adjust if the store taxes are not displayed how they are stored.
548
			 * Max is left alone because the filter was already increased.
549
			 * Kicks in when prices excluding tax are displayed including tax.
550
			 */
551
			if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) {
552
				$tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() );
553
				$class_min   = $min;
554
555
				foreach ( $tax_classes as $tax_class ) {
556
					if ( $tax_rates = WC_Tax::get_rates( $tax_class ) ) {
557
						$class_min = $min - WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $min, $tax_rates ) );
558
					}
559
				}
560
561
				$min = $class_min;
562
			}
563
564
			return array(
565
				'key'          => '_price',
566
				'value'        => array( $min, $max ),
567
				'compare'      => 'BETWEEN',
568
				'type'         => 'DECIMAL',
569
				'price_filter' => true,
570
			);
571
		}
572
		return array();
573
	}
574
575
	/**
576
	 * Return a meta query for filtering by rating.
577
	 * @return array
578
	 */
579
	public function rating_filter_meta_query() {
580
		return isset( $_GET['min_rating'] ) ? array(
581
			'key'           => '_wc_average_rating',
582
			'value'         => isset( $_GET['min_rating'] ) ? floatval( $_GET['min_rating'] ) : 0,
583
			'compare'       => '>=',
584
			'type'          => 'DECIMAL',
585
			'rating_filter' => true,
586
		) : array();
587
	}
588
589
	/**
590
	 * Returns a meta query to handle product visibility.
591
	 * @param string $compare (default: 'IN')
592
	 * @return array
593
	 */
594
	public function visibility_meta_query( $compare = 'IN' ) {
595
		return array(
596
			'key'     => '_visibility',
597
			'value'   => is_search() ? array( 'visible', 'search' ) : array( 'visible', 'catalog' ),
598
			'compare' => $compare,
599
		);
600
	}
601
602
	/**
603
	 * Returns a meta query to handle product stock status.
604
	 *
605
	 * @access public
606
	 * @param string $status (default: 'instock')
607
	 * @return array
608
	 */
609
	public function stock_status_meta_query( $status = 'instock' ) {
610
		return 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ? array(
611
			'key' 		=> '_stock_status',
612
			'value' 	=> $status,
613
			'compare' 	=> '=',
614
		) : array();
615
	}
616
617
	/**
618
	 * Appends tax queries to an array.
619
	 * @param array $tax_query
620
	 * @return array
621
	 */
622
	public function get_tax_query( $tax_query = array() ) {
623
		if ( ! is_array( $tax_query ) ) {
624
			$tax_query = array();
625
		}
626
627
		// Layered nav filters on terms
628
		if ( $_chosen_attributes = $this->get_layered_nav_chosen_attributes() ) {
629
			foreach ( $_chosen_attributes as $taxonomy => $data ) {
630
				$tax_query[] = array(
631
					'taxonomy' => $taxonomy,
632
					'field'    => 'slug',
633
					'terms'    => $data['terms'],
634
					'operator' => 'and' === $data['query_type'] ? 'AND' : 'IN',
635
					'include_children' => false,
636
				);
637
			}
638
		}
639
640
		return array_filter( apply_filters( 'woocommerce_product_query_tax_query', $tax_query, $this ) );
641
	}
642
643
	/**
644
	 * Get the tax query which was used by the main query.
645
	 * @return array
646
	 */
647
	public static function get_main_tax_query() {
648
		global $wp_the_query;
649
650
		$args      = $wp_the_query->query_vars;
651
		$tax_query = isset( $args['tax_query'] ) ? $args['tax_query'] : array();
652
653
		if ( ! empty( $args['taxonomy'] ) && ! empty( $args['term'] ) ) {
654
			$tax_query[ $args['taxonomy'] ] = array(
655
				'taxonomy' => $args['taxonomy'],
656
				'terms'    => array( $args['term'] ),
657
				'field'    => 'slug',
658
			);
659
		}
660
661 View Code Duplication
		if ( ! empty( $args['product_cat'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
662
			$tax_query[ 'product_cat' ] = array(
663
				'taxonomy' => 'product_cat',
664
				'terms'    => array( $args['product_cat'] ),
665
				'field'    => 'slug',
666
			);
667
		}
668
669 View Code Duplication
		if ( ! empty( $args['product_tag'] ) ) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
670
			$tax_query[ 'product_tag' ] = array(
671
				'taxonomy' => 'product_tag',
672
				'terms'    => array( $args['product_tag'] ),
673
				'field'    => 'slug',
674
			);
675
		}
676
677
		return $tax_query;
678
	}
679
680
	/**
681
	 * Get the meta query which was used by the main query.
682
	 * @return array
683
	 */
684
	public static function get_main_meta_query() {
685
		global $wp_the_query;
686
687
		$args       = $wp_the_query->query_vars;
688
		$meta_query = isset( $args['meta_query'] ) ? $args['meta_query'] : array();
689
690
		return $meta_query;
691
	}
692
693
	/**
694
	 * Layered Nav Init.
695
	 */
696
	public static function get_layered_nav_chosen_attributes() {
697
		if ( ! is_array( self::$_chosen_attributes ) ) {
698
			self::$_chosen_attributes = array();
699
700
			if ( $attribute_taxonomies = wc_get_attribute_taxonomies() ) {
701
				foreach ( $attribute_taxonomies as $tax ) {
702
					$attribute    = wc_sanitize_taxonomy_name( $tax->attribute_name );
703
					$taxonomy     = wc_attribute_taxonomy_name( $attribute );
704
					$filter_terms = ! empty( $_GET[ 'filter_' . $attribute ] ) ? explode( ',', wc_clean( $_GET[ 'filter_' . $attribute ] ) ) : array();
705
706
					if ( empty( $filter_terms ) || ! taxonomy_exists( $taxonomy ) ) {
707
						continue;
708
					}
709
710
					$query_type = ! empty( $_GET[ 'query_type_' . $attribute ] ) && in_array( $_GET[ 'query_type_' . $attribute ], array( 'and', 'or' ) ) ? wc_clean( $_GET[ 'query_type_' . $attribute ] ) : '';
711
					self::$_chosen_attributes[ $taxonomy ]['terms']      = array_map( 'sanitize_title', $filter_terms ); // Ensures correct encoding
712
					self::$_chosen_attributes[ $taxonomy ]['query_type'] = $query_type ? $query_type : apply_filters( 'woocommerce_layered_nav_default_query_type', 'and' );
713
				}
714
			}
715
		}
716
		return self::$_chosen_attributes;
717
	}
718
719
	/**
720
	 * @deprecated 2.6.0
721
	 */
722
	public function layered_nav_init() {
723
		_deprecated_function( 'layered_nav_init', '2.6', '' );
724
	}
725
726
	/**
727
	 * Get an unpaginated list all product ID's (both filtered and unfiltered). Makes use of transients.
728
	 * @deprecated 2.6.0 due to performance concerns
729
	 */
730
	public function get_products_in_view() {
731
		_deprecated_function( 'get_products_in_view', '2.6', '' );
732
	}
733
734
	/**
735
	 * Layered Nav post filter.
736
	 * @deprecated 2.6.0 due to performance concerns
737
	 */
738
	public function layered_nav_query( $filtered_posts ) {
0 ignored issues
show
Unused Code introduced by
The parameter $filtered_posts is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
739
		_deprecated_function( 'layered_nav_query', '2.6', '' );
740
	}
741
}
742