Completed
Push — add/sync-import-event ( 8770af...5ff453 )
by
unknown
130:35 queued 120:40
created

Jetpack_Search::init_hooks()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
nc 2
nop 0
dl 0
loc 9
rs 9.6666
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
		$service_url = 'https://public-api.wordpress.com/rest/v1/sites/' . $this->jetpack_blog_id . '/search';
106
107
		$start_time = microtime( true );
108
109
		$request = wp_remote_post( $service_url, array(
110
			'headers' => array(
111
				'Content-Type' => 'application/json',
112
			),
113
			'timeout'    => 10,
114
			'user-agent' => 'jetpack_search',
115
			'body'       => json_encode( $es_args ),
116
		) );
117
118
		$end_time = microtime( true );
119
120
		if ( is_wp_error( $request ) ) {
121
			return $request;
122
		}
123
		$response_code = wp_remote_retrieve_response_code( $request );
124
125
		if ( ! $response_code || $response_code < 200 || $response_code >= 300 ) {
126
			return new WP_Error( 'invalid_search_api_response', 'Invalid response from API - ' . $response_code );
127
		}
128
129
		$response = json_decode( wp_remote_retrieve_body( $request ), true );
130
131
		$took = is_array( $response ) && $response['took'] ? $response['took'] : null;
132
133
		$query = array(
134
			'args'          => $es_args,
135
			'response'      => $response,
136
			'response_code' => $response_code,
137
			'elapsed_time'   => ( $end_time - $start_time ) * 1000, // Convert from float seconds to ms
138
			'es_time'       => $took,
139
			'url'           => $service_url,
140
		);
141
142
		/**
143
		 * Fires after a search request has been performed
144
		 *
145
		 * Includes the following info in the $query parameter:
146
		 *
147
		 * array args Array of Elasticsearch arguments for the search
148
		 * array response Raw API response, JSON decoded
149
		 * int response_code HTTP response code of the request
150
		 * float elapsed_time Roundtrip time of the search request, in milliseconds
151
		 * float es_time Amount of time Elasticsearch spent running the request, in milliseconds
152
		 * string url API url that was queried
153
		 *
154
		 * @since 5.0
155
		 *
156
		 * @param array $query Array of information about the query performed
157
		 */
158
		do_action( 'did_jetpack_search_query', $query );
159
160
		return $response;
161
	}
162
163
	/**
164
	 * Bypass the normal Search query and offload it to Jetpack servers
165
	 *
166
	 * This is the main hook of the plugin and is responsible for returning the posts that match the search query
167
	 *
168
	 * @module search
169
	 *
170
	 * @param array $posts Current array of posts (still pre-query)
171
	 * @param WP_Query $query The WP_Query being filtered
172
	 *
173
	 * @return array Array of matching posts
174
	 */
175
	public function filter__posts_pre_query( $posts, $query ) {
176
		if ( ! $query->is_main_query() || ! $query->is_search() ) {
177
			return $posts;
178
		}
179
180
		$this->do_search( $query );
181
182
		if ( ! is_array( $this->search_result ) ) {
183
			return $posts;
184
		}
185
186
		// If no results, nothing to do
187
		if ( ! count( $this->search_result['results']['hits'] ) ) {
188
			return array();
189
		}
190
191
		$post_ids = array();
192
193
		foreach ( $this->search_result['results']['hits'] as $result ) {
194
			$post_ids[] = (int) $result['fields']['post_id'];
195
		}
196
197
		// Query all posts now
198
		$args = array(
199
			'post__in'  => $post_ids,
200
			'perm'      => 'readable',
201
			'post_type' => 'any',
202
		);
203
204
		$posts_query = new WP_Query( $args );
205
206
		// WP Core doesn't call the set_found_posts and its filters when filtering posts_pre_query like we do, so need to
207
		// do these manually
208
		$query->found_posts   = $this->found_posts;
209
		$query->max_num_pages = ceil( $this->found_posts / $query->get( 'posts_per_page' ) );
210
211
		return $posts_query->posts;
212
	}
213
214
	/**
215
	 * Build up the search, then run it against the Jetpack servers
216
	 *
217
	 * @param WP_Query $query The original WP_Query to use for the parameters of our search
218
	 */
219
	public function do_search( WP_Query $query ) {
220
		if ( ! $query->is_main_query() || ! $query->is_search() ) {
221
			return;
222
		}
223
224
		$page = ( $query->get( 'paged' ) ) ? absint( $query->get( 'paged' ) ) : 1;
225
226
		$posts_per_page = $query->get( 'posts_per_page' );
227
228
		// ES API does not allow more than 15 results at a time
229
		if ( $posts_per_page > 15 ) {
230
			$posts_per_page = 15;
231
		}
232
233
		// Start building the WP-style search query args
234
		// They'll be translated to ES format args later
235
		$es_wp_query_args = array(
236
			'query'          => $query->get( 's' ),
237
			'posts_per_page' => $posts_per_page,
238
			'paged'          => $page,
239
			'orderby'        => $query->get( 'orderby' ),
240
			'order'          => $query->get( 'order' ),
241
		);
242
243
		if ( ! empty( $this->aggregations ) ) {
244
			$es_wp_query_args['aggregations'] = $this->aggregations;
245
		}
246
247
		// Did we query for authors?
248
		if ( $query->get( 'author_name' ) ) {
249
			$es_wp_query_args['author_name'] = $query->get( 'author_name' );
250
		}
251
252
		$es_wp_query_args['post_type'] = $this->get_es_wp_query_post_type_for_query( $query );
253
254
		$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...
255
256
257
		/**
258
		 * Modify the search query parameters, such as controlling the post_type.
259
		 *
260
		 * These arguments are in the format of WP_Query arguments
261
		 *
262
		 * @module search
263
		 *
264
		 * @since 5.0.0
265
		 *
266
		 * @param array $es_wp_query_args The current query args, in WP_Query format
267
		 * @param WP_Query $query The original query object
268
		 */
269
		$es_wp_query_args = apply_filters( 'jetpack_search_es_wp_query_args', $es_wp_query_args, $query );
270
271
		// If page * posts_per_page is greater than our max offset, send a 404. This is necessary because the offset is
272
		// capped at $this->max_offset, so a high page would always return the last page of results otherwise
273
		if ( ( $es_wp_query_args['paged'] * $es_wp_query_args['posts_per_page'] ) > $this->max_offset ) {
274
			$query->set_404();
275
276
			return;
277
		}
278
279
		// If there were no post types returned, then 404 to avoid querying against non-public post types, which could
280
		// happen if we don't add the post type restriction to the ES query
281
		if ( empty( $es_wp_query_args['post_type'] ) ) {
282
			$query->set_404();
283
284
			return;
285
		}
286
287
		// Convert the WP-style args into ES args
288
		$es_query_args = $this->convert_wp_es_to_es_args( $es_wp_query_args );
289
290
		//Only trust ES to give us IDs, not the content since it is a mirror
291
		$es_query_args['fields'] = array(
292
			'post_id',
293
		);
294
295
		/**
296
		 * Modify the underlying ES query that is passed to the search endpoint. The returned args must represent a valid ES query
297
		 *
298
		 * This filter is harder to use if you're unfamiliar with ES, but allows complete control over the query
299
		 *
300
		 * @module search
301
		 *
302
		 * @since 5.0.0
303
		 *
304
		 * @param array $es_query_args The raw ES query args
305
		 * @param WP_Query $query The original query object
306
		 */
307
		$es_query_args = apply_filters( 'jetpack_search_es_query_args', $es_query_args, $query );
308
309
		// Do the actual search query!
310
		$this->search_result = $this->search( $es_query_args );
311
312
		if ( is_wp_error( $this->search_result ) || ! is_array( $this->search_result ) || empty( $this->search_result['results'] ) || empty( $this->search_result['results']['hits'] ) ) {
313
			$this->found_posts = 0;
314
315
			return;
316
		}
317
318
		// Total number of results for paging purposes. Capped at $this->>max_offset + $posts_per_page, as deep paging
319
		// gets quite expensive
320
		$this->found_posts = min( $this->search_result['results']['total'], $this->max_offset + $posts_per_page );
321
322
		return;
323
	}
324
325
	/**
326
	 * Given a WP_Query, convert its WP_Tax_Query (if present) into the WP-style ES term arguments for the search
327
	 *
328
	 * @module search
329
	 *
330
	 * @param WP_Query $query The original WP_Query object for which to parse the taxonomy query
331
	 *
332
	 * @return array The new WP-style ES arguments (that will be converted into 'real' ES arguments)
333
	 */
334
	public function get_es_wp_query_terms_for_query( WP_Query $query ) {
335
		$args = array();
336
337
		$the_tax_query = $query->tax_query;
338
339
		if ( ! $the_tax_query ) {
340
			return $args;
341
		}
342
343
344
		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...
345
			return $args;
346
		}
347
348
		$args = array();
349
350
		foreach ( $the_tax_query->queries as $tax_query ) {
351
			// Right now we only support slugs...see note above
352
			if ( 'slug' !== $tax_query['field'] ) {
353
				continue;
354
			}
355
356
			$taxonomy = $tax_query['taxonomy'];
357
358 View Code Duplication
			if ( ! isset( $args[ $taxonomy ] ) || ! is_array( $args[ $taxonomy ] ) ) {
359
				$args[ $taxonomy ] = array();
360
			}
361
362
			$args[ $taxonomy ] = array_merge( $args[ $taxonomy ], $tax_query['terms'] );
363
		}
364
365
		return $args;
366
	}
367
368
	/**
369
	 * Parse out the post type from a WP_Query
370
	 *
371
	 * Only allows post types that are not marked as 'exclude_from_search'
372
	 *
373
	 * @module search
374
	 *
375
	 * @param WP_Query $query Original WP_Query object
376
	 *
377
	 * @return array Array of searchable post types corresponding to the original query
378
	 */
379
	public function get_es_wp_query_post_type_for_query( WP_Query $query ) {
380
		$post_types = $query->get( 'post_type' );
381
382
		// If we're searching 'any', we want to only pass searchable post types to ES
383
		if ( 'any' === $post_types ) {
384
			$post_types = array_values( get_post_types( array(
385
				'exclude_from_search' => false,
386
			) ) );
387
		}
388
389
		if ( ! is_array( $post_types ) ) {
390
			$post_types = array( $post_types );
391
		}
392
393
		$post_types = array_unique( $post_types );
394
395
		$sanitized_post_types = array();
396
397
		// Make sure the post types are queryable
398
		foreach ( $post_types as $post_type ) {
399
			if ( ! $post_type ) {
400
				continue;
401
			}
402
403
			$post_type_object = get_post_type_object( $post_type );
404
405
			if ( ! $post_type_object || $post_type_object->exclude_from_search ) {
406
				continue;
407
			}
408
409
			$sanitized_post_types[] = $post_type;
410
		}
411
412
		return $sanitized_post_types;
413
	}
414
415
	/**
416
	 * Initialze widgets for the Search module
417
	 *
418
	 * @module search
419
	 */
420
	public function action__widgets_init() {
421
		require_once( dirname( __FILE__ ) . '/class.jetpack-search-widget-filters.php' );
422
423
		register_widget( 'Jetpack_Search_Widget_Filters' );
424
	}
425
426
	/**
427
	 * Get the Elasticsearch result
428
	 *
429
	 * @module search
430
	 *
431
	 * @param bool $raw If true, does not check for WP_Error or return the 'results' array - the JSON decoded HTTP response
432
	 *
433
	 * @return array|bool The search results, or false if there was a failure
434
	 */
435
	public function get_search_result( $raw = false ) {
436
		if ( $raw ) {
437
			return $this->search_result;
438
		}
439
440
		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;
441
	}
442
443
	/**
444
	 * Add the date portion of a WP_Query onto the query args
445
	 *
446
	 * @param array    $es_wp_query_args
447
	 * @param WP_Query $query The original WP_Query
448
	 *
449
	 * @return array The es wp query args, with date filters added (as needed)
450
	 */
451
	public function filter__add_date_filter_to_query( array $es_wp_query_args, WP_Query $query ) {
452
		if ( $query->get( 'year' ) ) {
453
			if ( $query->get( 'monthnum' ) ) {
454
				// Padding
455
				$date_monthnum = sprintf( '%02d', $query->get( 'monthnum' ) );
456
457
				if ( $query->get( 'day' ) ) {
458
					// Padding
459
					$date_day = sprintf( '%02d', $query->get( 'day' ) );
460
461
					$date_start = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $date_day . ' 00:00:00';
462
					$date_end   = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $date_day . ' 23:59:59';
463
				} else {
464
					$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
465
466
					$date_start = $query->get( 'year' ) . '-' . $date_monthnum . '-01 00:00:00';
467
					$date_end   = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $days_in_month . ' 23:59:59';
468
				}
469
			} else {
470
				$date_start = $query->get( 'year' ) . '-01-01 00:00:00';
471
				$date_end   = $query->get( 'year' ) . '-12-31 23:59:59';
472
			}
473
474
			$es_wp_query_args['date_range'] = array(
475
				'field' => 'date',
476
				'gte'   => $date_start,
477
				'lte'   => $date_end,
478
			);
479
		}
480
481
		return $es_wp_query_args;
482
	}
483
484
	/**
485
	 * Converts WP_Query style args to ES args
486
	 *
487
	 * @module search
488
	 *
489
	 * @param array $args Array of WP_Query style arguments
490
	 *
491
	 * @return array Array of ES style query arguments
492
	 */
493
	function convert_wp_es_to_es_args( array $args ) {
494
		jetpack_require_lib( 'jetpack-wpes-query-builder' );
495
496
		$builder = new Jetpack_WPES_Query_Builder();
497
498
		$defaults = array(
499
			'blog_id'        => get_current_blog_id(),
500
501
			'query'          => null,    // Search phrase
502
			'query_fields'   => array( 'title', 'content', 'author', 'tag', 'category' ),
503
504
			'post_type'      => null,  // string or an array
505
			'terms'          => array(), // ex: array( 'taxonomy-1' => array( 'slug' ), 'taxonomy-2' => array( 'slug-a', 'slug-b' ) )
506
507
			'author'         => null,    // id or an array of ids
508
			'author_name'    => array(), // string or an array
509
510
			'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'
511
512
			'orderby'        => null,    // Defaults to 'relevance' if query is set, otherwise 'date'. Pass an array for multiple orders.
513
			'order'          => 'DESC',
514
515
			'posts_per_page' => 10,
516
517
			'offset'         => null,
518
			'paged'          => null,
519
520
			/**
521
			 * Aggregations. Examples:
522
			 * array(
523
			 *     'Tag'       => array( 'type' => 'taxonomy', 'taxonomy' => 'post_tag', 'count' => 10 ) ),
524
			 *     'Post Type' => array( 'type' => 'post_type', 'count' => 10 ) ),
525
			 * );
526
			 */
527
			'aggregations'         => null,
528
		);
529
530
		$args = wp_parse_args( $args, $defaults );
531
532
		$es_query_args = array(
533
			'blog_id' => absint( $args['blog_id'] ),
534
			'size'    => absint( $args['posts_per_page'] ),
535
		);
536
537
		// ES "from" arg (offset)
538
		if ( $args['offset'] ) {
539
			$es_query_args['from'] = absint( $args['offset'] );
540
		} elseif ( $args['paged'] ) {
541
			$es_query_args['from'] = max( 0, ( absint( $args['paged'] ) - 1 ) * $es_query_args['size'] );
542
		}
543
544
		// Limit the offset to $this->max_offset posts, as deep pages get exponentially slower
545
		// See https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html
546
		$es_query_args['from'] = min( $es_query_args['from'], $this->max_offset );
547
548
		if ( ! is_array( $args['author_name'] ) ) {
549
			$args['author_name'] = array( $args['author_name'] );
550
		}
551
552
		// ES stores usernames, not IDs, so transform
553
		if ( ! empty( $args['author'] ) ) {
554
			if ( ! is_array( $args['author'] ) ) {
555
				$args['author'] = array( $args['author'] );
556
			}
557
558
			foreach ( $args['author'] as $author ) {
559
				$user = get_user_by( 'id', $author );
560
561
				if ( $user && ! empty( $user->user_login ) ) {
562
					$args['author_name'][] = $user->user_login;
563
				}
564
			}
565
		}
566
567
		//////////////////////////////////////////////////
568
		// Build the filters from the query elements.
569
		// Filters rock because they are cached from one query to the next
570
		// but they are cached as individual filters, rather than all combined together.
571
		// May get performance boost by also caching the top level boolean filter too.
572
		$filters = array();
573
574
		if ( $args['post_type'] ) {
575
			if ( ! is_array( $args['post_type'] ) ) {
576
				$args['post_type'] = array( $args['post_type'] );
577
			}
578
579
			$filters[] = array(
580
				'terms' => array(
581
					'post_type' => $args['post_type'],
582
				),
583
			);
584
		}
585
586
		if ( $args['author_name'] ) {
587
			$filters[] = array(
588
				'terms' => array(
589
					'author_login' => $args['author_name'],
590
				),
591
			);
592
		}
593
594
		if ( ! empty( $args['date_range'] ) && isset( $args['date_range']['field'] ) ) {
595
			$field = $args['date_range']['field'];
596
597
			unset( $args['date_range']['field'] );
598
599
			$filters[] = array(
600
				'range' => array(
601
					$field => $args['date_range'],
602
				),
603
			);
604
		}
605
606
		if ( is_array( $args['terms'] ) ) {
607
			foreach ( $args['terms'] as $tax => $terms ) {
608
				$terms = (array) $terms;
609
610
				if ( count( $terms ) && mb_strlen( $tax ) ) {
611 View Code Duplication
					switch ( $tax ) {
612
						case 'post_tag':
613
							$tax_fld = 'tag.slug';
614
615
							break;
616
617
						case 'category':
618
							$tax_fld = 'category.slug';
619
620
							break;
621
622
						default:
623
							$tax_fld = 'taxonomy.' . $tax . '.slug';
624
625
							break;
626
					}
627
628
					foreach ( $terms as $term ) {
629
						$filters[] = array(
630
							'term' => array(
631
								$tax_fld => $term,
632
							),
633
						);
634
					}
635
				}
636
			}
637
		}
638
639
		if ( $args['query'] ) {
640
			$query = array(
641
				'multi_match' => array(
642
					'query'    => $args['query'],
643
					'fields'   => $args['query_fields'],
644
					'operator' => 'and',
645
					'type'     => 'cross_fields',
646
				),
647
			);
648
649
			$builder->add_query( $query );
650
651
			Jetpack_Search::score_query_by_recency( $builder );
652
653
			if ( ! $args['orderby'] ) {
654
				$args['orderby'] = array( 'relevance' );
655
			}
656
		} else {
657
			if ( ! $args['orderby'] ) {
658
				$args['orderby'] = array( 'date' );
659
			}
660
		}
661
662
		// Validate the "order" field
663
		switch ( strtolower( $args['order'] ) ) {
664
			case 'asc':
665
				$args['order'] = 'asc';
666
				break;
667
668
			case 'desc':
669
			default:
670
				$args['order'] = 'desc';
671
				break;
672
		}
673
674
		$es_query_args['sort'] = array();
675
676
		foreach ( (array) $args['orderby'] as $orderby ) {
677
			// Translate orderby from WP field to ES field
678
			switch ( $orderby ) {
679
				case 'relevance' :
680
					//never order by score ascending
681
					$es_query_args['sort'][] = array(
682
						'_score' => array(
683
							'order' => 'desc',
684
						),
685
					);
686
687
					break;
688
689 View Code Duplication
				case 'date' :
690
					$es_query_args['sort'][] = array(
691
						'date' => array(
692
							'order' => $args['order'],
693
						),
694
					);
695
696
					break;
697
698 View Code Duplication
				case 'ID' :
699
					$es_query_args['sort'][] = array(
700
						'id' => array(
701
							'order' => $args['order'],
702
						),
703
					);
704
705
					break;
706
707
				case 'author' :
708
					$es_query_args['sort'][] = array(
709
						'author.raw' => array(
710
							'order' => $args['order'],
711
						),
712
					);
713
714
					break;
715
			} // End switch().
716
		} // End foreach().
717
718
		if ( empty( $es_query_args['sort'] ) ) {
719
			unset( $es_query_args['sort'] );
720
		}
721
722
		if ( ! empty( $filters ) && is_array( $filters ) ) {
723
			foreach ( $filters as $filter ) {
724
				$builder->add_filter( $filter );
725
			}
726
727
			$es_query_args['filter'] = $builder->build_filter();
728
		}
729
730
		$es_query_args['query'] = $builder->build_query();
731
732
		// Aggregations
733
		if ( ! empty( $args['aggregations'] ) ) {
734
			$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...
735
736
			$es_query_args['aggregations'] = $builder->build_aggregation();
737
		}
738
739
		return $es_query_args;
740
	}
741
742
	/**
743
	 * Given an array of aggregations, parse and add them onto the Jetpack_WPES_Query_Builder object for use in ES
744
	 *
745
	 * @module search
746
	 *
747
	 * @param array $aggregations Array of Aggregations (filters) to add to the Jetpack_WPES_Query_Builder
748
	 *
749
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
750
	 */
751
	public function add_aggregations_to_es_query_builder( array $aggregations, Jetpack_WPES_Query_Builder $builder ) {
752
		foreach ( $aggregations as $label => $aggregation ) {
753
			switch ( $aggregation['type'] ) {
754
				case 'taxonomy':
755
					$this->add_taxonomy_aggregation_to_es_query_builder( $aggregation, $label, $builder );
756
757
					break;
758
759
				case 'post_type':
760
					$this->add_post_type_aggregation_to_es_query_builder( $aggregation, $label, $builder );
761
762
					break;
763
764
				case 'date_histogram':
765
					$this->add_date_histogram_aggregation_to_es_query_builder( $aggregation, $label, $builder );
766
767
					break;
768
			}
769
		}
770
	}
771
772
	/**
773
	 * Given an individual taxonomy aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
774
	 *
775
	 * @module search
776
	 *
777
	 * @param array $aggregation The aggregation to add to the query builder
778
	 * @param string $label The 'label' (unique id) for this aggregation
779
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
780
	 */
781
	public function add_taxonomy_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
782
		$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...
783
784
		switch ( $aggregation['taxonomy'] ) {
785
			case 'post_tag':
786
				$field = 'tag';
787
				break;
788
789
			case 'category':
790
				$field = 'category';
791
				break;
792
793
			default:
794
				$field = 'taxonomy.' . $aggregation['taxonomy'];
795
				break;
796
		}
797
798
		$builder->add_aggs( $label, array(
799
			'terms' => array(
800
				'field' => $field . '.slug',
801
				'size' => min( (int) $aggregation['count'], $this->max_aggregations_count ),
802
			),
803
		));
804
	}
805
806
	/**
807
	 * Given an individual post_type aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
808
	 *
809
	 * @module search
810
	 *
811
	 * @param array $aggregation The aggregation to add to the query builder
812
	 * @param string $label The 'label' (unique id) for this aggregation
813
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
814
	 */
815
	public function add_post_type_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
816
		$builder->add_aggs( $label, array(
817
			'terms' => array(
818
				'field' => 'post_type',
819
				'size' => min( (int) $aggregation['count'], $this->max_aggregations_count ),
820
			),
821
		));
822
	}
823
824
	/**
825
	 * Given an individual date_histogram aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
826
	 *
827
	 * @module search
828
	 *
829
	 * @param array $aggregation The aggregation to add to the query builder
830
	 * @param string $label The 'label' (unique id) for this aggregation
831
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
832
	 */
833
	public function add_date_histogram_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
834
		$builder->add_aggs( $label, array(
835
			'date_histogram' => array(
836
				'interval' => $aggregation['interval'],
837
				'field'    => ( ! empty( $aggregation['field'] ) && 'post_date_gmt' == $aggregation['field'] ) ? 'date_gmt' : 'date',
838
			),
839
		));
840
	}
841
842
	/**
843
	 * And an existing filter object with a list of additional filters.
844
	 *
845
	 * Attempts to optimize the filters somewhat.
846
	 *
847
	 * @module search
848
	 *
849
	 * @param array $curr_filter The existing filters to build upon
850
	 * @param array $filters The new filters to add
851
	 *
852
	 * @return array The resulting merged filters
853
	 */
854
	public static function and_es_filters( array $curr_filter, array $filters ) {
855
		if ( ! is_array( $curr_filter ) || isset( $curr_filter['match_all'] ) ) {
856
			if ( 1 === count( $filters ) ) {
857
				return $filters[0];
858
			}
859
860
			return array(
861
				'and' => $filters,
862
			);
863
		}
864
865
		return array(
866
			'and' => array_merge( array( $curr_filter ), $filters ),
867
		);
868
	}
869
870
	/**
871
	 * Add a recency score to a given Jetpack_WPES_Query_Builder object, for emphasizing newer posts in results
872
	 *
873
	 * Internally uses a gauss decay function
874
	 *
875
	 * @module search
876
	 *
877
	 * @param Jetpack_WPES_Query_Builder $builder The Jetpack_WPES_Query_Builder to add the recency score to
878
	 *
879
	 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#function-decay
880
	 */
881
	public static function score_query_by_recency( Jetpack_WPES_Query_Builder &$builder ) {
882
		//Newer content gets weighted slightly higher
883
		$date_scale  = '360d';
884
		$date_decay  = 0.9;
885
		$date_origin = date( 'Y-m-d' );
886
887
		$builder->add_decay( 'gauss', array(
888
			'date_gmt' => array(
889
				'origin' => $date_origin,
890
				'scale'  => $date_scale,
891
				'decay'  => $date_decay,
892
			),
893
		));
894
	}
895
896
	/**
897
	 * Set the available filters for the search
898
	 *
899
	 * These get rendered via the Jetpack_Search_Widget_Filters() widget
900
	 *
901
	 * Behind the scenes, these are implemented using Elasticsearch Aggregations.
902
	 *
903
	 * If you do not require counts of how many documents match each filter, please consider using regular WP Query
904
	 * arguments instead, such as via the jetpack_search_es_wp_query_args filter
905
	 *
906
	 * @module search
907
	 *
908
	 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
909
	 *
910
	 * @param array $aggregations Array of filters (aggregations) to apply to the search
911
	 */
912
	public function set_filters( array $aggregations ) {
913
		$this->aggregations = $aggregations;
914
	}
915
916
	/**
917
	 * Set the search's facets (deprecated)
918
	 *
919
	 * @module search
920
	 *
921
	 * @deprecated 5.0 Please use Jetpack_Search::set_filters() instead
922
	 *
923
	 * @see Jetpack_Search::set_filters()
924
	 *
925
	 * @param array $facets Array of facets to apply to the search
926
	 */
927
	public function set_facets( array $facets ) {
928
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::set_filters()' );
929
930
		$this->set_filters( $facets );
931
	}
932
933
	/**
934
	 * Get the raw Aggregation results from the ES response
935
	 *
936
	 * @module search
937
	 *
938
	 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
939
	 *
940
	 * @return array Array of Aggregations performed on the search
941
	 */
942
	public function get_search_aggregations_results() {
943
		$aggregations = array();
944
945
		$search_result = $this->get_search_result();
946
947
		if ( ! empty( $search_result ) && ! empty( $search_result['aggregations'] ) ) {
948
			$aggregations = $search_result['aggregations'];
949
		}
950
951
		return $aggregations;
952
	}
953
954
	/**
955
	 * Get the raw Facet results from the ES response
956
	 *
957
	 * @module search
958
	 *
959
	 * @deprecated 5.0 Please use Jetpack_Search::get_search_aggregations_results() instead
960
	 *
961
	 * @see Jetpack_Search::get_search_aggregations_results()
962
	 *
963
	 * @return array Array of Facets performed on the search
964
	 */
965
	public function get_search_facets() {
966
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_search_aggregations_results()' );
967
968
		return $this->get_search_aggregations_results();
969
	}
970
971
	/**
972
	 * Get the results of the Filters performed, including the number of matching documents
973
	 *
974
	 * Returns an array of Filters (keyed by $label, as passed to Jetpack_Search::set_filters()), containing the Filter and all resulting
975
	 * matching buckets, the url for applying/removing each bucket, etc.
976
	 *
977
	 * NOTE - if this is called before the search is performed, an empty array will be returned. Use the $aggregations class
978
	 * member if you need to access the raw filters set in Jetpack_Search::set_filters()
979
	 *
980
	 * @module search
981
	 *
982
	 * @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...
983
	 *
984
	 * @return array Array of Filters applied and info about them
985
	 */
986
	public function get_filters( WP_Query $query = null ) {
987
		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...
988
			global $wp_query;
989
990
			$query = $wp_query;
991
		}
992
993
		$aggregation_data = $this->aggregations;
994
995
		if ( empty( $aggregation_data ) ) {
996
			return $aggregation_data;
997
		}
998
999
		$aggregation_results = $this->get_search_aggregations_results();
1000
1001
		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...
1002
			return $aggregation_data;
1003
		}
1004
1005
		// NOTE - Looping over the _results_, not the original configured aggregations, so we get the 'real' data from ES
1006
		foreach ( $aggregation_results as $label => $aggregation ) {
1007
			if ( empty( $aggregation ) ) {
1008
				continue;
1009
			}
1010
1011
			$type = $this->aggregations[ $label ]['type'];
1012
1013
			$aggregation_data[ $label ]['buckets'] = array();
1014
1015
			$existing_term_slugs = array();
1016
1017
			$tax_query_var = null;
1018
1019
			// Figure out which terms are active in the query, for this taxonomy
1020
			if ( 'taxonomy' === $this->aggregations[ $label ]['type'] ) {
1021
				$tax_query_var = $this->get_taxonomy_query_var(  $this->aggregations[ $label ]['taxonomy'] );
1022
1023
				if ( ! empty( $query->tax_query ) && ! empty( $query->tax_query->queries ) && is_array( $query->tax_query->queries ) ) {
1024
					foreach( $query->tax_query->queries as $tax_query ) {
1025
						if ( $this->aggregations[ $label ]['taxonomy'] === $tax_query['taxonomy'] &&
1026
						     'slug' === $tax_query['field'] &&
1027
						     is_array( $tax_query['terms'] ) ) {
1028
							$existing_term_slugs = array_merge( $existing_term_slugs, $tax_query['terms'] );
1029
						}
1030
					}
1031
				}
1032
			}
1033
1034
			// Now take the resulting found aggregation items and generate the additional info about them, such as
1035
			// activation/deactivation url, name, count, etc
1036
			$buckets = array();
1037
1038
			if ( ! empty( $aggregation['buckets'] ) ) {
1039
				$buckets = (array) $aggregation['buckets'];
1040
			}
1041
1042
			// Some aggregation types like date_histogram don't support the max results parameter
1043
			if ( is_int( $this->aggregations[ $label ]['count'] ) && count( $buckets ) > $this->aggregations[ $label ]['count'] ) {
1044
				$buckets = array_slice( $buckets, 0, $this->aggregations[ $label ]['count'] );
1045
			}
1046
1047
			foreach ( $buckets as $item ) {
1048
				$query_vars = array();
1049
				$active     = false;
1050
				$remove_url = null;
1051
				$name       = '';
1052
1053
				// What type was the original aggregation?
1054
				switch ( $type ) {
1055
					case 'taxonomy':
1056
						$taxonomy = $this->aggregations[ $label ]['taxonomy'];
1057
1058
						$term = get_term_by( 'slug', $item['key'], $taxonomy );
1059
1060
						if ( ! $term || ! $tax_query_var ) {
1061
							continue 2; // switch() is considered a looping structure
1062
						}
1063
1064
						$query_vars = array(
1065
							$tax_query_var => implode( '+', array_merge( $existing_term_slugs, array( $term->slug ) ) ),
1066
						);
1067
1068
						$name = $term->name;
1069
1070
						// Let's determine if this term is active or not
1071
1072
						if ( in_array( $item['key'], $existing_term_slugs, true ) ) {
1073
							$active = true;
1074
1075
							$slug_count = count( $existing_term_slugs );
1076
1077 View Code Duplication
							if ( $slug_count > 1 ) {
1078
								$remove_url = add_query_arg( $tax_query_var, urlencode( implode( '+', array_diff( $existing_term_slugs, array( $item['key'] ) ) ) ) );
1079
							} else {
1080
								$remove_url = remove_query_arg( $tax_query_var );
1081
							}
1082
						}
1083
1084
						break;
1085
1086
					case 'post_type':
1087
						$post_type = get_post_type_object( $item['key'] );
1088
1089
						if ( ! $post_type || $post_type->exclude_from_search ) {
1090
							continue 2;  // switch() is considered a looping structure
1091
						}
1092
1093
						$query_vars = array(
1094
							'post_type' => $item['key'],
1095
						);
1096
1097
						$name = $post_type->labels->singular_name;
1098
1099
						// Is this post type active on this search?
1100
						$post_types = $query->get( 'post_type' );
1101
1102
						if ( ! is_array( $post_types ) ) {
1103
							$post_types = array( $post_types );
1104
						}
1105
1106
						if ( in_array( $item['key'], $post_types ) ) {
1107
							$active = true;
1108
1109
							$post_type_count = count( $post_types );
1110
1111
							// 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
1112 View Code Duplication
							if ( $post_type_count > 1 ) {
1113
								$remove_url = add_query_arg( 'post_type', urlencode_deep( array_diff( $post_types, array( $item['key'] ) ) ) );
1114
							} else {
1115
								$remove_url = remove_query_arg( 'post_type' );
1116
							}
1117
						}
1118
1119
						break;
1120
1121
					case 'date_histogram':
1122
						$timestamp = $item['key'] / 1000;
1123
1124
						$current_year  = $query->get( 'year' );
1125
						$current_month = $query->get( 'monthnum' );
1126
						$current_day   = $query->get( 'day' );
1127
1128
						switch ( $this->aggregations[ $label ]['interval'] ) {
1129
							case 'year':
1130
								$year = (int) date( 'Y', $timestamp );
1131
1132
								$query_vars = array(
1133
									'year'     => $year,
1134
									'monthnum' => false,
1135
									'day'      => false,
1136
								);
1137
1138
								$name = $year;
1139
1140
								// Is this year currently selected?
1141
								if ( ! empty( $current_year ) && (int) $current_year === $year ) {
1142
									$active = true;
1143
1144
									$remove_url = remove_query_arg( array( 'year', 'monthnum', 'day' ) );
1145
								}
1146
1147
								break;
1148
1149
							case 'month':
1150
								$year  = (int) date( 'Y', $timestamp );
1151
								$month = (int) date( 'n', $timestamp );
1152
1153
								$query_vars = array(
1154
									'year'     => $year,
1155
									'monthnum' => $month,
1156
									'day'      => false,
1157
								);
1158
1159
								$name = date( 'F Y', $timestamp );
1160
1161
								// Is this month currently selected?
1162
								if ( ! empty( $current_year ) && (int) $current_year === $year &&
1163
								     ! empty( $current_month ) && (int) $current_month === $month ) {
1164
									$active = true;
1165
1166
									$remove_url = remove_query_arg( array( 'monthnum', 'day' ) );
1167
								}
1168
1169
								break;
1170
1171
							case 'day':
1172
								$year  = (int) date( 'Y', $timestamp );
1173
								$month = (int) date( 'n', $timestamp );
1174
								$day   = (int) date( 'j', $timestamp );
1175
1176
								$query_vars = array(
1177
									'year'     => $year,
1178
									'monthnum' => $month,
1179
									'day'      => $day,
1180
								);
1181
1182
								$name = date( 'F jS, Y', $timestamp );
1183
1184
								// Is this day currently selected?
1185
								if ( ! empty( $current_year ) && (int) $current_year === $year &&
1186
								     ! empty( $current_month ) && (int) $current_month === $month &&
1187
								     ! empty( $current_day ) && (int) $current_day === $day ) {
1188
									$active = true;
1189
1190
									$remove_url = remove_query_arg( array( 'day' ) );
1191
								}
1192
1193
								break;
1194
1195
							default:
1196
								continue 3; // switch() is considered a looping structure
1197
						} // End switch().
1198
1199
						break;
1200
1201
					default:
1202
						//continue 2; // switch() is considered a looping structure
1203
				} // End switch().
1204
1205
				// Need to urlencode param values since add_query_arg doesn't
1206
				$url_params = urlencode_deep( $query_vars );
1207
1208
				$aggregation_data[ $label ]['buckets'][] = array(
1209
					'url'        => add_query_arg( $url_params ),
1210
					'query_vars' => $query_vars,
1211
					'name'       => $name,
1212
					'count'      => $item['doc_count'],
1213
					'active'     => $active,
1214
					'remove_url' => $remove_url,
1215
					'type'       => $type,
1216
					'type_label' => $label,
1217
				);
1218
			} // End foreach().
1219
		} // End foreach().
1220
1221
		return $aggregation_data;
1222
	}
1223
1224
	/**
1225
	 * Get the results of the Facets performed
1226
	 *
1227
	 * @module search
1228
	 *
1229
	 * @deprecated 5.0 Please use Jetpack_Search::get_filters() instead
1230
	 *
1231
	 * @see Jetpack_Search::get_filters()
1232
	 *
1233
	 * @return array $facets Array of Facets applied and info about them
1234
	 */
1235
	public function get_search_facet_data() {
1236
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_filters()' );
1237
1238
		return $this->get_filters();
1239
	}
1240
1241
	/**
1242
	 * Get the Filters that are currently applied to this search
1243
	 *
1244
	 * @module search
1245
	 *
1246
	 * @return array Array if Filters that were applied
1247
	 */
1248
	public function get_active_filter_buckets() {
1249
		$active_buckets = array();
1250
1251
		$filters = $this->get_filters();
1252
1253
		if ( ! is_array( $filters ) ) {
1254
			return $active_buckets;
1255
		}
1256
1257
		foreach( $filters as $filter ) {
1258
			if ( isset( $filters['buckets'] ) && is_array( $filter['buckets'] ) ) {
1259
				foreach( $filter['buckets'] as $item ) {
1260
					if ( isset( $item['active'] ) && $item['active'] ) {
1261
						$active_buckets[] = $item;
1262
					}
1263
				}
1264
			}
1265
		}
1266
1267
		return $active_buckets;
1268
	}
1269
1270
	/**
1271
	 * Get the Filters that are currently applied to this search
1272
	 *
1273
	 * @module search
1274
	 *
1275
	 * @return array Array if Filters that were applied
1276
	 */
1277
	public function get_current_filters() {
1278
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_active_filter_buckets()' );
1279
1280
		return $this->get_active_filter_buckets();
1281
	}
1282
1283
	/**
1284
	 * Calculate the right query var to use for a given taxonomy
1285
	 *
1286
	 * Allows custom code to modify the GET var that is used to represent a given taxonomy, via the jetpack_search_taxonomy_query_var filter
1287
	 *
1288
	 * @module search
1289
	 *
1290
	 * @param string $taxonomy_name The name of the taxonomy for which to get the query var
1291
	 *
1292
	 * @return bool|string The query var to use for this taxonomy, or false if none found
1293
	 */
1294
	public function get_taxonomy_query_var( $taxonomy_name ) {
1295
		$taxonomy = get_taxonomy( $taxonomy_name );
1296
1297
		if ( ! $taxonomy || is_wp_error( $taxonomy ) ) {
1298
			return false;
1299
		}
1300
1301
		/**
1302
		 * Modify the query var to use for a given taxonomy
1303
		 *
1304
		 * @module search
1305
		 *
1306
		 * @since 5.0.0
1307
		 *
1308
		 * @param string $query_var The current query_var for the taxonomy
1309
		 * @param string $taxonomy_name The taxonomy name
1310
		 */
1311
		return apply_filters( 'jetpack_search_taxonomy_query_var', $taxonomy->query_var, $taxonomy_name );
1312
	}
1313
}
1314