Completed
Push — add/7777-ga-anonymize-ip ( 94dfd5 )
by
unknown
10:21
created

Jetpack_Search::set_facets()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 1
dl 0
loc 5
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
class Jetpack_Search {
4
5
	protected $found_posts = 0;
6
7
	/**
8
	 * The maximum offset ('from' param), since deep pages get exponentially slower.
9
	 *
10
	 * @see https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html
11
	 */
12
	protected $max_offset = 200;
13
14
	protected $search_result;
15
16
	protected $original_blog_id;
17
	protected $jetpack_blog_id;
18
19
	protected $aggregations = array();
20
	protected $max_aggregations_count = 100;
21
22
	protected static $instance;
23
24
	//Languages with custom analyzers, other languages are supported,
25
	// but are analyzed with the default analyzer.
26
	public static $analyzed_langs = array( 'ar', 'bg', 'ca', 'cs', 'da', 'de', 'el', 'en', 'es', 'eu', 'fa', 'fi', 'fr', 'he', 'hi', 'hu', 'hy', 'id', 'it', 'ja', 'ko', 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh' );
27
28
	protected function __construct() {
29
		/* Don't do anything, needs to be initialized via instance() method */
30
	}
31
32
	public function __clone() {
33
		wp_die( "Please don't __clone Jetpack_Search" );
34
	}
35
36
	public function __wakeup() {
37
		wp_die( "Please don't __wakeup Jetpack_Search" );
38
	}
39
40
	/**
41
	 * Get singleton instance of Jetpack_Search
42
	 *
43
	 * Instantiates and sets up a new instance if needed, or returns the singleton
44
	 *
45
	 * @module search
46
	 *
47
	 * @return Jetpack_Search The Jetpack_Search singleton
48
	 */
49
	public static function instance() {
50
		if ( ! isset( self::$instance ) ) {
51
			self::$instance = new Jetpack_Search();
52
53
			self::$instance->setup();
54
		}
55
56
		return self::$instance;
57
	}
58
59
	/**
60
	 * Perform various setup tasks for the class
61
	 *
62
	 * Checks various pre-requisites and adds hooks
63
	 *
64
	 * @module search
65
	 */
66
	public function setup() {
67
		if ( ! Jetpack::is_active() ) {
68
			return;
69
		}
70
71
		$this->jetpack_blog_id = Jetpack::get_option( 'id' );
72
73
		if ( ! $this->jetpack_blog_id ) {
74
			return;
75
		}
76
77
		$this->init_hooks();
78
	}
79
80
	/**
81
	 * Setup the various hooks needed for the plugin to take over Search duties
82
	 *
83
	 * @module search
84
	 */
85
	public function init_hooks() {
86
		add_action( 'widgets_init', array( $this, 'action__widgets_init' ) );
87
88
		if ( ! is_admin() ) {
89
			add_filter( 'posts_pre_query', array( $this, 'filter__posts_pre_query' ), 10, 2 );
90
91
			add_filter( 'jetpack_search_es_wp_query_args', array( $this, 'filter__add_date_filter_to_query' ),  10, 2 );
92
		}
93
	}
94
95
	/*
96
	 * Run a search on the WP.com public API.
97
	 *
98
	 * @module search
99
	 *
100
	 * @param array $es_args Args conforming to the WP.com /sites/<blog_id>/search endpoint
101
	 *
102
	 * @return object|WP_Error The response from the public api, or a WP_Error
103
	 */
104
	public function search( array $es_args ) {
105
		$endpoint    = sprintf( '/sites/%s/search', $this->jetpack_blog_id );
106
		$service_url = 'https://public-api.wordpress.com/rest/v1' . $endpoint;
107
108
		$do_authenticated_request = false;
109
110
		if ( class_exists( 'Jetpack_Client' ) &&
111
			isset( $es_args['authenticated_request'] ) &&
112
			true === $es_args['authenticated_request'] ) {
113
			$do_authenticated_request = true;
114
		}
115
116
		unset( $es_args['authenticated_request'] );
117
118
		$request_args = array(
119
			'headers' => array(
120
				'Content-Type' => 'application/json',
121
			),
122
			'timeout'    => 10,
123
			'user-agent' => 'jetpack_search',
124
		);
125
126
		$request_body = json_encode( $es_args );
127
128
		$start_time = microtime( true );
129
130
		if ( $do_authenticated_request ) {
131
			$request_args['method'] = 'POST';
132
133
			$request = Jetpack_Client::wpcom_json_api_request_as_blog( $endpoint, Jetpack_Client::WPCOM_JSON_API_VERSION, $request_args, $request_body );
134
		} else {
135
			$request_args = array_merge( $request_args, array(
136
				'body' => $request_body,
137
			) );
138
139
			$request = wp_remote_post( $service_url, $request_args );
140
		}
141
142
		$end_time = microtime( true );
143
144
		if ( is_wp_error( $request ) ) {
145
			return $request;
146
		}
147
148
		$response_code = wp_remote_retrieve_response_code( $request );
149
150
		if ( ! $response_code || $response_code < 200 || $response_code >= 300 ) {
151
			return new WP_Error( 'invalid_search_api_response', 'Invalid response from API - ' . $response_code );
152
		}
153
154
		$response = json_decode( wp_remote_retrieve_body( $request ), true );
155
156
		$took = is_array( $response ) && $response['took'] ? $response['took'] : null;
157
158
		$query = array(
159
			'args'          => $es_args,
160
			'response'      => $response,
161
			'response_code' => $response_code,
162
			'elapsed_time'   => ( $end_time - $start_time ) * 1000, // Convert from float seconds to ms
163
			'es_time'       => $took,
164
			'url'           => $service_url,
165
		);
166
167
		/**
168
		 * Fires after a search request has been performed
169
		 *
170
		 * Includes the following info in the $query parameter:
171
		 *
172
		 * array args Array of Elasticsearch arguments for the search
173
		 * array response Raw API response, JSON decoded
174
		 * int response_code HTTP response code of the request
175
		 * float elapsed_time Roundtrip time of the search request, in milliseconds
176
		 * float es_time Amount of time Elasticsearch spent running the request, in milliseconds
177
		 * string url API url that was queried
178
		 *
179
		 * @since 5.0
180
		 *
181
		 * @param array $query Array of information about the query performed
182
		 */
183
		do_action( 'did_jetpack_search_query', $query );
184
185
		return $response;
186
	}
187
188
	/**
189
	 * Bypass the normal Search query and offload it to Jetpack servers
190
	 *
191
	 * This is the main hook of the plugin and is responsible for returning the posts that match the search query
192
	 *
193
	 * @module search
194
	 *
195
	 * @param array $posts Current array of posts (still pre-query)
196
	 * @param WP_Query $query The WP_Query being filtered
197
	 *
198
	 * @return array Array of matching posts
199
	 */
200
	public function filter__posts_pre_query( $posts, $query ) {
201
		if ( ! $query->is_main_query() || ! $query->is_search() ) {
202
			return $posts;
203
		}
204
205
		$this->do_search( $query );
206
207
		if ( ! is_array( $this->search_result ) ) {
208
			return $posts;
209
		}
210
211
		// If no results, nothing to do
212
		if ( ! count( $this->search_result['results']['hits'] ) ) {
213
			return array();
214
		}
215
216
		$post_ids = array();
217
218
		foreach ( $this->search_result['results']['hits'] as $result ) {
219
			$post_ids[] = (int) $result['fields']['post_id'];
220
		}
221
222
		// Query all posts now
223
		$args = array(
224
			'post__in'  => $post_ids,
225
			'perm'      => 'readable',
226
			'post_type' => 'any',
227
		);
228
229
		$posts_query = new WP_Query( $args );
230
231
		// WP Core doesn't call the set_found_posts and its filters when filtering posts_pre_query like we do, so need to
232
		// do these manually
233
		$query->found_posts   = $this->found_posts;
234
		$query->max_num_pages = ceil( $this->found_posts / $query->get( 'posts_per_page' ) );
235
236
		return $posts_query->posts;
237
	}
238
239
	/**
240
	 * Build up the search, then run it against the Jetpack servers
241
	 *
242
	 * @param WP_Query $query The original WP_Query to use for the parameters of our search
243
	 */
244
	public function do_search( WP_Query $query ) {
245
		if ( ! $query->is_main_query() || ! $query->is_search() ) {
246
			return;
247
		}
248
249
		$page = ( $query->get( 'paged' ) ) ? absint( $query->get( 'paged' ) ) : 1;
250
251
		$posts_per_page = $query->get( 'posts_per_page' );
252
253
		// ES API does not allow more than 15 results at a time
254
		if ( $posts_per_page > 15 ) {
255
			$posts_per_page = 15;
256
		}
257
258
		// Start building the WP-style search query args
259
		// They'll be translated to ES format args later
260
		$es_wp_query_args = array(
261
			'query'          => $query->get( 's' ),
262
			'posts_per_page' => $posts_per_page,
263
			'paged'          => $page,
264
			'orderby'        => $query->get( 'orderby' ),
265
			'order'          => $query->get( 'order' ),
266
		);
267
268
		if ( ! empty( $this->aggregations ) ) {
269
			$es_wp_query_args['aggregations'] = $this->aggregations;
270
		}
271
272
		// Did we query for authors?
273
		if ( $query->get( 'author_name' ) ) {
274
			$es_wp_query_args['author_name'] = $query->get( 'author_name' );
275
		}
276
277
		$es_wp_query_args['post_type'] = $this->get_es_wp_query_post_type_for_query( $query );
278
279
		$es_wp_query_args['terms']     = $this->get_es_wp_query_terms_for_query( $query );
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned correctly; expected 1 space but found 5 spaces

This check looks for improperly formatted assignments.

Every assignment must have exactly one space before and one space after the equals operator.

To illustrate:

$a = "a";
$ab = "ab";
$abc = "abc";

will have no issues, while

$a   = "a";
$ab  = "ab";
$abc = "abc";

will report issues in lines 1 and 2.

Loading history...
280
281
282
		/**
283
		 * Modify the search query parameters, such as controlling the post_type.
284
		 *
285
		 * These arguments are in the format of WP_Query arguments
286
		 *
287
		 * @module search
288
		 *
289
		 * @since 5.0.0
290
		 *
291
		 * @param array $es_wp_query_args The current query args, in WP_Query format
292
		 * @param WP_Query $query The original query object
293
		 */
294
		$es_wp_query_args = apply_filters( 'jetpack_search_es_wp_query_args', $es_wp_query_args, $query );
295
296
		// If page * posts_per_page is greater than our max offset, send a 404. This is necessary because the offset is
297
		// capped at $this->max_offset, so a high page would always return the last page of results otherwise
298
		if ( ( $es_wp_query_args['paged'] * $es_wp_query_args['posts_per_page'] ) > $this->max_offset ) {
299
			$query->set_404();
300
301
			return;
302
		}
303
304
		// If there were no post types returned, then 404 to avoid querying against non-public post types, which could
305
		// happen if we don't add the post type restriction to the ES query
306
		if ( empty( $es_wp_query_args['post_type'] ) ) {
307
			$query->set_404();
308
309
			return;
310
		}
311
312
		// Convert the WP-style args into ES args
313
		$es_query_args = $this->convert_wp_es_to_es_args( $es_wp_query_args );
314
315
		//Only trust ES to give us IDs, not the content since it is a mirror
316
		$es_query_args['fields'] = array(
317
			'post_id',
318
		);
319
320
		/**
321
		 * Modify the underlying ES query that is passed to the search endpoint. The returned args must represent a valid ES query
322
		 *
323
		 * This filter is harder to use if you're unfamiliar with ES, but allows complete control over the query
324
		 *
325
		 * @module search
326
		 *
327
		 * @since 5.0.0
328
		 *
329
		 * @param array $es_query_args The raw ES query args
330
		 * @param WP_Query $query The original query object
331
		 */
332
		$es_query_args = apply_filters( 'jetpack_search_es_query_args', $es_query_args, $query );
333
334
		// Do the actual search query!
335
		$this->search_result = $this->search( $es_query_args );
336
337
		if ( is_wp_error( $this->search_result ) || ! is_array( $this->search_result ) || empty( $this->search_result['results'] ) || empty( $this->search_result['results']['hits'] ) ) {
338
			$this->found_posts = 0;
339
340
			return;
341
		}
342
343
		// Total number of results for paging purposes. Capped at $this->>max_offset + $posts_per_page, as deep paging
344
		// gets quite expensive
345
		$this->found_posts = min( $this->search_result['results']['total'], $this->max_offset + $posts_per_page );
346
347
		return;
348
	}
349
350
	/**
351
	 * Given a WP_Query, convert its WP_Tax_Query (if present) into the WP-style ES term arguments for the search
352
	 *
353
	 * @module search
354
	 *
355
	 * @param WP_Query $query The original WP_Query object for which to parse the taxonomy query
356
	 *
357
	 * @return array The new WP-style ES arguments (that will be converted into 'real' ES arguments)
358
	 */
359
	public function get_es_wp_query_terms_for_query( WP_Query $query ) {
360
		$args = array();
361
362
		$the_tax_query = $query->tax_query;
363
364
		if ( ! $the_tax_query ) {
365
			return $args;
366
		}
367
368
369
		if ( ! $the_tax_query instanceof WP_Tax_Query || empty( $the_tax_query->queried_terms ) || ! is_array( $the_tax_query->queried_terms ) ) {
0 ignored issues
show
Bug introduced by
The class WP_Tax_Query does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
370
			return $args;
371
		}
372
373
		$args = array();
374
375
		foreach ( $the_tax_query->queries as $tax_query ) {
376
			// Right now we only support slugs...see note above
377
			if ( 'slug' !== $tax_query['field'] ) {
378
				continue;
379
			}
380
381
			$taxonomy = $tax_query['taxonomy'];
382
383 View Code Duplication
			if ( ! isset( $args[ $taxonomy ] ) || ! is_array( $args[ $taxonomy ] ) ) {
384
				$args[ $taxonomy ] = array();
385
			}
386
387
			$args[ $taxonomy ] = array_merge( $args[ $taxonomy ], $tax_query['terms'] );
388
		}
389
390
		return $args;
391
	}
392
393
	/**
394
	 * Parse out the post type from a WP_Query
395
	 *
396
	 * Only allows post types that are not marked as 'exclude_from_search'
397
	 *
398
	 * @module search
399
	 *
400
	 * @param WP_Query $query Original WP_Query object
401
	 *
402
	 * @return array Array of searchable post types corresponding to the original query
403
	 */
404
	public function get_es_wp_query_post_type_for_query( WP_Query $query ) {
405
		$post_types = $query->get( 'post_type' );
406
407
		// If we're searching 'any', we want to only pass searchable post types to ES
408
		if ( 'any' === $post_types ) {
409
			$post_types = array_values( get_post_types( array(
410
				'exclude_from_search' => false,
411
			) ) );
412
		}
413
414
		if ( ! is_array( $post_types ) ) {
415
			$post_types = array( $post_types );
416
		}
417
418
		$post_types = array_unique( $post_types );
419
420
		$sanitized_post_types = array();
421
422
		// Make sure the post types are queryable
423
		foreach ( $post_types as $post_type ) {
424
			if ( ! $post_type ) {
425
				continue;
426
			}
427
428
			$post_type_object = get_post_type_object( $post_type );
429
430
			if ( ! $post_type_object || $post_type_object->exclude_from_search ) {
431
				continue;
432
			}
433
434
			$sanitized_post_types[] = $post_type;
435
		}
436
437
		return $sanitized_post_types;
438
	}
439
440
	/**
441
	 * Initialze widgets for the Search module
442
	 *
443
	 * @module search
444
	 */
445
	public function action__widgets_init() {
446
		require_once( dirname( __FILE__ ) . '/class.jetpack-search-widget-filters.php' );
447
448
		register_widget( 'Jetpack_Search_Widget_Filters' );
449
	}
450
451
	/**
452
	 * Get the Elasticsearch result
453
	 *
454
	 * @module search
455
	 *
456
	 * @param bool $raw If true, does not check for WP_Error or return the 'results' array - the JSON decoded HTTP response
457
	 *
458
	 * @return array|bool The search results, or false if there was a failure
459
	 */
460
	public function get_search_result( $raw = false ) {
461
		if ( $raw ) {
462
			return $this->search_result;
463
		}
464
465
		return ( ! empty( $this->search_result ) && ! is_wp_error( $this->search_result ) && is_array( $this->search_result ) && ! empty( $this->search_result['results'] ) ) ? $this->search_result['results'] : false;
466
	}
467
468
	/**
469
	 * Add the date portion of a WP_Query onto the query args
470
	 *
471
	 * @param array    $es_wp_query_args
472
	 * @param WP_Query $query The original WP_Query
473
	 *
474
	 * @return array The es wp query args, with date filters added (as needed)
475
	 */
476
	public function filter__add_date_filter_to_query( array $es_wp_query_args, WP_Query $query ) {
477
		if ( $query->get( 'year' ) ) {
478
			if ( $query->get( 'monthnum' ) ) {
479
				// Padding
480
				$date_monthnum = sprintf( '%02d', $query->get( 'monthnum' ) );
481
482
				if ( $query->get( 'day' ) ) {
483
					// Padding
484
					$date_day = sprintf( '%02d', $query->get( 'day' ) );
485
486
					$date_start = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $date_day . ' 00:00:00';
487
					$date_end   = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $date_day . ' 23:59:59';
488
				} else {
489
					$days_in_month = date( 't', mktime( 0, 0, 0, $query->get( 'monthnum' ), 14, $query->get( 'year' ) ) ); // 14 = middle of the month so no chance of DST issues
490
491
					$date_start = $query->get( 'year' ) . '-' . $date_monthnum . '-01 00:00:00';
492
					$date_end   = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $days_in_month . ' 23:59:59';
493
				}
494
			} else {
495
				$date_start = $query->get( 'year' ) . '-01-01 00:00:00';
496
				$date_end   = $query->get( 'year' ) . '-12-31 23:59:59';
497
			}
498
499
			$es_wp_query_args['date_range'] = array(
500
				'field' => 'date',
501
				'gte'   => $date_start,
502
				'lte'   => $date_end,
503
			);
504
		}
505
506
		return $es_wp_query_args;
507
	}
508
509
	/**
510
	 * Converts WP_Query style args to ES args
511
	 *
512
	 * @module search
513
	 *
514
	 * @param array $args Array of WP_Query style arguments
515
	 *
516
	 * @return array Array of ES style query arguments
517
	 */
518
	function convert_wp_es_to_es_args( array $args ) {
519
		jetpack_require_lib( 'jetpack-wpes-query-builder' );
520
521
		$builder = new Jetpack_WPES_Query_Builder();
522
523
		$defaults = array(
524
			'blog_id'        => get_current_blog_id(),
525
526
			'query'          => null,    // Search phrase
527
			'query_fields'   => array( 'title', 'content', 'author', 'tag', 'category' ),
528
529
			'post_type'      => null,  // string or an array
530
			'terms'          => array(), // ex: array( 'taxonomy-1' => array( 'slug' ), 'taxonomy-2' => array( 'slug-a', 'slug-b' ) )
531
532
			'author'         => null,    // id or an array of ids
533
			'author_name'    => array(), // string or an array
534
535
			'date_range'     => null,    // array( 'field' => 'date', 'gt' => 'YYYY-MM-dd', 'lte' => 'YYYY-MM-dd' ); date formats: 'YYYY-MM-dd' or 'YYYY-MM-dd HH:MM:SS'
536
537
			'orderby'        => null,    // Defaults to 'relevance' if query is set, otherwise 'date'. Pass an array for multiple orders.
538
			'order'          => 'DESC',
539
540
			'posts_per_page' => 10,
541
542
			'offset'         => null,
543
			'paged'          => null,
544
545
			/**
546
			 * Aggregations. Examples:
547
			 * array(
548
			 *     'Tag'       => array( 'type' => 'taxonomy', 'taxonomy' => 'post_tag', 'count' => 10 ) ),
549
			 *     'Post Type' => array( 'type' => 'post_type', 'count' => 10 ) ),
550
			 * );
551
			 */
552
			'aggregations'         => null,
553
		);
554
555
		$args = wp_parse_args( $args, $defaults );
556
557
		$es_query_args = array(
558
			'blog_id' => absint( $args['blog_id'] ),
559
			'size'    => absint( $args['posts_per_page'] ),
560
		);
561
562
		// ES "from" arg (offset)
563
		if ( $args['offset'] ) {
564
			$es_query_args['from'] = absint( $args['offset'] );
565
		} elseif ( $args['paged'] ) {
566
			$es_query_args['from'] = max( 0, ( absint( $args['paged'] ) - 1 ) * $es_query_args['size'] );
567
		}
568
569
		// Limit the offset to $this->max_offset posts, as deep pages get exponentially slower
570
		// See https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html
571
		$es_query_args['from'] = min( $es_query_args['from'], $this->max_offset );
572
573
		if ( ! is_array( $args['author_name'] ) ) {
574
			$args['author_name'] = array( $args['author_name'] );
575
		}
576
577
		// ES stores usernames, not IDs, so transform
578
		if ( ! empty( $args['author'] ) ) {
579
			if ( ! is_array( $args['author'] ) ) {
580
				$args['author'] = array( $args['author'] );
581
			}
582
583
			foreach ( $args['author'] as $author ) {
584
				$user = get_user_by( 'id', $author );
585
586
				if ( $user && ! empty( $user->user_login ) ) {
587
					$args['author_name'][] = $user->user_login;
588
				}
589
			}
590
		}
591
592
		//////////////////////////////////////////////////
593
		// Build the filters from the query elements.
594
		// Filters rock because they are cached from one query to the next
595
		// but they are cached as individual filters, rather than all combined together.
596
		// May get performance boost by also caching the top level boolean filter too.
597
		$filters = array();
598
599
		if ( $args['post_type'] ) {
600
			if ( ! is_array( $args['post_type'] ) ) {
601
				$args['post_type'] = array( $args['post_type'] );
602
			}
603
604
			$filters[] = array(
605
				'terms' => array(
606
					'post_type' => $args['post_type'],
607
				),
608
			);
609
		}
610
611
		if ( $args['author_name'] ) {
612
			$filters[] = array(
613
				'terms' => array(
614
					'author_login' => $args['author_name'],
615
				),
616
			);
617
		}
618
619
		if ( ! empty( $args['date_range'] ) && isset( $args['date_range']['field'] ) ) {
620
			$field = $args['date_range']['field'];
621
622
			unset( $args['date_range']['field'] );
623
624
			$filters[] = array(
625
				'range' => array(
626
					$field => $args['date_range'],
627
				),
628
			);
629
		}
630
631
		if ( is_array( $args['terms'] ) ) {
632
			foreach ( $args['terms'] as $tax => $terms ) {
633
				$terms = (array) $terms;
634
635
				if ( count( $terms ) && mb_strlen( $tax ) ) {
636 View Code Duplication
					switch ( $tax ) {
637
						case 'post_tag':
638
							$tax_fld = 'tag.slug';
639
640
							break;
641
642
						case 'category':
643
							$tax_fld = 'category.slug';
644
645
							break;
646
647
						default:
648
							$tax_fld = 'taxonomy.' . $tax . '.slug';
649
650
							break;
651
					}
652
653
					foreach ( $terms as $term ) {
654
						$filters[] = array(
655
							'term' => array(
656
								$tax_fld => $term,
657
							),
658
						);
659
					}
660
				}
661
			}
662
		}
663
664
		if ( $args['query'] ) {
665
			$query = array(
666
				'multi_match' => array(
667
					'query'    => $args['query'],
668
					'fields'   => $args['query_fields'],
669
					'operator' => 'and',
670
					'type'     => 'cross_fields',
671
				),
672
			);
673
674
			$builder->add_query( $query );
675
676
			Jetpack_Search::score_query_by_recency( $builder );
677
678
			if ( ! $args['orderby'] ) {
679
				$args['orderby'] = array( 'relevance' );
680
			}
681
		} else {
682
			if ( ! $args['orderby'] ) {
683
				$args['orderby'] = array( 'date' );
684
			}
685
		}
686
687
		// Validate the "order" field
688
		switch ( strtolower( $args['order'] ) ) {
689
			case 'asc':
690
				$args['order'] = 'asc';
691
				break;
692
693
			case 'desc':
694
			default:
695
				$args['order'] = 'desc';
696
				break;
697
		}
698
699
		$es_query_args['sort'] = array();
700
701
		foreach ( (array) $args['orderby'] as $orderby ) {
702
			// Translate orderby from WP field to ES field
703
			switch ( $orderby ) {
704
				case 'relevance' :
705
					//never order by score ascending
706
					$es_query_args['sort'][] = array(
707
						'_score' => array(
708
							'order' => 'desc',
709
						),
710
					);
711
712
					break;
713
714 View Code Duplication
				case 'date' :
715
					$es_query_args['sort'][] = array(
716
						'date' => array(
717
							'order' => $args['order'],
718
						),
719
					);
720
721
					break;
722
723 View Code Duplication
				case 'ID' :
724
					$es_query_args['sort'][] = array(
725
						'id' => array(
726
							'order' => $args['order'],
727
						),
728
					);
729
730
					break;
731
732
				case 'author' :
733
					$es_query_args['sort'][] = array(
734
						'author.raw' => array(
735
							'order' => $args['order'],
736
						),
737
					);
738
739
					break;
740
			} // End switch().
741
		} // End foreach().
742
743
		if ( empty( $es_query_args['sort'] ) ) {
744
			unset( $es_query_args['sort'] );
745
		}
746
747
		if ( ! empty( $filters ) && is_array( $filters ) ) {
748
			foreach ( $filters as $filter ) {
749
				$builder->add_filter( $filter );
750
			}
751
752
			$es_query_args['filter'] = $builder->build_filter();
753
		}
754
755
		$es_query_args['query'] = $builder->build_query();
756
757
		// Aggregations
758
		if ( ! empty( $args['aggregations'] ) ) {
759
			$this->add_aggregations_to_es_query_builder( $args['aggregations'], $builder );
0 ignored issues
show
Documentation introduced by
$args['aggregations'] is of type string, but the function expects a array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
760
761
			$es_query_args['aggregations'] = $builder->build_aggregation();
762
		}
763
764
		return $es_query_args;
765
	}
766
767
	/**
768
	 * Given an array of aggregations, parse and add them onto the Jetpack_WPES_Query_Builder object for use in ES
769
	 *
770
	 * @module search
771
	 *
772
	 * @param array $aggregations Array of Aggregations (filters) to add to the Jetpack_WPES_Query_Builder
773
	 *
774
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
775
	 */
776
	public function add_aggregations_to_es_query_builder( array $aggregations, Jetpack_WPES_Query_Builder $builder ) {
777
		foreach ( $aggregations as $label => $aggregation ) {
778
			switch ( $aggregation['type'] ) {
779
				case 'taxonomy':
780
					$this->add_taxonomy_aggregation_to_es_query_builder( $aggregation, $label, $builder );
781
782
					break;
783
784
				case 'post_type':
785
					$this->add_post_type_aggregation_to_es_query_builder( $aggregation, $label, $builder );
786
787
					break;
788
789
				case 'date_histogram':
790
					$this->add_date_histogram_aggregation_to_es_query_builder( $aggregation, $label, $builder );
791
792
					break;
793
			}
794
		}
795
	}
796
797
	/**
798
	 * Given an individual taxonomy aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
799
	 *
800
	 * @module search
801
	 *
802
	 * @param array $aggregation The aggregation to add to the query builder
803
	 * @param string $label The 'label' (unique id) for this aggregation
804
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
805
	 */
806
	public function add_taxonomy_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
807
		$field = null;
0 ignored issues
show
Unused Code introduced by
$field 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...
808
809
		switch ( $aggregation['taxonomy'] ) {
810
			case 'post_tag':
811
				$field = 'tag';
812
				break;
813
814
			case 'category':
815
				$field = 'category';
816
				break;
817
818
			default:
819
				$field = 'taxonomy.' . $aggregation['taxonomy'];
820
				break;
821
		}
822
823
		$builder->add_aggs( $label, array(
824
			'terms' => array(
825
				'field' => $field . '.slug',
826
				'size' => min( (int) $aggregation['count'], $this->max_aggregations_count ),
827
			),
828
		));
829
	}
830
831
	/**
832
	 * Given an individual post_type aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
833
	 *
834
	 * @module search
835
	 *
836
	 * @param array $aggregation The aggregation to add to the query builder
837
	 * @param string $label The 'label' (unique id) for this aggregation
838
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
839
	 */
840
	public function add_post_type_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
841
		$builder->add_aggs( $label, array(
842
			'terms' => array(
843
				'field' => 'post_type',
844
				'size' => min( (int) $aggregation['count'], $this->max_aggregations_count ),
845
			),
846
		));
847
	}
848
849
	/**
850
	 * Given an individual date_histogram aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
851
	 *
852
	 * @module search
853
	 *
854
	 * @param array $aggregation The aggregation to add to the query builder
855
	 * @param string $label The 'label' (unique id) for this aggregation
856
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
857
	 */
858
	public function add_date_histogram_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
859
		$builder->add_aggs( $label, array(
860
			'date_histogram' => array(
861
				'interval' => $aggregation['interval'],
862
				'field'    => ( ! empty( $aggregation['field'] ) && 'post_date_gmt' == $aggregation['field'] ) ? 'date_gmt' : 'date',
863
			),
864
		));
865
	}
866
867
	/**
868
	 * And an existing filter object with a list of additional filters.
869
	 *
870
	 * Attempts to optimize the filters somewhat.
871
	 *
872
	 * @module search
873
	 *
874
	 * @param array $curr_filter The existing filters to build upon
875
	 * @param array $filters The new filters to add
876
	 *
877
	 * @return array The resulting merged filters
878
	 */
879
	public static function and_es_filters( array $curr_filter, array $filters ) {
880
		if ( ! is_array( $curr_filter ) || isset( $curr_filter['match_all'] ) ) {
881
			if ( 1 === count( $filters ) ) {
882
				return $filters[0];
883
			}
884
885
			return array(
886
				'and' => $filters,
887
			);
888
		}
889
890
		return array(
891
			'and' => array_merge( array( $curr_filter ), $filters ),
892
		);
893
	}
894
895
	/**
896
	 * Add a recency score to a given Jetpack_WPES_Query_Builder object, for emphasizing newer posts in results
897
	 *
898
	 * Internally uses a gauss decay function
899
	 *
900
	 * @module search
901
	 *
902
	 * @param Jetpack_WPES_Query_Builder $builder The Jetpack_WPES_Query_Builder to add the recency score to
903
	 *
904
	 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#function-decay
905
	 */
906
	public static function score_query_by_recency( Jetpack_WPES_Query_Builder &$builder ) {
907
		//Newer content gets weighted slightly higher
908
		$date_scale  = '360d';
909
		$date_decay  = 0.9;
910
		$date_origin = date( 'Y-m-d' );
911
912
		$builder->add_decay( 'gauss', array(
913
			'date_gmt' => array(
914
				'origin' => $date_origin,
915
				'scale'  => $date_scale,
916
				'decay'  => $date_decay,
917
			),
918
		));
919
	}
920
921
	/**
922
	 * Set the available filters for the search
923
	 *
924
	 * These get rendered via the Jetpack_Search_Widget_Filters() widget
925
	 *
926
	 * Behind the scenes, these are implemented using Elasticsearch Aggregations.
927
	 *
928
	 * If you do not require counts of how many documents match each filter, please consider using regular WP Query
929
	 * arguments instead, such as via the jetpack_search_es_wp_query_args filter
930
	 *
931
	 * @module search
932
	 *
933
	 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
934
	 *
935
	 * @param array $aggregations Array of filters (aggregations) to apply to the search
936
	 */
937
	public function set_filters( array $aggregations ) {
938
		$this->aggregations = $aggregations;
939
	}
940
941
	/**
942
	 * Set the search's facets (deprecated)
943
	 *
944
	 * @module search
945
	 *
946
	 * @deprecated 5.0 Please use Jetpack_Search::set_filters() instead
947
	 *
948
	 * @see Jetpack_Search::set_filters()
949
	 *
950
	 * @param array $facets Array of facets to apply to the search
951
	 */
952
	public function set_facets( array $facets ) {
953
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::set_filters()' );
954
955
		$this->set_filters( $facets );
956
	}
957
958
	/**
959
	 * Get the raw Aggregation results from the ES response
960
	 *
961
	 * @module search
962
	 *
963
	 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
964
	 *
965
	 * @return array Array of Aggregations performed on the search
966
	 */
967
	public function get_search_aggregations_results() {
968
		$aggregations = array();
969
970
		$search_result = $this->get_search_result();
971
972
		if ( ! empty( $search_result ) && ! empty( $search_result['aggregations'] ) ) {
973
			$aggregations = $search_result['aggregations'];
974
		}
975
976
		return $aggregations;
977
	}
978
979
	/**
980
	 * Get the raw Facet results from the ES response
981
	 *
982
	 * @module search
983
	 *
984
	 * @deprecated 5.0 Please use Jetpack_Search::get_search_aggregations_results() instead
985
	 *
986
	 * @see Jetpack_Search::get_search_aggregations_results()
987
	 *
988
	 * @return array Array of Facets performed on the search
989
	 */
990
	public function get_search_facets() {
991
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_search_aggregations_results()' );
992
993
		return $this->get_search_aggregations_results();
994
	}
995
996
	/**
997
	 * Get the results of the Filters performed, including the number of matching documents
998
	 *
999
	 * Returns an array of Filters (keyed by $label, as passed to Jetpack_Search::set_filters()), containing the Filter and all resulting
1000
	 * matching buckets, the url for applying/removing each bucket, etc.
1001
	 *
1002
	 * NOTE - if this is called before the search is performed, an empty array will be returned. Use the $aggregations class
1003
	 * member if you need to access the raw filters set in Jetpack_Search::set_filters()
1004
	 *
1005
	 * @module search
1006
	 *
1007
	 * @param WP_Query $query The optional original WP_Query to use for determining which filters are active. Defaults to the main query
0 ignored issues
show
Documentation introduced by
Should the type for parameter $query not be null|WP_Query?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1008
	 *
1009
	 * @return array Array of Filters applied and info about them
1010
	 */
1011
	public function get_filters( WP_Query $query = null ) {
1012
		if ( ! $query instanceof WP_Query ) {
0 ignored issues
show
Bug introduced by
The class WP_Query does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
1013
			global $wp_query;
1014
1015
			$query = $wp_query;
1016
		}
1017
1018
		$aggregation_data = $this->aggregations;
1019
1020
		if ( empty( $aggregation_data ) ) {
1021
			return $aggregation_data;
1022
		}
1023
1024
		$aggregation_results = $this->get_search_aggregations_results();
1025
1026
		if ( ! $aggregation_results ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $aggregation_results of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1027
			return $aggregation_data;
1028
		}
1029
1030
		// NOTE - Looping over the _results_, not the original configured aggregations, so we get the 'real' data from ES
1031
		foreach ( $aggregation_results as $label => $aggregation ) {
1032
			if ( empty( $aggregation ) ) {
1033
				continue;
1034
			}
1035
1036
			$type = $this->aggregations[ $label ]['type'];
1037
1038
			$aggregation_data[ $label ]['buckets'] = array();
1039
1040
			$existing_term_slugs = array();
1041
1042
			$tax_query_var = null;
1043
1044
			// Figure out which terms are active in the query, for this taxonomy
1045
			if ( 'taxonomy' === $this->aggregations[ $label ]['type'] ) {
1046
				$tax_query_var = $this->get_taxonomy_query_var(  $this->aggregations[ $label ]['taxonomy'] );
1047
1048
				if ( ! empty( $query->tax_query ) && ! empty( $query->tax_query->queries ) && is_array( $query->tax_query->queries ) ) {
1049
					foreach( $query->tax_query->queries as $tax_query ) {
1050
						if ( $this->aggregations[ $label ]['taxonomy'] === $tax_query['taxonomy'] &&
1051
						     'slug' === $tax_query['field'] &&
1052
						     is_array( $tax_query['terms'] ) ) {
1053
							$existing_term_slugs = array_merge( $existing_term_slugs, $tax_query['terms'] );
1054
						}
1055
					}
1056
				}
1057
			}
1058
1059
			// Now take the resulting found aggregation items and generate the additional info about them, such as
1060
			// activation/deactivation url, name, count, etc
1061
			$buckets = array();
1062
1063
			if ( ! empty( $aggregation['buckets'] ) ) {
1064
				$buckets = (array) $aggregation['buckets'];
1065
			}
1066
1067
			// Some aggregation types like date_histogram don't support the max results parameter
1068
			if ( is_int( $this->aggregations[ $label ]['count'] ) && count( $buckets ) > $this->aggregations[ $label ]['count'] ) {
1069
				$buckets = array_slice( $buckets, 0, $this->aggregations[ $label ]['count'] );
1070
			}
1071
1072
			foreach ( $buckets as $item ) {
1073
				$query_vars = array();
1074
				$active     = false;
1075
				$remove_url = null;
1076
				$name       = '';
1077
1078
				// What type was the original aggregation?
1079
				switch ( $type ) {
1080
					case 'taxonomy':
1081
						$taxonomy = $this->aggregations[ $label ]['taxonomy'];
1082
1083
						$term = get_term_by( 'slug', $item['key'], $taxonomy );
1084
1085
						if ( ! $term || ! $tax_query_var ) {
1086
							continue 2; // switch() is considered a looping structure
1087
						}
1088
1089
						$query_vars = array(
1090
							$tax_query_var => implode( '+', array_merge( $existing_term_slugs, array( $term->slug ) ) ),
1091
						);
1092
1093
						$name = $term->name;
1094
1095
						// Let's determine if this term is active or not
1096
1097
						if ( in_array( $item['key'], $existing_term_slugs, true ) ) {
1098
							$active = true;
1099
1100
							$slug_count = count( $existing_term_slugs );
1101
1102 View Code Duplication
							if ( $slug_count > 1 ) {
1103
								$remove_url = add_query_arg( $tax_query_var, urlencode( implode( '+', array_diff( $existing_term_slugs, array( $item['key'] ) ) ) ) );
1104
							} else {
1105
								$remove_url = remove_query_arg( $tax_query_var );
1106
							}
1107
						}
1108
1109
						break;
1110
1111
					case 'post_type':
1112
						$post_type = get_post_type_object( $item['key'] );
1113
1114
						if ( ! $post_type || $post_type->exclude_from_search ) {
1115
							continue 2;  // switch() is considered a looping structure
1116
						}
1117
1118
						$query_vars = array(
1119
							'post_type' => $item['key'],
1120
						);
1121
1122
						$name = $post_type->labels->singular_name;
1123
1124
						// Is this post type active on this search?
1125
						$post_types = $query->get( 'post_type' );
1126
1127
						if ( ! is_array( $post_types ) ) {
1128
							$post_types = array( $post_types );
1129
						}
1130
1131
						if ( in_array( $item['key'], $post_types ) ) {
1132
							$active = true;
1133
1134
							$post_type_count = count( $post_types );
1135
1136
							// For the right 'remove filter' url, we need to remove the post type from the array, or remove the param entirely if it's the only one
1137 View Code Duplication
							if ( $post_type_count > 1 ) {
1138
								$remove_url = add_query_arg( 'post_type', urlencode_deep( array_diff( $post_types, array( $item['key'] ) ) ) );
1139
							} else {
1140
								$remove_url = remove_query_arg( 'post_type' );
1141
							}
1142
						}
1143
1144
						break;
1145
1146
					case 'date_histogram':
1147
						$timestamp = $item['key'] / 1000;
1148
1149
						$current_year  = $query->get( 'year' );
1150
						$current_month = $query->get( 'monthnum' );
1151
						$current_day   = $query->get( 'day' );
1152
1153
						switch ( $this->aggregations[ $label ]['interval'] ) {
1154
							case 'year':
1155
								$year = (int) date( 'Y', $timestamp );
1156
1157
								$query_vars = array(
1158
									'year'     => $year,
1159
									'monthnum' => false,
1160
									'day'      => false,
1161
								);
1162
1163
								$name = $year;
1164
1165
								// Is this year currently selected?
1166
								if ( ! empty( $current_year ) && (int) $current_year === $year ) {
1167
									$active = true;
1168
1169
									$remove_url = remove_query_arg( array( 'year', 'monthnum', 'day' ) );
1170
								}
1171
1172
								break;
1173
1174
							case 'month':
1175
								$year  = (int) date( 'Y', $timestamp );
1176
								$month = (int) date( 'n', $timestamp );
1177
1178
								$query_vars = array(
1179
									'year'     => $year,
1180
									'monthnum' => $month,
1181
									'day'      => false,
1182
								);
1183
1184
								$name = date( 'F Y', $timestamp );
1185
1186
								// Is this month currently selected?
1187
								if ( ! empty( $current_year ) && (int) $current_year === $year &&
1188
								     ! empty( $current_month ) && (int) $current_month === $month ) {
1189
									$active = true;
1190
1191
									$remove_url = remove_query_arg( array( 'monthnum', 'day' ) );
1192
								}
1193
1194
								break;
1195
1196
							case 'day':
1197
								$year  = (int) date( 'Y', $timestamp );
1198
								$month = (int) date( 'n', $timestamp );
1199
								$day   = (int) date( 'j', $timestamp );
1200
1201
								$query_vars = array(
1202
									'year'     => $year,
1203
									'monthnum' => $month,
1204
									'day'      => $day,
1205
								);
1206
1207
								$name = date( 'F jS, Y', $timestamp );
1208
1209
								// Is this day currently selected?
1210
								if ( ! empty( $current_year ) && (int) $current_year === $year &&
1211
								     ! empty( $current_month ) && (int) $current_month === $month &&
1212
								     ! empty( $current_day ) && (int) $current_day === $day ) {
1213
									$active = true;
1214
1215
									$remove_url = remove_query_arg( array( 'day' ) );
1216
								}
1217
1218
								break;
1219
1220
							default:
1221
								continue 3; // switch() is considered a looping structure
1222
						} // End switch().
1223
1224
						break;
1225
1226
					default:
1227
						//continue 2; // switch() is considered a looping structure
1228
				} // End switch().
1229
1230
				// Need to urlencode param values since add_query_arg doesn't
1231
				$url_params = urlencode_deep( $query_vars );
1232
1233
				$aggregation_data[ $label ]['buckets'][] = array(
1234
					'url'        => add_query_arg( $url_params ),
1235
					'query_vars' => $query_vars,
1236
					'name'       => $name,
1237
					'count'      => $item['doc_count'],
1238
					'active'     => $active,
1239
					'remove_url' => $remove_url,
1240
					'type'       => $type,
1241
					'type_label' => $label,
1242
				);
1243
			} // End foreach().
1244
		} // End foreach().
1245
1246
		return $aggregation_data;
1247
	}
1248
1249
	/**
1250
	 * Get the results of the Facets performed
1251
	 *
1252
	 * @module search
1253
	 *
1254
	 * @deprecated 5.0 Please use Jetpack_Search::get_filters() instead
1255
	 *
1256
	 * @see Jetpack_Search::get_filters()
1257
	 *
1258
	 * @return array $facets Array of Facets applied and info about them
1259
	 */
1260
	public function get_search_facet_data() {
1261
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_filters()' );
1262
1263
		return $this->get_filters();
1264
	}
1265
1266
	/**
1267
	 * Get the Filters that are currently applied to this search
1268
	 *
1269
	 * @module search
1270
	 *
1271
	 * @return array Array if Filters that were applied
1272
	 */
1273
	public function get_active_filter_buckets() {
1274
		$active_buckets = array();
1275
1276
		$filters = $this->get_filters();
1277
1278
		if ( ! is_array( $filters ) ) {
1279
			return $active_buckets;
1280
		}
1281
1282
		foreach( $filters as $filter ) {
1283
			if ( isset( $filters['buckets'] ) && is_array( $filter['buckets'] ) ) {
1284
				foreach( $filter['buckets'] as $item ) {
1285
					if ( isset( $item['active'] ) && $item['active'] ) {
1286
						$active_buckets[] = $item;
1287
					}
1288
				}
1289
			}
1290
		}
1291
1292
		return $active_buckets;
1293
	}
1294
1295
	/**
1296
	 * Get the Filters that are currently applied to this search
1297
	 *
1298
	 * @module search
1299
	 *
1300
	 * @return array Array if Filters that were applied
1301
	 */
1302
	public function get_current_filters() {
1303
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_active_filter_buckets()' );
1304
1305
		return $this->get_active_filter_buckets();
1306
	}
1307
1308
	/**
1309
	 * Calculate the right query var to use for a given taxonomy
1310
	 *
1311
	 * Allows custom code to modify the GET var that is used to represent a given taxonomy, via the jetpack_search_taxonomy_query_var filter
1312
	 *
1313
	 * @module search
1314
	 *
1315
	 * @param string $taxonomy_name The name of the taxonomy for which to get the query var
1316
	 *
1317
	 * @return bool|string The query var to use for this taxonomy, or false if none found
1318
	 */
1319
	public function get_taxonomy_query_var( $taxonomy_name ) {
1320
		$taxonomy = get_taxonomy( $taxonomy_name );
1321
1322
		if ( ! $taxonomy || is_wp_error( $taxonomy ) ) {
1323
			return false;
1324
		}
1325
1326
		/**
1327
		 * Modify the query var to use for a given taxonomy
1328
		 *
1329
		 * @module search
1330
		 *
1331
		 * @since 5.0.0
1332
		 *
1333
		 * @param string $query_var The current query_var for the taxonomy
1334
		 * @param string $taxonomy_name The taxonomy name
1335
		 */
1336
		return apply_filters( 'jetpack_search_taxonomy_query_var', $taxonomy->query_var, $taxonomy_name );
1337
	}
1338
}
1339