Completed
Push — master ( 1ca9d8...e20565 )
by Mike
08:40
created

WC_Query::search_post_excerpt()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 13
rs 9.4285
cc 3
eloc 8
nc 2
nop 1
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 21 and the first side effect is on line 13.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
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.3.0
7
 * @package		WooCommerce/Classes
8
 * @category	Class
9
 * @author 		WooThemes
10
 */
11
12
if ( ! defined( 'ABSPATH' ) ) {
13
	exit;
14
}
15
16
if ( ! class_exists( 'WC_Query' ) ) :
17
18
/**
19
 * WC_Query Class.
20
 */
21
class WC_Query {
22
23
	/** @public array Query vars to add to wp */
24
	public $query_vars = array();
25
26
	/** @public array Unfiltered product ids (before layered nav etc) */
27
	public $unfiltered_product_ids 	= array();
28
29
	/** @public array Filtered product ids (after layered nav) */
30
	public $filtered_product_ids 	= array();
31
32
	/** @public array Filtered product ids (after layered nav, per taxonomy) */
33
	public $filtered_product_ids_for_taxonomy 	= array();
34
35
	/** @public array Product IDs that match the layered nav + price filter */
36
	public $post__in 		= array();
37
38
	/** @public array|string The meta query for the page */
39
	public $meta_query 		= '';
40
41
	/** @public array Post IDs matching layered nav only */
42
	public $layered_nav_post__in 	= array();
43
44
	/** @public array Stores post IDs matching layered nav, so price filter can find max price in view */
45
	public $layered_nav_product_ids = array();
46
47
	/**
48
	 * Constructor for the query class. Hooks in methods.
49
	 *
50
	 * @access public
51
	 */
52
	public function __construct() {
53
		add_action( 'init', array( $this, 'add_endpoints' ) );
54
		add_action( 'init', array( $this, 'layered_nav_init' ) );
55
		add_action( 'init', array( $this, 'price_filter_init' ) );
56
57
		if ( ! is_admin() ) {
58
			add_action( 'wp_loaded', array( $this, 'get_errors' ), 20 );
59
			add_filter( 'query_vars', array( $this, 'add_query_vars'), 0 );
60
			add_action( 'parse_request', array( $this, 'parse_request'), 0 );
61
			add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
62
			add_filter( 'the_posts', array( $this, 'the_posts' ), 11, 2 );
63
			add_action( 'wp', array( $this, 'remove_product_query' ) );
64
			add_action( 'wp', array( $this, 'remove_ordering_args' ) );
65
		}
66
67
		$this->init_query_vars();
68
	}
69
70
	/**
71
	 * Get any errors from querystring.
72
	 */
73
	public function get_errors() {
74
		if ( ! empty( $_GET['wc_error'] ) && ( $error = sanitize_text_field( $_GET['wc_error'] ) ) && ! wc_has_notice( $error, 'error' ) ) {
75
			wc_add_notice( $error, 'error' );
76
		}
77
	}
78
79
	/**
80
	 * Init query vars by loading options.
81
	 */
82
	public function init_query_vars() {
83
		// Query vars to add to WP
84
		$this->query_vars = array(
85
			// Checkout actions
86
			'order-pay'          => get_option( 'woocommerce_checkout_pay_endpoint', 'order-pay' ),
87
			'order-received'     => get_option( 'woocommerce_checkout_order_received_endpoint', 'order-received' ),
88
89
			// My account actions
90
			'view-order'         => get_option( 'woocommerce_myaccount_view_order_endpoint', 'view-order' ),
91
			'edit-account'       => get_option( 'woocommerce_myaccount_edit_account_endpoint', 'edit-account' ),
92
			'edit-address'       => get_option( 'woocommerce_myaccount_edit_address_endpoint', 'edit-address' ),
93
			'lost-password'      => get_option( 'woocommerce_myaccount_lost_password_endpoint', 'lost-password' ),
94
			'customer-logout'    => get_option( 'woocommerce_logout_endpoint', 'customer-logout' ),
95
			'add-payment-method' => get_option( 'woocommerce_myaccount_add_payment_method_endpoint', 'add-payment-method' ),
96
		);
97
	}
98
99
	/**
100
	 * Get page title for an endpoint.
101
	 * @param  string
102
	 * @return string
103
	 */
104
	public function get_endpoint_title( $endpoint ) {
105
		global $wp;
106
107
		switch ( $endpoint ) {
108
			case 'order-pay' :
109
				$title = __( 'Pay for Order', 'woocommerce' );
110
			break;
111
			case 'order-received' :
112
				$title = __( 'Order Received', 'woocommerce' );
113
			break;
114
			case 'view-order' :
115
				$order = wc_get_order( $wp->query_vars['view-order'] );
116
				$title = ( $order ) ? sprintf( __( 'Order #%s', 'woocommerce' ), $order->get_order_number() ) : '';
117
			break;
118
			case 'edit-account' :
119
				$title = __( 'Edit Account Details', 'woocommerce' );
120
			break;
121
			case 'edit-address' :
122
				$title = __( 'Edit Address', 'woocommerce' );
123
			break;
124
			case 'add-payment-method' :
125
				$title = __( 'Add Payment Method', 'woocommerce' );
126
			break;
127
			case 'lost-password' :
128
				$title = __( 'Lost Password', 'woocommerce' );
129
			break;
130
			default :
131
				$title = '';
132
			break;
133
		}
134
		return $title;
135
	}
136
137
	/**
138
	 * Add endpoints for query vars.
139
	 */
140
	public function add_endpoints() {
141
		foreach ( $this->query_vars as $key => $var ) {
142
			add_rewrite_endpoint( $var, EP_ROOT | EP_PAGES );
143
		}
144
	}
145
146
	/**
147
	 * Add query vars.
148
	 *
149
	 * @access public
150
	 * @param array $vars
151
	 * @return array
152
	 */
153
	public function add_query_vars( $vars ) {
154
		foreach ( $this->query_vars as $key => $var ) {
155
			$vars[] = $key;
156
		}
157
158
		return $vars;
159
	}
160
161
	/**
162
	 * Get query vars.
163
	 *
164
	 * @return array
165
	 */
166
	public function get_query_vars() {
167
		return $this->query_vars;
168
	}
169
170
	/**
171
	 * Get query current active query var.
172
	 *
173
	 * @return string
174
	 */
175
	public function get_current_endpoint() {
176
		global $wp;
177
		foreach ( $this->get_query_vars() as $key => $value ) {
178
			if ( isset( $wp->query_vars[ $key ] ) ) {
179
				return $key;
180
			}
181
		}
182
		return '';
183
	}
184
185
	/**
186
	 * Parse the request and look for query vars - endpoints may not be supported.
187
	 */
188
	public function parse_request() {
189
		global $wp;
190
191
		// Map query vars to their keys, or get them if endpoints are not supported
192
		foreach ( $this->query_vars as $key => $var ) {
193
			if ( isset( $_GET[ $var ] ) ) {
194
				$wp->query_vars[ $key ] = $_GET[ $var ];
195
			}
196
197
			elseif ( isset( $wp->query_vars[ $var ] ) ) {
198
				$wp->query_vars[ $key ] = $wp->query_vars[ $var ];
199
			}
200
		}
201
	}
202
203
	/**
204
	 * Hook into pre_get_posts to do the main product query.
205
	 *
206
	 * @param mixed $q query object
207
	 */
208
	public function pre_get_posts( $q ) {
209
		// We only want to affect the main query
210
		if ( ! $q->is_main_query() ) {
211
			return;
212
		}
213
214
		// Fix for verbose page rules
215
		if ( $GLOBALS['wp_rewrite']->use_verbose_page_rules && isset( $q->queried_object->ID ) && $q->queried_object->ID === wc_get_page_id( 'shop' ) ) {
216
			$q->set( 'post_type', 'product' );
217
			$q->set( 'page', '' );
218
			$q->set( 'pagename', '' );
219
220
			// Fix conditional Functions
221
			$q->is_archive           = true;
222
			$q->is_post_type_archive = true;
223
			$q->is_singular          = false;
224
			$q->is_page              = false;
225
		}
226
227
		// Fix for endpoints on the homepage
228
		if ( $q->is_home() && 'page' === get_option( 'show_on_front' ) && absint( get_option( 'page_on_front' ) ) !== absint( $q->get( 'page_id' ) ) ) {
229
			$_query = wp_parse_args( $q->query );
230
			if ( ! empty( $_query ) && array_intersect( array_keys( $_query ), array_keys( $this->query_vars ) ) ) {
231
				$q->is_page     = true;
232
				$q->is_home     = false;
233
				$q->is_singular = true;
234
				$q->set( 'page_id', (int) get_option( 'page_on_front' ) );
235
				add_filter( 'redirect_canonical', '__return_false' );
236
			}
237
		}
238
239
		// When orderby is set, WordPress shows posts. Get around that here.
240
		if ( $q->is_home() && 'page' === get_option( 'show_on_front' ) && absint( get_option( 'page_on_front' ) ) === wc_get_page_id( 'shop' ) ) {
241
			$_query = wp_parse_args( $q->query );
242
			if ( empty( $_query ) || ! array_diff( array_keys( $_query ), array( 'preview', 'page', 'paged', 'cpage', 'orderby' ) ) ) {
243
				$q->is_page = true;
244
				$q->is_home = false;
245
				$q->set( 'page_id', (int) get_option( 'page_on_front' ) );
246
				$q->set( 'post_type', 'product' );
247
			}
248
		}
249
250
		// Special check for shops with the product archive on front
251
		if ( $q->is_page() && 'page' === get_option( 'show_on_front' ) && absint( $q->get( 'page_id' ) ) === wc_get_page_id( 'shop' ) ) {
252
253
			// This is a front-page shop
254
			$q->set( 'post_type', 'product' );
255
			$q->set( 'page_id', '' );
256
257
			if ( isset( $q->query['paged'] ) ) {
258
				$q->set( 'paged', $q->query['paged'] );
259
			}
260
261
			// Define a variable so we know this is the front page shop later on
262
			define( 'SHOP_IS_ON_FRONT', true );
263
264
			// Get the actual WP page to avoid errors and let us use is_front_page()
265
			// This is hacky but works. Awaiting http://core.trac.wordpress.org/ticket/21096
266
			global $wp_post_types;
267
268
			$shop_page 	= get_post( wc_get_page_id( 'shop' ) );
269
270
			$wp_post_types['product']->ID 			= $shop_page->ID;
271
			$wp_post_types['product']->post_title 	= $shop_page->post_title;
272
			$wp_post_types['product']->post_name 	= $shop_page->post_name;
273
			$wp_post_types['product']->post_type    = $shop_page->post_type;
274
			$wp_post_types['product']->ancestors    = get_ancestors( $shop_page->ID, $shop_page->post_type );
275
276
			// Fix conditional Functions like is_front_page
277
			$q->is_singular          = false;
278
			$q->is_post_type_archive = true;
279
			$q->is_archive           = true;
280
			$q->is_page              = true;
281
282
			// Remove post type archive name from front page title tag
283
			add_filter( 'post_type_archive_title', '__return_empty_string', 5 );
284
285
			// Fix WP SEO
286
			if ( class_exists( 'WPSEO_Meta' ) ) {
287
				add_filter( 'wpseo_metadesc', array( $this, 'wpseo_metadesc' ) );
288
				add_filter( 'wpseo_metakey', array( $this, 'wpseo_metakey' ) );
289
			}
290
291
		// Only apply to product categories, the product post archive, the shop page, product tags, and product attribute taxonomies
292
		} elseif ( ! $q->is_post_type_archive( 'product' ) && ! $q->is_tax( get_object_taxonomies( 'product' ) ) ) {
293
			return;
294
		}
295
296
		$this->product_query( $q );
297
298
		if ( is_search() ) {
299
			add_filter( 'posts_where', array( $this, 'search_post_excerpt' ) );
300
			add_filter( 'wp', array( $this, 'remove_posts_where' ) );
301
		}
302
303
		// We're on a shop page so queue the woocommerce_get_products_in_view function
304
		add_action( 'wp', array( $this, 'get_products_in_view' ), 2);
305
306
		// And remove the pre_get_posts hook
307
		$this->remove_product_query();
308
	}
309
310
	/**
311
	 * Search post excerpt.
312
	 *
313
	 * @access public
314
	 * @param string $where (default: '')
315
	 * @return string (modified where clause)
316
	 */
317
	public function search_post_excerpt( $where = '' ) {
318
		global $wp_the_query;
319
320
		// If this is not a WC Query, do not modify the query
321
		if ( empty( $wp_the_query->query_vars['wc_query'] ) || empty( $wp_the_query->query_vars['s'] ) )
322
			return $where;
323
324
		$where = preg_replace(
325
			"/post_title\s+LIKE\s*(\'\%[^\%]+\%\')/",
326
			"post_title LIKE $1) OR (post_excerpt LIKE $1", $where );
327
328
		return $where;
329
	}
330
331
	/**
332
	 * WP SEO meta description.
333
	 *
334
	 * Hooked into wpseo_ hook already, so no need for function_exist.
335
	 *
336
	 * @access public
337
	 * @return string
338
	 */
339
	public function wpseo_metadesc() {
340
		return WPSEO_Meta::get_value( 'metadesc', wc_get_page_id('shop') );
341
	}
342
343
	/**
344
	 * WP SEO meta key.
345
	 *
346
	 * Hooked into wpseo_ hook already, so no need for function_exist.
347
	 *
348
	 * @access public
349
	 * @return string
350
	 */
351
	public function wpseo_metakey() {
352
		return WPSEO_Meta::get_value( 'metakey', wc_get_page_id('shop') );
353
	}
354
355
	/**
356
	 * Hook into the_posts to do the main product query if needed - relevanssi compatibility.
357
	 *
358
	 * @access public
359
	 * @param array $posts
360
	 * @param WP_Query|bool $query (default: false)
361
	 * @return array
362
	 */
363
	public function the_posts( $posts, $query = false ) {
364
		// Abort if there's no query
365
		if ( ! $query )
366
			return $posts;
367
368
		// Abort if we're not filtering posts
369
		if ( empty( $this->post__in ) )
370
			return $posts;
371
372
		// Abort if this query has already been done
373
		if ( ! empty( $query->wc_query ) )
374
			return $posts;
375
376
		// Abort if this isn't a search query
377
		if ( empty( $query->query_vars["s"] ) )
378
			return $posts;
379
380
		// Abort if we're not on a post type archive/product taxonomy
381
		if 	( ! $query->is_post_type_archive( 'product' ) && ! $query->is_tax( get_object_taxonomies( 'product' ) ) )
382
			return $posts;
383
384
		$filtered_posts   = array();
385
		$queried_post_ids = array();
386
387
		foreach ( $posts as $post ) {
388
			if ( in_array( $post->ID, $this->post__in ) ) {
389
				$filtered_posts[] = $post;
390
				$queried_post_ids[] = $post->ID;
391
			}
392
		}
393
394
		$query->posts = $filtered_posts;
395
		$query->post_count = count( $filtered_posts );
396
397
		// Ensure filters are set
398
		$this->unfiltered_product_ids = $queried_post_ids;
399
		$this->filtered_product_ids   = $queried_post_ids;
400
401 View Code Duplication
		if ( sizeof( $this->layered_nav_post__in ) > 0 ) {
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...
402
			$this->layered_nav_product_ids = array_intersect( $this->unfiltered_product_ids, $this->layered_nav_post__in );
403
		} else {
404
			$this->layered_nav_product_ids = $this->unfiltered_product_ids;
405
		}
406
407
		return $filtered_posts;
408
	}
409
410
411
	/**
412
	 * Query the products, applying sorting/ordering etc. This applies to the main wordpress loop.
413
	 *
414
	 * @param mixed $q
415
	 */
416
	public function product_query( $q ) {
417
418
		// Meta query
419
		$meta_query = $this->get_meta_query( $q->get( 'meta_query' ) );
420
421
		// Ordering
422
		$ordering   = $this->get_catalog_ordering_args();
423
424
		// Get a list of post id's which match the current filters set (in the layered nav and price filter)
425
		$post__in   = array_unique( apply_filters( 'loop_shop_post_in', array() ) );
426
427
		// Ordering query vars
428
		$q->set( 'orderby', $ordering['orderby'] );
429
		$q->set( 'order', $ordering['order'] );
430
		if ( isset( $ordering['meta_key'] ) ) {
431
			$q->set( 'meta_key', $ordering['meta_key'] );
432
		}
433
434
		// Query vars that affect posts shown
435
		$q->set( 'meta_query', $meta_query );
436
		$q->set( 'post__in', $post__in );
437
		$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' ) ) );
438
439
		// Set a special variable
440
		$q->set( 'wc_query', 'product_query' );
441
442
		// Store variables
443
		$this->post__in   = $post__in;
444
		$this->meta_query = $meta_query;
0 ignored issues
show
Documentation Bug introduced by
It seems like $meta_query of type array is incompatible with the declared type string of property $meta_query.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
445
446
		do_action( 'woocommerce_product_query', $q, $this );
447
	}
448
449
450
	/**
451
	 * Remove the query.
452
	 */
453
	public function remove_product_query() {
454
		remove_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
455
	}
456
457
	/**
458
	 * Remove ordering queries.
459
	 */
460
	public function remove_ordering_args() {
461
		remove_filter( 'posts_clauses', array( $this, 'order_by_popularity_post_clauses' ) );
462
		remove_filter( 'posts_clauses', array( $this, 'order_by_rating_post_clauses' ) );
463
	}
464
465
	/**
466
	 * Remove the posts_where filter.
467
	 */
468
	public function remove_posts_where() {
469
		remove_filter( 'posts_where', array( $this, 'search_post_excerpt' ) );
470
	}
471
472
473
	/**
474
	 * Get an unpaginated list all product ID's (both filtered and unfiltered). Makes use of transients.
475
	 */
476
	public function get_products_in_view() {
477
		global $wp_the_query;
478
479
		// Get main query
480
		$current_wp_query = $wp_the_query->query;
481
482
		// Get WP Query for current page (without 'paged')
483
		unset( $current_wp_query['paged'] );
484
485
		// Generate a transient name based on current query
486
		$transient_name = 'wc_uf_pid_' . md5( http_build_query( $current_wp_query ) . WC_Cache_Helper::get_transient_version( 'product_query' ) );
487
		$transient_name = ( is_search() ) ? $transient_name . '_s' : $transient_name;
488
489
		if ( false === ( $unfiltered_product_ids = get_transient( $transient_name ) ) ) {
490
491
			// Get all visible posts, regardless of filters
492
			$unfiltered_args = array_merge(
493
				$current_wp_query,
494
				array(
495
					'post_type'              => 'product',
496
					'numberposts'            => -1,
497
					'post_status'            => 'publish',
498
					'meta_query'             => $this->meta_query,
499
					'fields'                 => 'ids',
500
					'no_found_rows'          => true,
501
					'update_post_meta_cache' => false,
502
					'update_post_term_cache' => false,
503
					'pagename'               => '',
504
					'wc_query'               => 'get_products_in_view'
505
				)
506
			);
507
508
			$unfiltered_product_ids = apply_filters( 'woocommerce_unfiltered_product_ids', get_posts( $unfiltered_args ), $unfiltered_args );
509
510
			set_transient( $transient_name, $unfiltered_product_ids, DAY_IN_SECONDS * 30 );
511
		}
512
513
		// Store the variable
514
		$this->unfiltered_product_ids = $unfiltered_product_ids;
515
516
		// Also store filtered posts ids...
517 View Code Duplication
		if ( sizeof( $this->post__in ) > 0 ) {
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...
518
			$this->filtered_product_ids = array_intersect( $this->unfiltered_product_ids, $this->post__in );
519
		} else {
520
			$this->filtered_product_ids = $this->unfiltered_product_ids;
521
		}
522
523
		// And filtered post ids which just take layered nav into consideration (to find max price in the price widget)
524 View Code Duplication
		if ( sizeof( $this->layered_nav_post__in ) > 0 ) {
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...
525
			$this->layered_nav_product_ids = array_intersect( $this->unfiltered_product_ids, $this->layered_nav_post__in );
526
		} else {
527
			$this->layered_nav_product_ids = $this->unfiltered_product_ids;
528
		}
529
	}
530
531
532
	/**
533
	 * Returns an array of arguments for ordering products based on the selected values.
534
	 *
535
	 * @access public
536
	 * @return array
537
	 */
538
	public function get_catalog_ordering_args( $orderby = '', $order = '' ) {
539
		global $wpdb;
540
541
		// Get ordering from query string unless defined
542
		if ( ! $orderby ) {
543
			$orderby_value = isset( $_GET['orderby'] ) ? wc_clean( $_GET['orderby'] ) : apply_filters( 'woocommerce_default_catalog_orderby', get_option( 'woocommerce_default_catalog_orderby' ) );
544
545
			// Get order + orderby args from string
546
			$orderby_value = explode( '-', $orderby_value );
547
			$orderby       = esc_attr( $orderby_value[0] );
548
			$order         = ! empty( $orderby_value[1] ) ? $orderby_value[1] : $order;
549
		}
550
551
		$orderby = strtolower( $orderby );
552
		$order   = strtoupper( $order );
553
		$args    = array();
554
555
		// default - menu_order
556
		$args['orderby']  = 'menu_order title';
557
		$args['order']    = $order == 'DESC' ? 'DESC' : 'ASC';
558
		$args['meta_key'] = '';
559
560
		switch ( $orderby ) {
561
			case 'rand' :
562
				$args['orderby']  = 'rand';
563
			break;
564
			case 'date' :
565
				$args['orderby']  = 'date';
566
				$args['order']    = $order == 'ASC' ? 'ASC' : 'DESC';
567
			break;
568
			case 'price' :
569
				$args['orderby']  = "meta_value_num ID";
570
				$args['order']    = $order == 'DESC' ? 'DESC' : 'ASC';
571
				$args['meta_key'] = '_price';
572
			break;
573
			case 'popularity' :
574
				$args['meta_key'] = 'total_sales';
575
576
				// Sorting handled later though a hook
577
				add_filter( 'posts_clauses', array( $this, 'order_by_popularity_post_clauses' ) );
578
			break;
579
			case 'rating' :
580
				// Sorting handled later though a hook
581
				add_filter( 'posts_clauses', array( $this, 'order_by_rating_post_clauses' ) );
582
			break;
583
			case 'title' :
584
				$args['orderby']  = 'title';
585
				$args['order']    = $order == 'DESC' ? 'DESC' : 'ASC';
586
			break;
587
		}
588
589
		return apply_filters( 'woocommerce_get_catalog_ordering_args', $args );
590
	}
591
592
	/**
593
	 * WP Core doens't let us change the sort direction for invidual orderby params - http://core.trac.wordpress.org/ticket/17065.
594
	 *
595
	 * This lets us sort by meta value desc, and have a second orderby param.
596
	 *
597
	 * @access public
598
	 * @param array $args
599
	 * @return array
600
	 */
601
	public function order_by_popularity_post_clauses( $args ) {
602
		global $wpdb;
603
604
		$args['orderby'] = "$wpdb->postmeta.meta_value+0 DESC, $wpdb->posts.post_date DESC";
605
606
		return $args;
607
	}
608
609
	/**
610
	 * Order by rating post clauses.
611
	 *
612
	 * @access public
613
	 * @param array $args
614
	 * @return array
615
	 */
616
	public function order_by_rating_post_clauses( $args ) {
617
		global $wpdb;
618
619
		$args['fields'] .= ", AVG( $wpdb->commentmeta.meta_value ) as average_rating ";
620
621
		$args['where'] .= " AND ( $wpdb->commentmeta.meta_key = 'rating' OR $wpdb->commentmeta.meta_key IS null ) ";
622
623
		$args['join'] .= "
624
			LEFT OUTER JOIN $wpdb->comments ON($wpdb->posts.ID = $wpdb->comments.comment_post_ID)
625
			LEFT JOIN $wpdb->commentmeta ON($wpdb->comments.comment_ID = $wpdb->commentmeta.comment_id)
626
		";
627
628
		$args['orderby'] = "average_rating DESC, $wpdb->posts.post_date DESC";
629
630
		$args['groupby'] = "$wpdb->posts.ID";
631
632
		return $args;
633
	}
634
635
	/**
636
	 * Appends meta queries to an array.
637
	 * @access public
638
	 * @param array $meta_query
639
	 * @return array
640
	 */
641
	public function get_meta_query( $meta_query = array() ) {
642
		if ( ! is_array( $meta_query ) )
643
			$meta_query = array();
644
645
		$meta_query[] = $this->visibility_meta_query();
646
		$meta_query[] = $this->stock_status_meta_query();
647
648
		return array_filter( $meta_query );
649
	}
650
651
	/**
652
	 * Returns a meta query to handle product visibility.
653
	 *
654
	 * @access public
655
	 * @param string $compare (default: 'IN')
656
	 * @return array
657
	 */
658
	public function visibility_meta_query( $compare = 'IN' ) {
659
		if ( is_search() )
660
			$in = array( 'visible', 'search' );
661
		else
662
			$in = array( 'visible', 'catalog' );
663
664
		$meta_query = array(
665
			'key'     => '_visibility',
666
			'value'   => $in,
667
			'compare' => $compare
668
		);
669
670
		return $meta_query;
671
	}
672
673
	/**
674
	 * Returns a meta query to handle product stock status.
675
	 *
676
	 * @access public
677
	 * @param string $status (default: 'instock')
678
	 * @return array
679
	 */
680
	public function stock_status_meta_query( $status = 'instock' ) {
681
		$meta_query = array();
682
		if ( get_option( 'woocommerce_hide_out_of_stock_items' ) == 'yes' ) {
683
			$meta_query = array(
684
				'key' 		=> '_stock_status',
685
				'value' 	=> $status,
686
				'compare' 	=> '='
687
			);
688
		}
689
		return $meta_query;
690
	}
691
692
	/**
693
	 * Layered Nav Init.
694
	 */
695
	public function layered_nav_init( ) {
696
		if ( apply_filters( 'woocommerce_is_layered_nav_active', is_active_widget( false, false, 'woocommerce_layered_nav', true ) ) && ! is_admin() ) {
697
698
			global $_chosen_attributes;
699
700
			$_chosen_attributes = array();
701
702
			if ( $attribute_taxonomies = wc_get_attribute_taxonomies() ) {
703
				foreach ( $attribute_taxonomies as $tax ) {
704
					$attribute    = wc_sanitize_taxonomy_name( $tax->attribute_name );
705
					$taxonomy     = wc_attribute_taxonomy_name( $attribute );
706
					$name         = 'filter_' . $attribute;
0 ignored issues
show
Unused Code introduced by
$name is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
707
					$filter_terms = ! empty( $_GET[ 'filter_' . $attribute ] ) ? explode( ',', wc_clean( $_GET[ 'filter_' . $attribute ] ) ) : array();
708
					$query_type   = ! empty( $_GET[ 'query_type_' . $attribute ] ) && in_array( $_GET[ 'query_type_' . $attribute ], array( 'and', 'or' ) ) ? wc_clean( $_GET[ 'query_type_' . $attribute ] ) : '';
709
710
					if ( ! $query_type ) {
711
						$query_type = apply_filters( 'woocommerce_layered_nav_default_query_type', 'and' );
712
					}
713
714
					if ( ! empty( $filter_terms ) && taxonomy_exists( $taxonomy ) ) {
715
						$_chosen_attributes[ $taxonomy ]['terms']      = array_map( 'sanitize_title', $filter_terms ); // Ensures correct encoding
716
						$_chosen_attributes[ $taxonomy ]['query_type'] = $query_type;
717
					}
718
				}
719
			}
720
721
			add_filter( 'loop_shop_post_in', array( $this, 'layered_nav_query' ) );
722
		}
723
	}
724
725
	/**
726
	 * Layered Nav post filter.
727
	 *
728
	 * @param array $filtered_posts
729
	 * @return array
730
	 */
731
	public function layered_nav_query( $filtered_posts ) {
732
		global $_chosen_attributes;
733
734
		if ( sizeof( $_chosen_attributes ) > 0 ) {
735
736
			$matched_products   = array(
737
				'and' => array(),
738
				'or'  => array()
739
			);
740
			$filtered_attribute = array(
741
				'and' => false,
742
				'or'  => false
743
			);
744
745
			foreach ( $_chosen_attributes as $attribute => $data ) {
746
				$matched_products_from_attribute = array();
747
				$filtered = false;
748
749
				if ( sizeof( $data['terms'] ) > 0 ) {
750
					foreach ( $data['terms'] as $value ) {
751
752
						$args = array(
753
							'post_type' 	=> 'product',
754
							'numberposts' 	=> -1,
755
							'post_status' 	=> 'publish',
756
							'fields' 		=> 'ids',
757
							'no_found_rows' => true,
758
							'tax_query' => array(
759
								array(
760
									'taxonomy' 	=> $attribute,
761
									'terms' 	=> $value,
762
									'field' 	=> 'slug'
763
								)
764
							)
765
						);
766
767
						$post_ids = apply_filters( 'woocommerce_layered_nav_query_post_ids', get_posts( $args ), $args, $attribute, $value );
768
769
						if ( ! is_wp_error( $post_ids ) ) {
770
771
							if ( sizeof( $matched_products_from_attribute ) > 0 || $filtered ) {
772
								$matched_products_from_attribute = $data['query_type'] == 'or' ? array_merge( $post_ids, $matched_products_from_attribute ) : array_intersect( $post_ids, $matched_products_from_attribute );
773
							} else {
774
								$matched_products_from_attribute = $post_ids;
775
							}
776
777
							$filtered = true;
778
						}
779
					}
780
				}
781
782
				if ( sizeof( $matched_products[ $data['query_type'] ] ) > 0 || $filtered_attribute[ $data['query_type'] ] === true ) {
783
					$matched_products[ $data['query_type'] ] = ( $data['query_type'] == 'or' ) ? array_merge( $matched_products_from_attribute, $matched_products[ $data['query_type'] ] ) : array_intersect( $matched_products_from_attribute, $matched_products[ $data['query_type'] ] );
784
				} else {
785
					$matched_products[ $data['query_type'] ] = $matched_products_from_attribute;
786
				}
787
788
				$filtered_attribute[ $data['query_type'] ] = true;
789
790
				$this->filtered_product_ids_for_taxonomy[ $attribute ] = $matched_products_from_attribute;
791
			}
792
793
			// Combine our AND and OR result sets
794
			if ( $filtered_attribute['and'] && $filtered_attribute['or'] )
795
				$results = array_intersect( $matched_products[ 'and' ], $matched_products[ 'or' ] );
796
			else
797
				$results = array_merge( $matched_products[ 'and' ], $matched_products[ 'or' ] );
798
799
			if ( $filtered ) {
800
801
				WC()->query->layered_nav_post__in   = $results;
802
				WC()->query->layered_nav_post__in[] = 0;
803
804
				if ( sizeof( $filtered_posts ) == 0 ) {
805
					$filtered_posts   = $results;
806
					$filtered_posts[] = 0;
807
				} else {
808
					$filtered_posts   = array_intersect( $filtered_posts, $results );
809
					$filtered_posts[] = 0;
810
				}
811
812
			}
813
		}
814
		return (array) $filtered_posts;
815
	}
816
817
	/**
818
	 * Price filter Init.
819
	 */
820
	public function price_filter_init() {
821
		if ( apply_filters( 'woocommerce_is_price_filter_active', is_active_widget( false, false, 'woocommerce_price_filter', true ) ) && ! is_admin() ) {
822
823
			$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
824
825
			wp_register_script( 'wc-jquery-ui-touchpunch', WC()->plugin_url() . '/assets/js/jquery-ui-touch-punch/jquery-ui-touch-punch' . $suffix . '.js', array( 'jquery-ui-slider' ), WC_VERSION, true );
826
			wp_register_script( 'wc-price-slider', WC()->plugin_url() . '/assets/js/frontend/price-slider' . $suffix . '.js', array( 'jquery-ui-slider', 'wc-jquery-ui-touchpunch' ), WC_VERSION, true );
827
828
			wp_localize_script( 'wc-price-slider', 'woocommerce_price_slider_params', array(
829
				'currency_symbol' 	=> get_woocommerce_currency_symbol(),
830
				'currency_pos'      => get_option( 'woocommerce_currency_pos' ),
831
				'min_price'			=> isset( $_GET['min_price'] ) ? esc_attr( $_GET['min_price'] ) : '',
832
				'max_price'			=> isset( $_GET['max_price'] ) ? esc_attr( $_GET['max_price'] ) : ''
833
			) );
834
835
			add_filter( 'loop_shop_post_in', array( $this, 'price_filter' ) );
836
		}
837
	}
838
839
	/**
840
	 * Price Filter post filter.
841
	 *
842
	 * @param array $filtered_posts
843
	 * @return array
844
	 */
845
	public function price_filter( $filtered_posts = array() ) {
846
		global $wpdb;
847
848
		if ( isset( $_GET['max_price'] ) || isset( $_GET['min_price'] ) ) {
849
850
			$matched_products = array();
851
			$min              = isset( $_GET['min_price'] ) ? floatval( $_GET['min_price'] ) : 0;
852
			$max              = isset( $_GET['max_price'] ) ? floatval( $_GET['max_price'] ) : 9999999999;
853
854
			// If displaying prices in the shop including taxes, but prices don't include taxes..
855
			if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) {
856
				$tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() );
857
858
				foreach ( $tax_classes as $tax_class ) {
859
					$tax_rates = WC_Tax::get_rates( $tax_class );
860
					$min_class = $min - WC_Tax::get_tax_total( WC_Tax::calc_inclusive_tax( $min, $tax_rates ) );
861
					$max_class = $max - WC_Tax::get_tax_total( WC_Tax::calc_inclusive_tax( $max, $tax_rates ) );
862
863
					$matched_products_query = apply_filters( 'woocommerce_price_filter_results', $wpdb->get_results( $wpdb->prepare( "
864
						SELECT DISTINCT ID, post_parent, post_type FROM {$wpdb->posts}
865
						INNER JOIN {$wpdb->postmeta} pm1 ON ID = pm1.post_id
866
						INNER JOIN {$wpdb->postmeta} pm2 ON ID = pm2.post_id
867
						WHERE post_type IN ( 'product', 'product_variation' )
868
						AND post_status = 'publish'
869
						AND pm1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', apply_filters( 'woocommerce_price_filter_meta_keys', array( '_price' ) ) ) ) . "')
870
						AND pm1.meta_value BETWEEN %f AND %f
871
						AND pm2.meta_key = '_tax_class'
872
						AND pm2.meta_value = %s
873
					", $min_class, $max_class, sanitize_title( $tax_class ) ), OBJECT_K ), $min_class, $max_class );
874
875 View Code Duplication
					if ( $matched_products_query ) {
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...
876
						foreach ( $matched_products_query as $product ) {
877
							if ( $product->post_type == 'product' ) {
878
								$matched_products[] = $product->ID;
879
							}
880
							if ( $product->post_parent > 0 ) {
881
								$matched_products[] = $product->post_parent;
882
							}
883
						}
884
					}
885
				}
886
			} else {
887
				$matched_products_query = apply_filters( 'woocommerce_price_filter_results', $wpdb->get_results( $wpdb->prepare( "
888
					SELECT DISTINCT ID, post_parent, post_type FROM {$wpdb->posts}
889
					INNER JOIN {$wpdb->postmeta} pm1 ON ID = pm1.post_id
890
					WHERE post_type IN ( 'product', 'product_variation' )
891
					AND post_status = 'publish'
892
					AND pm1.meta_key IN ('" . implode( "','", array_map( 'esc_sql', apply_filters( 'woocommerce_price_filter_meta_keys', array( '_price' ) ) ) ) . "')
893
					AND pm1.meta_value BETWEEN %f AND %f
894
				", $min, $max ), OBJECT_K ), $min, $max );
895
896 View Code Duplication
				if ( $matched_products_query ) {
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...
897
					foreach ( $matched_products_query as $product ) {
898
						if ( $product->post_type == 'product' ) {
899
							$matched_products[] = $product->ID;
900
						}
901
						if ( $product->post_parent > 0 ) {
902
							$matched_products[] = $product->post_parent;
903
						}
904
					}
905
				}
906
			}
907
908
			$matched_products = array_unique( $matched_products );
909
910
			// Filter the id's
911
			if ( 0 === sizeof( $filtered_posts ) ) {
912
				$filtered_posts = $matched_products;
913
			} else {
914
				$filtered_posts = array_intersect( $filtered_posts, $matched_products );
915
			}
916
			$filtered_posts[] = 0;
917
		}
918
919
		return (array) $filtered_posts;
920
	}
921
922
}
923
924
endif;
925
926
return new WC_Query();
927