Completed
Push — master ( 4de8af...d756b9 )
by Mike
09:12
created

WC_Query::wpseo_metakey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
eloc 2
nc 1
nop 0
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
			if ( ! empty( $var ) ) {
161
				add_rewrite_endpoint( $var, $mask );
162
			}
163
		}
164
	}
165
166
	/**
167
	 * Add query vars.
168
	 *
169
	 * @access public
170
	 * @param array $vars
171
	 * @return array
172
	 */
173
	public function add_query_vars( $vars ) {
174
		foreach ( $this->query_vars as $key => $var ) {
175
			$vars[] = $key;
176
		}
177
		return $vars;
178
	}
179
180
	/**
181
	 * Get query vars.
182
	 *
183
	 * @return array
184
	 */
185
	public function get_query_vars() {
186
		return $this->query_vars;
187
	}
188
189
	/**
190
	 * Get query current active query var.
191
	 *
192
	 * @return string
193
	 */
194
	public function get_current_endpoint() {
195
		global $wp;
196
		foreach ( $this->get_query_vars() as $key => $value ) {
197
			if ( isset( $wp->query_vars[ $key ] ) ) {
198
				return $key;
199
			}
200
		}
201
		return '';
202
	}
203
204
	/**
205
	 * Parse the request and look for query vars - endpoints may not be supported.
206
	 */
207
	public function parse_request() {
208
		global $wp;
209
210
		// Map query vars to their keys, or get them if endpoints are not supported
211
		foreach ( $this->query_vars as $key => $var ) {
212
			if ( isset( $_GET[ $var ] ) ) {
213
				$wp->query_vars[ $key ] = $_GET[ $var ];
214
			}
215
216
			elseif ( isset( $wp->query_vars[ $var ] ) ) {
217
				$wp->query_vars[ $key ] = $wp->query_vars[ $var ];
218
			}
219
		}
220
	}
221
222
	/**
223
	 * Are we currently on the front page?
224
	 * @return boolean
225
	 */
226
	private function is_showing_page_on_front( $q ) {
227
		return $q->is_home() && 'page' === get_option( 'show_on_front' );
228
	}
229
230
	/**
231
	 * Is the front page a page we define?
232
	 * @return boolean
233
	 */
234
	private function page_on_front_is( $page_id ) {
235
		return absint( get_option( 'page_on_front' ) ) === absint( $page_id );
236
	}
237
238
	/**
239
	 * Hook into pre_get_posts to do the main product query.
240
	 *
241
	 * @param mixed $q query object
242
	 */
243
	public function pre_get_posts( $q ) {
244
		// We only want to affect the main query
245
		if ( ! $q->is_main_query() ) {
246
			return;
247
		}
248
249
		// Fix for endpoints on the homepage
250
		if ( $this->is_showing_page_on_front( $q ) && ! $this->page_on_front_is( $q->get( 'page_id' ) ) ) {
251
			$_query = wp_parse_args( $q->query );
252
			if ( ! empty( $_query ) && array_intersect( array_keys( $_query ), array_keys( $this->query_vars ) ) ) {
253
				$q->is_page     = true;
254
				$q->is_home     = false;
255
				$q->is_singular = true;
256
				$q->set( 'page_id', (int) get_option( 'page_on_front' ) );
257
				add_filter( 'redirect_canonical', '__return_false' );
258
			}
259
		}
260
261
		// When orderby is set, WordPress shows posts. Get around that here.
262
		if ( $this->is_showing_page_on_front( $q ) && $this->page_on_front_is( wc_get_page_id( 'shop' ) ) ) {
263
			$_query = wp_parse_args( $q->query );
264
			if ( empty( $_query ) || ! array_diff( array_keys( $_query ), array( 'preview', 'page', 'paged', 'cpage', 'orderby' ) ) ) {
265
				$q->is_page = true;
266
				$q->is_home = false;
267
				$q->set( 'page_id', (int) get_option( 'page_on_front' ) );
268
				$q->set( 'post_type', 'product' );
269
			}
270
		}
271
272
		// Fix product feeds
273
		if ( $q->is_feed() && $q->is_post_type_archive( 'product' ) ) {
274
			$q->is_comment_feed = false;
275
		}
276
277
		// Special check for shops with the product archive on front
278
		if ( $q->is_page() && 'page' === get_option( 'show_on_front' ) && absint( $q->get( 'page_id' ) ) === wc_get_page_id( 'shop' ) ) {
279
			// This is a front-page shop
280
			$q->set( 'post_type', 'product' );
281
			$q->set( 'page_id', '' );
282
283
			if ( isset( $q->query['paged'] ) ) {
284
				$q->set( 'paged', $q->query['paged'] );
285
			}
286
287
			// Define a variable so we know this is the front page shop later on
288
			define( 'SHOP_IS_ON_FRONT', true );
289
290
			// Get the actual WP page to avoid errors and let us use is_front_page()
291
			// This is hacky but works. Awaiting https://core.trac.wordpress.org/ticket/21096
292
			global $wp_post_types;
293
294
			$shop_page 	= get_post( wc_get_page_id( 'shop' ) );
295
296
			$wp_post_types['product']->ID 			= $shop_page->ID;
297
			$wp_post_types['product']->post_title 	= $shop_page->post_title;
298
			$wp_post_types['product']->post_name 	= $shop_page->post_name;
299
			$wp_post_types['product']->post_type    = $shop_page->post_type;
300
			$wp_post_types['product']->ancestors    = get_ancestors( $shop_page->ID, $shop_page->post_type );
301
302
			// Fix conditional Functions like is_front_page
303
			$q->is_singular          = false;
304
			$q->is_post_type_archive = true;
305
			$q->is_archive           = true;
306
			$q->is_page              = true;
307
308
			// Remove post type archive name from front page title tag
309
			add_filter( 'post_type_archive_title', '__return_empty_string', 5 );
310
311
			// Fix WP SEO
312
			if ( class_exists( 'WPSEO_Meta' ) ) {
313
				add_filter( 'wpseo_metadesc', array( $this, 'wpseo_metadesc' ) );
314
				add_filter( 'wpseo_metakey', array( $this, 'wpseo_metakey' ) );
315
			}
316
317
		// Only apply to product categories, the product post archive, the shop page, product tags, and product attribute taxonomies
318
		} elseif ( ! $q->is_post_type_archive( 'product' ) && ! $q->is_tax( get_object_taxonomies( 'product' ) ) ) {
319
			return;
320
		}
321
322
		$this->product_query( $q );
323
324
		if ( is_search() ) {
325
			add_filter( 'posts_where', array( $this, 'search_post_excerpt' ) );
326
			add_filter( 'wp', array( $this, 'remove_posts_where' ) );
327
		}
328
329
		// And remove the pre_get_posts hook
330
		$this->remove_product_query();
331
	}
332
333
	/**
334
	 * Search post excerpt.
335
	 *
336
	 * @access public
337
	 * @param string $where (default: '')
338
	 * @return string (modified where clause)
339
	 */
340
	public function search_post_excerpt( $where = '' ) {
341
		global $wp_the_query;
342
343
		// If this is not a WC Query, do not modify the query
344
		if ( empty( $wp_the_query->query_vars['wc_query'] ) || empty( $wp_the_query->query_vars['s'] ) )
345
			return $where;
346
347
		$where = preg_replace(
348
			"/post_title\s+LIKE\s*(\'\%[^\%]+\%\')/",
349
			"post_title LIKE $1) OR (post_excerpt LIKE $1", $where );
350
351
		return $where;
352
	}
353
354
	/**
355
	 * WP SEO meta description.
356
	 *
357
	 * Hooked into wpseo_ hook already, so no need for function_exist.
358
	 *
359
	 * @access public
360
	 * @return string
361
	 */
362
	public function wpseo_metadesc() {
363
		return WPSEO_Meta::get_value( 'metadesc', wc_get_page_id( 'shop' ) );
364
	}
365
366
	/**
367
	 * WP SEO meta key.
368
	 *
369
	 * Hooked into wpseo_ hook already, so no need for function_exist.
370
	 *
371
	 * @access public
372
	 * @return string
373
	 */
374
	public function wpseo_metakey() {
375
		return WPSEO_Meta::get_value( 'metakey', wc_get_page_id( 'shop' ) );
376
	}
377
378
	/**
379
	 * Query the products, applying sorting/ordering etc. This applies to the main wordpress loop.
380
	 *
381
	 * @param mixed $q
382
	 */
383
	public function product_query( $q ) {
384
		// Ordering query vars
385
		$ordering  = $this->get_catalog_ordering_args();
386
		$q->set( 'orderby', $ordering['orderby'] );
387
		$q->set( 'order', $ordering['order'] );
388
		if ( isset( $ordering['meta_key'] ) ) {
389
			$q->set( 'meta_key', $ordering['meta_key'] );
390
		}
391
392
		// Query vars that affect posts shown
393
		$q->set( 'meta_query', $this->get_meta_query( $q->get( 'meta_query' ) ) );
394
		$q->set( 'tax_query', $this->get_tax_query( $q->get( 'tax_query' ) ) );
395
		$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' ) ) );
396
		$q->set( 'wc_query', 'product_query' );
397
		$q->set( 'post__in', array_unique( apply_filters( 'loop_shop_post_in', array() ) ) );
398
399
		do_action( 'woocommerce_product_query', $q, $this );
400
	}
401
402
403
	/**
404
	 * Remove the query.
405
	 */
406
	public function remove_product_query() {
407
		remove_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
408
	}
409
410
	/**
411
	 * Remove ordering queries.
412
	 */
413
	public function remove_ordering_args() {
414
		remove_filter( 'posts_clauses', array( $this, 'order_by_popularity_post_clauses' ) );
415
		remove_filter( 'posts_clauses', array( $this, 'order_by_rating_post_clauses' ) );
416
	}
417
418
	/**
419
	 * Remove the posts_where filter.
420
	 */
421
	public function remove_posts_where() {
422
		remove_filter( 'posts_where', array( $this, 'search_post_excerpt' ) );
423
	}
424
425
	/**
426
	 * Returns an array of arguments for ordering products based on the selected values.
427
	 *
428
	 * @access public
429
	 * @return array
430
	 */
431
	public function get_catalog_ordering_args( $orderby = '', $order = '' ) {
432
		// Get ordering from query string unless defined
433
		if ( ! $orderby ) {
434
			$orderby_value = isset( $_GET['orderby'] ) ? wc_clean( $_GET['orderby'] ) : apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby' ) );
435
436
			// Get order + orderby args from string
437
			$orderby_value = explode( '-', $orderby_value );
438
			$orderby       = esc_attr( $orderby_value[0] );
439
			$order         = ! empty( $orderby_value[1] ) ? $orderby_value[1] : $order;
440
		}
441
442
		$orderby = strtolower( $orderby );
443
		$order   = strtoupper( $order );
444
		$args    = array();
445
446
		// default - menu_order
447
		$args['orderby']  = 'menu_order title';
448
		$args['order']    = $order == 'DESC' ? 'DESC' : 'ASC';
449
		$args['meta_key'] = '';
450
451
		switch ( $orderby ) {
452
			case 'rand' :
453
				$args['orderby']  = 'rand';
454
			break;
455
			case 'date' :
456
				$args['orderby']  = 'date ID';
457
				$args['order']    = $order == 'ASC' ? 'ASC' : 'DESC';
458
			break;
459
			case 'price' :
460
				$args['orderby']  = "meta_value_num ID";
461
				$args['order']    = $order == 'DESC' ? 'DESC' : 'ASC';
462
				$args['meta_key'] = '_price';
463
			break;
464
			case 'popularity' :
465
				$args['meta_key'] = 'total_sales';
466
467
				// Sorting handled later though a hook
468
				add_filter( 'posts_clauses', array( $this, 'order_by_popularity_post_clauses' ) );
469
			break;
470
			case 'rating' :
471
				// Sorting handled later though a hook
472
				add_filter( 'posts_clauses', array( $this, 'order_by_rating_post_clauses' ) );
473
			break;
474
			case 'title' :
475
				$args['orderby']  = 'title';
476
				$args['order']    = $order == 'DESC' ? 'DESC' : 'ASC';
477
			break;
478
		}
479
480
		return apply_filters( 'woocommerce_get_catalog_ordering_args', $args );
481
	}
482
483
	/**
484
	 * WP Core doens't let us change the sort direction for invidual orderby params - https://core.trac.wordpress.org/ticket/17065.
485
	 *
486
	 * This lets us sort by meta value desc, and have a second orderby param.
487
	 *
488
	 * @access public
489
	 * @param array $args
490
	 * @return array
491
	 */
492
	public function order_by_popularity_post_clauses( $args ) {
493
		global $wpdb;
494
		$args['orderby'] = "$wpdb->postmeta.meta_value+0 DESC, $wpdb->posts.post_date DESC";
495
		return $args;
496
	}
497
498
	/**
499
	 * Order by rating post clauses.
500
	 *
501
	 * @access public
502
	 * @param array $args
503
	 * @return array
504
	 */
505
	public function order_by_rating_post_clauses( $args ) {
506
		global $wpdb;
507
508
		$args['fields'] .= ", AVG( $wpdb->commentmeta.meta_value ) as average_rating ";
509
		$args['where']  .= " AND ( $wpdb->commentmeta.meta_key = 'rating' OR $wpdb->commentmeta.meta_key IS null ) ";
510
		$args['join']   .= "
511
			LEFT OUTER JOIN $wpdb->comments ON($wpdb->posts.ID = $wpdb->comments.comment_post_ID)
512
			LEFT JOIN $wpdb->commentmeta ON($wpdb->comments.comment_ID = $wpdb->commentmeta.comment_id)
513
		";
514
		$args['orderby'] = "average_rating DESC, $wpdb->posts.post_date DESC";
515
		$args['groupby'] = "$wpdb->posts.ID";
516
517
		return $args;
518
	}
519
520
	/**
521
	 * Appends meta queries to an array.
522
	 *
523
	 * @param  array $meta_query
524
	 * @return array
525
	 */
526
	public function get_meta_query( $meta_query = array() ) {
527
		if ( ! is_array( $meta_query ) ) {
528
			$meta_query = array();
529
		}
530
531
		$meta_query['visibility']    = $this->visibility_meta_query();
532
		$meta_query['stock_status']  = $this->stock_status_meta_query();
533
		$meta_query['price_filter']  = $this->price_filter_meta_query();
534
		$meta_query['rating_filter'] = $this->rating_filter_meta_query();
535
536
		return array_filter( apply_filters( 'woocommerce_product_query_meta_query', $meta_query, $this ) );
537
	}
538
539
	/**
540
	 * Return a meta query for filtering by price.
541
	 * @return array
542
	 */
543
	private function price_filter_meta_query() {
544
		if ( isset( $_GET['max_price'] ) || isset( $_GET['min_price'] ) ) {
545
			$min = isset( $_GET['min_price'] ) ? floatval( $_GET['min_price'] ) : 0;
546
			$max = isset( $_GET['max_price'] ) ? floatval( $_GET['max_price'] ) : 9999999999;
547
548
			/**
549
			 * Adjust if the store taxes are not displayed how they are stored.
550
			 * Max is left alone because the filter was already increased.
551
			 * Kicks in when prices excluding tax are displayed including tax.
552
			 */
553
			if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) {
554
				$tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() );
555
				$class_min   = $min;
556
557
				foreach ( $tax_classes as $tax_class ) {
558
					if ( $tax_rates = WC_Tax::get_rates( $tax_class ) ) {
559
						$class_min = $min - WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $min, $tax_rates ) );
560
					}
561
				}
562
563
				$min = $class_min;
564
			}
565
566
			return array(
567
				'key'          => '_price',
568
				'value'        => array( $min, $max ),
569
				'compare'      => 'BETWEEN',
570
				'type'         => 'DECIMAL',
571
				'price_filter' => true,
572
			);
573
		}
574
		return array();
575
	}
576
577
	/**
578
	 * Return a meta query for filtering by rating.
579
	 * @return array
580
	 */
581
	public function rating_filter_meta_query() {
582
		return isset( $_GET['min_rating'] ) ? array(
583
			'key'           => '_wc_average_rating',
584
			'value'         => isset( $_GET['min_rating'] ) ? floatval( $_GET['min_rating'] ) : 0,
585
			'compare'       => '>=',
586
			'type'          => 'DECIMAL',
587
			'rating_filter' => true,
588
		) : array();
589
	}
590
591
	/**
592
	 * Returns a meta query to handle product visibility.
593
	 * @param string $compare (default: 'IN')
594
	 * @return array
595
	 */
596
	public function visibility_meta_query( $compare = 'IN' ) {
597
		return array(
598
			'key'     => '_visibility',
599
			'value'   => is_search() ? array( 'visible', 'search' ) : array( 'visible', 'catalog' ),
600
			'compare' => $compare,
601
		);
602
	}
603
604
	/**
605
	 * Returns a meta query to handle product stock status.
606
	 *
607
	 * @access public
608
	 * @param string $status (default: 'instock')
609
	 * @return array
610
	 */
611
	public function stock_status_meta_query( $status = 'instock' ) {
612
		return 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) ? array(
613
			'key' 		=> '_stock_status',
614
			'value' 	=> $status,
615
			'compare' 	=> '=',
616
		) : array();
617
	}
618
619
	/**
620
	 * Appends tax queries to an array.
621
	 * @param array $tax_query
622
	 * @return array
623
	 */
624
	public function get_tax_query( $tax_query = array() ) {
625
		if ( ! is_array( $tax_query ) ) {
626
			$tax_query = array();
627
		}
628
629
		// Layered nav filters on terms
630
		if ( $_chosen_attributes = $this->get_layered_nav_chosen_attributes() ) {
631
			foreach ( $_chosen_attributes as $taxonomy => $data ) {
632
				$tax_query[] = array(
633
					'taxonomy' => $taxonomy,
634
					'field'    => 'slug',
635
					'terms'    => $data['terms'],
636
					'operator' => 'and' === $data['query_type'] ? 'AND' : 'IN',
637
					'include_children' => false,
638
				);
639
			}
640
		}
641
642
		return array_filter( apply_filters( 'woocommerce_product_query_tax_query', $tax_query, $this ) );
643
	}
644
645
	/**
646
	 * Get the tax query which was used by the main query.
647
	 * @return array
648
	 */
649
	public static function get_main_tax_query() {
650
		global $wp_the_query;
651
652
		$args      = $wp_the_query->query_vars;
653
		$tax_query = isset( $args['tax_query'] ) ? $args['tax_query'] : array();
654
655
		if ( ! empty( $args['taxonomy'] ) && ! empty( $args['term'] ) ) {
656
			$tax_query[ $args['taxonomy'] ] = array(
657
				'taxonomy' => $args['taxonomy'],
658
				'terms'    => array( $args['term'] ),
659
				'field'    => 'slug',
660
			);
661
		}
662
663 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...
664
			$tax_query[ 'product_cat' ] = array(
665
				'taxonomy' => 'product_cat',
666
				'terms'    => array( $args['product_cat'] ),
667
				'field'    => 'slug',
668
			);
669
		}
670
671 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...
672
			$tax_query[ 'product_tag' ] = array(
673
				'taxonomy' => 'product_tag',
674
				'terms'    => array( $args['product_tag'] ),
675
				'field'    => 'slug',
676
			);
677
		}
678
679
		return $tax_query;
680
	}
681
682
	/**
683
	 * Get the meta query which was used by the main query.
684
	 * @return array
685
	 */
686
	public static function get_main_meta_query() {
687
		global $wp_the_query;
688
689
		$args       = $wp_the_query->query_vars;
690
		$meta_query = isset( $args['meta_query'] ) ? $args['meta_query'] : array();
691
692
		return $meta_query;
693
	}
694
695
	/**
696
	 * Based on WP_Query::parse_search
697
	 */
698
	public static function get_main_search_query_sql() {
699
		global $wp_the_query, $wpdb;
700
701
		$args         = $wp_the_query->query_vars;
702
		$search_terms = isset( $args['search_terms'] ) ? $args['search_terms'] : array();
703
		$sql          = array();
704
705
		foreach ( $search_terms as $term ) {
706
			// Terms prefixed with '-' should be excluded.
707
			$include = '-' !== substr( $term, 0, 1 );
708
709
			if ( $include ) {
710
				$like_op  = 'LIKE';
711
				$andor_op = 'OR';
712
			} else {
713
				$like_op  = 'NOT LIKE';
714
				$andor_op = 'AND';
715
				$term     = substr( $term, 1 );
716
			}
717
718
			$like  = '%' . $wpdb->esc_like( $term ) . '%';
719
			$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 );
720
		}
721
722
		if ( ! empty( $sql ) && ! is_user_logged_in() ) {
723
			$sql[] = "($wpdb->posts.post_password = '')";
724
		}
725
726
		return implode( ' AND ', $sql );
727
	}
728
729
	/**
730
	 * Layered Nav Init.
731
	 */
732
	public static function get_layered_nav_chosen_attributes() {
733
		if ( ! is_array( self::$_chosen_attributes ) ) {
734
			self::$_chosen_attributes = array();
735
736
			if ( $attribute_taxonomies = wc_get_attribute_taxonomies() ) {
737
				foreach ( $attribute_taxonomies as $tax ) {
738
					$attribute    = wc_sanitize_taxonomy_name( $tax->attribute_name );
739
					$taxonomy     = wc_attribute_taxonomy_name( $attribute );
740
					$filter_terms = ! empty( $_GET[ 'filter_' . $attribute ] ) ? explode( ',', wc_clean( $_GET[ 'filter_' . $attribute ] ) ) : array();
741
742
					if ( empty( $filter_terms ) || ! taxonomy_exists( $taxonomy ) ) {
743
						continue;
744
					}
745
746
					$query_type = ! empty( $_GET[ 'query_type_' . $attribute ] ) && in_array( $_GET[ 'query_type_' . $attribute ], array( 'and', 'or' ) ) ? wc_clean( $_GET[ 'query_type_' . $attribute ] ) : '';
747
					self::$_chosen_attributes[ $taxonomy ]['terms']      = array_map( 'sanitize_title', $filter_terms ); // Ensures correct encoding
748
					self::$_chosen_attributes[ $taxonomy ]['query_type'] = $query_type ? $query_type : apply_filters( 'woocommerce_layered_nav_default_query_type', 'and' );
749
				}
750
			}
751
		}
752
		return self::$_chosen_attributes;
753
	}
754
755
	/**
756
	 * @deprecated 2.6.0
757
	 */
758
	public function layered_nav_init() {
759
		_deprecated_function( 'layered_nav_init', '2.6', '' );
760
	}
761
762
	/**
763
	 * Get an unpaginated list all product ID's (both filtered and unfiltered). Makes use of transients.
764
	 * @deprecated 2.6.0 due to performance concerns
765
	 */
766
	public function get_products_in_view() {
767
		_deprecated_function( 'get_products_in_view', '2.6', '' );
768
	}
769
770
	/**
771
	 * Layered Nav post filter.
772
	 * @deprecated 2.6.0 due to performance concerns
773
	 */
774
	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...
775
		_deprecated_function( 'layered_nav_query', '2.6', '' );
776
	}
777
}
778