Completed
Push — 8096/search-fix-aggregation-or... ( 30f839 )
by
unknown
14:56 queued 06:57
created

Jetpack_Search::fix_aggregation_ordering()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 4
nop 2
dl 0
loc 15
rs 8.8571
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() || ! Jetpack::active_plan_supports( 'search' ) ) {
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
		// If we have aggregations, fix the ordering to match the input order (ES doesn't
344
		// guarantee the return order)
345
		if ( isset( $this->search_result['results']['aggregations'] ) && ! empty( $this->search_result['results']['aggregations'] ) ) {
346
			$this->search_result['results']['aggregations'] = $this->fix_aggregation_ordering( $this->search_result['results']['aggregations'], $this->aggregations );
347
		}
348
349
		// Total number of results for paging purposes. Capped at $this->>max_offset + $posts_per_page, as deep paging
350
		// gets quite expensive
351
		$this->found_posts = min( $this->search_result['results']['total'], $this->max_offset + $posts_per_page );
352
353
		return;
354
	}
355
356
	/**
357
	 * Given a WP_Query, convert its WP_Tax_Query (if present) into the WP-style ES term arguments for the search
358
	 *
359
	 * @module search
360
	 *
361
	 * @param WP_Query $query The original WP_Query object for which to parse the taxonomy query
362
	 *
363
	 * @return array The new WP-style ES arguments (that will be converted into 'real' ES arguments)
364
	 */
365
	public function get_es_wp_query_terms_for_query( WP_Query $query ) {
366
		$args = array();
367
368
		$the_tax_query = $query->tax_query;
369
370
		if ( ! $the_tax_query ) {
371
			return $args;
372
		}
373
374
375
		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...
376
			return $args;
377
		}
378
379
		$args = array();
380
381
		foreach ( $the_tax_query->queries as $tax_query ) {
382
			// Right now we only support slugs...see note above
383
			if ( 'slug' !== $tax_query['field'] ) {
384
				continue;
385
			}
386
387
			$taxonomy = $tax_query['taxonomy'];
388
389 View Code Duplication
			if ( ! isset( $args[ $taxonomy ] ) || ! is_array( $args[ $taxonomy ] ) ) {
390
				$args[ $taxonomy ] = array();
391
			}
392
393
			$args[ $taxonomy ] = array_merge( $args[ $taxonomy ], $tax_query['terms'] );
394
		}
395
396
		return $args;
397
	}
398
399
	/**
400
	 * Parse out the post type from a WP_Query
401
	 *
402
	 * Only allows post types that are not marked as 'exclude_from_search'
403
	 *
404
	 * @module search
405
	 *
406
	 * @param WP_Query $query Original WP_Query object
407
	 *
408
	 * @return array Array of searchable post types corresponding to the original query
409
	 */
410
	public function get_es_wp_query_post_type_for_query( WP_Query $query ) {
411
		$post_types = $query->get( 'post_type' );
412
413
		// If we're searching 'any', we want to only pass searchable post types to ES
414
		if ( 'any' === $post_types ) {
415
			$post_types = array_values( get_post_types( array(
416
				'exclude_from_search' => false,
417
			) ) );
418
		}
419
420
		if ( ! is_array( $post_types ) ) {
421
			$post_types = array( $post_types );
422
		}
423
424
		$post_types = array_unique( $post_types );
425
426
		$sanitized_post_types = array();
427
428
		// Make sure the post types are queryable
429
		foreach ( $post_types as $post_type ) {
430
			if ( ! $post_type ) {
431
				continue;
432
			}
433
434
			$post_type_object = get_post_type_object( $post_type );
435
436
			if ( ! $post_type_object || $post_type_object->exclude_from_search ) {
437
				continue;
438
			}
439
440
			$sanitized_post_types[] = $post_type;
441
		}
442
443
		return $sanitized_post_types;
444
	}
445
446
	/**
447
	 * Initialze widgets for the Search module
448
	 *
449
	 * @module search
450
	 */
451
	public function action__widgets_init() {
452
		require_once( dirname( __FILE__ ) . '/class.jetpack-search-widget-filters.php' );
453
454
		register_widget( 'Jetpack_Search_Widget_Filters' );
455
	}
456
457
	/**
458
	 * Get the Elasticsearch result
459
	 *
460
	 * @module search
461
	 *
462
	 * @param bool $raw If true, does not check for WP_Error or return the 'results' array - the JSON decoded HTTP response
463
	 *
464
	 * @return array|bool The search results, or false if there was a failure
465
	 */
466
	public function get_search_result( $raw = false ) {
467
		if ( $raw ) {
468
			return $this->search_result;
469
		}
470
471
		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;
472
	}
473
474
	/**
475
	 * Add the date portion of a WP_Query onto the query args
476
	 *
477
	 * @param array    $es_wp_query_args
478
	 * @param WP_Query $query The original WP_Query
479
	 *
480
	 * @return array The es wp query args, with date filters added (as needed)
481
	 */
482
	public function filter__add_date_filter_to_query( array $es_wp_query_args, WP_Query $query ) {
483
		if ( $query->get( 'year' ) ) {
484
			if ( $query->get( 'monthnum' ) ) {
485
				// Padding
486
				$date_monthnum = sprintf( '%02d', $query->get( 'monthnum' ) );
487
488
				if ( $query->get( 'day' ) ) {
489
					// Padding
490
					$date_day = sprintf( '%02d', $query->get( 'day' ) );
491
492
					$date_start = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $date_day . ' 00:00:00';
493
					$date_end   = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $date_day . ' 23:59:59';
494
				} else {
495
					$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
496
497
					$date_start = $query->get( 'year' ) . '-' . $date_monthnum . '-01 00:00:00';
498
					$date_end   = $query->get( 'year' ) . '-' . $date_monthnum . '-' . $days_in_month . ' 23:59:59';
499
				}
500
			} else {
501
				$date_start = $query->get( 'year' ) . '-01-01 00:00:00';
502
				$date_end   = $query->get( 'year' ) . '-12-31 23:59:59';
503
			}
504
505
			$es_wp_query_args['date_range'] = array(
506
				'field' => 'date',
507
				'gte'   => $date_start,
508
				'lte'   => $date_end,
509
			);
510
		}
511
512
		return $es_wp_query_args;
513
	}
514
515
	/**
516
	 * Converts WP_Query style args to ES args
517
	 *
518
	 * @module search
519
	 *
520
	 * @param array $args Array of WP_Query style arguments
521
	 *
522
	 * @return array Array of ES style query arguments
523
	 */
524
	function convert_wp_es_to_es_args( array $args ) {
525
		jetpack_require_lib( 'jetpack-wpes-query-builder' );
526
527
		$builder = new Jetpack_WPES_Query_Builder();
528
529
		$defaults = array(
530
			'blog_id'        => get_current_blog_id(),
531
532
			'query'          => null,    // Search phrase
533
			'query_fields'   => array( 'title', 'content', 'author', 'tag', 'category' ),
534
535
			'post_type'      => null,  // string or an array
536
			'terms'          => array(), // ex: array( 'taxonomy-1' => array( 'slug' ), 'taxonomy-2' => array( 'slug-a', 'slug-b' ) )
537
538
			'author'         => null,    // id or an array of ids
539
			'author_name'    => array(), // string or an array
540
541
			'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'
542
543
			'orderby'        => null,    // Defaults to 'relevance' if query is set, otherwise 'date'. Pass an array for multiple orders.
544
			'order'          => 'DESC',
545
546
			'posts_per_page' => 10,
547
548
			'offset'         => null,
549
			'paged'          => null,
550
551
			/**
552
			 * Aggregations. Examples:
553
			 * array(
554
			 *     'Tag'       => array( 'type' => 'taxonomy', 'taxonomy' => 'post_tag', 'count' => 10 ) ),
555
			 *     'Post Type' => array( 'type' => 'post_type', 'count' => 10 ) ),
556
			 * );
557
			 */
558
			'aggregations'         => null,
559
		);
560
561
		$args = wp_parse_args( $args, $defaults );
562
563
		$es_query_args = array(
564
			'blog_id' => absint( $args['blog_id'] ),
565
			'size'    => absint( $args['posts_per_page'] ),
566
		);
567
568
		// ES "from" arg (offset)
569
		if ( $args['offset'] ) {
570
			$es_query_args['from'] = absint( $args['offset'] );
571
		} elseif ( $args['paged'] ) {
572
			$es_query_args['from'] = max( 0, ( absint( $args['paged'] ) - 1 ) * $es_query_args['size'] );
573
		}
574
575
		// Limit the offset to $this->max_offset posts, as deep pages get exponentially slower
576
		// See https://www.elastic.co/guide/en/elasticsearch/guide/current/pagination.html
577
		$es_query_args['from'] = min( $es_query_args['from'], $this->max_offset );
578
579
		if ( ! is_array( $args['author_name'] ) ) {
580
			$args['author_name'] = array( $args['author_name'] );
581
		}
582
583
		// ES stores usernames, not IDs, so transform
584
		if ( ! empty( $args['author'] ) ) {
585
			if ( ! is_array( $args['author'] ) ) {
586
				$args['author'] = array( $args['author'] );
587
			}
588
589
			foreach ( $args['author'] as $author ) {
590
				$user = get_user_by( 'id', $author );
591
592
				if ( $user && ! empty( $user->user_login ) ) {
593
					$args['author_name'][] = $user->user_login;
594
				}
595
			}
596
		}
597
598
		//////////////////////////////////////////////////
599
		// Build the filters from the query elements.
600
		// Filters rock because they are cached from one query to the next
601
		// but they are cached as individual filters, rather than all combined together.
602
		// May get performance boost by also caching the top level boolean filter too.
603
		$filters = array();
604
605
		if ( $args['post_type'] ) {
606
			if ( ! is_array( $args['post_type'] ) ) {
607
				$args['post_type'] = array( $args['post_type'] );
608
			}
609
610
			$filters[] = array(
611
				'terms' => array(
612
					'post_type' => $args['post_type'],
613
				),
614
			);
615
		}
616
617
		if ( $args['author_name'] ) {
618
			$filters[] = array(
619
				'terms' => array(
620
					'author_login' => $args['author_name'],
621
				),
622
			);
623
		}
624
625
		if ( ! empty( $args['date_range'] ) && isset( $args['date_range']['field'] ) ) {
626
			$field = $args['date_range']['field'];
627
628
			unset( $args['date_range']['field'] );
629
630
			$filters[] = array(
631
				'range' => array(
632
					$field => $args['date_range'],
633
				),
634
			);
635
		}
636
637
		if ( is_array( $args['terms'] ) ) {
638
			foreach ( $args['terms'] as $tax => $terms ) {
639
				$terms = (array) $terms;
640
641
				if ( count( $terms ) && mb_strlen( $tax ) ) {
642 View Code Duplication
					switch ( $tax ) {
643
						case 'post_tag':
644
							$tax_fld = 'tag.slug';
645
646
							break;
647
648
						case 'category':
649
							$tax_fld = 'category.slug';
650
651
							break;
652
653
						default:
654
							$tax_fld = 'taxonomy.' . $tax . '.slug';
655
656
							break;
657
					}
658
659
					foreach ( $terms as $term ) {
660
						$filters[] = array(
661
							'term' => array(
662
								$tax_fld => $term,
663
							),
664
						);
665
					}
666
				}
667
			}
668
		}
669
670
		if ( $args['query'] ) {
671
			$query = array(
672
				'multi_match' => array(
673
					'query'    => $args['query'],
674
					'fields'   => $args['query_fields'],
675
					'operator' => 'and',
676
					'type'     => 'cross_fields',
677
				),
678
			);
679
680
			$builder->add_query( $query );
681
682
			Jetpack_Search::score_query_by_recency( $builder );
683
684
			if ( ! $args['orderby'] ) {
685
				$args['orderby'] = array( 'relevance' );
686
			}
687
		} else {
688
			if ( ! $args['orderby'] ) {
689
				$args['orderby'] = array( 'date' );
690
			}
691
		}
692
693
		// Validate the "order" field
694
		switch ( strtolower( $args['order'] ) ) {
695
			case 'asc':
696
				$args['order'] = 'asc';
697
				break;
698
699
			case 'desc':
700
			default:
701
				$args['order'] = 'desc';
702
				break;
703
		}
704
705
		$es_query_args['sort'] = array();
706
707
		foreach ( (array) $args['orderby'] as $orderby ) {
708
			// Translate orderby from WP field to ES field
709
			switch ( $orderby ) {
710
				case 'relevance' :
711
					//never order by score ascending
712
					$es_query_args['sort'][] = array(
713
						'_score' => array(
714
							'order' => 'desc',
715
						),
716
					);
717
718
					break;
719
720 View Code Duplication
				case 'date' :
721
					$es_query_args['sort'][] = array(
722
						'date' => array(
723
							'order' => $args['order'],
724
						),
725
					);
726
727
					break;
728
729 View Code Duplication
				case 'ID' :
730
					$es_query_args['sort'][] = array(
731
						'id' => array(
732
							'order' => $args['order'],
733
						),
734
					);
735
736
					break;
737
738
				case 'author' :
739
					$es_query_args['sort'][] = array(
740
						'author.raw' => array(
741
							'order' => $args['order'],
742
						),
743
					);
744
745
					break;
746
			} // End switch().
747
		} // End foreach().
748
749
		if ( empty( $es_query_args['sort'] ) ) {
750
			unset( $es_query_args['sort'] );
751
		}
752
753
		if ( ! empty( $filters ) && is_array( $filters ) ) {
754
			foreach ( $filters as $filter ) {
755
				$builder->add_filter( $filter );
756
			}
757
758
			$es_query_args['filter'] = $builder->build_filter();
759
		}
760
761
		$es_query_args['query'] = $builder->build_query();
762
763
		// Aggregations
764
		if ( ! empty( $args['aggregations'] ) ) {
765
			$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...
766
767
			$es_query_args['aggregations'] = $builder->build_aggregation();
768
		}
769
770
		return $es_query_args;
771
	}
772
773
	/**
774
	 * Given an array of aggregations, parse and add them onto the Jetpack_WPES_Query_Builder object for use in ES
775
	 *
776
	 * @module search
777
	 *
778
	 * @param array $aggregations Array of Aggregations (filters) to add to the Jetpack_WPES_Query_Builder
779
	 *
780
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
781
	 */
782
	public function add_aggregations_to_es_query_builder( array $aggregations, Jetpack_WPES_Query_Builder $builder ) {
783
		foreach ( $aggregations as $label => $aggregation ) {
784
			switch ( $aggregation['type'] ) {
785
				case 'taxonomy':
786
					$this->add_taxonomy_aggregation_to_es_query_builder( $aggregation, $label, $builder );
787
788
					break;
789
790
				case 'post_type':
791
					$this->add_post_type_aggregation_to_es_query_builder( $aggregation, $label, $builder );
792
793
					break;
794
795
				case 'date_histogram':
796
					$this->add_date_histogram_aggregation_to_es_query_builder( $aggregation, $label, $builder );
797
798
					break;
799
			}
800
		}
801
	}
802
803
	/**
804
	 * Given an individual taxonomy aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
805
	 *
806
	 * @module search
807
	 *
808
	 * @param array $aggregation The aggregation to add to the query builder
809
	 * @param string $label The 'label' (unique id) for this aggregation
810
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
811
	 */
812
	public function add_taxonomy_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
813
		$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...
814
815
		switch ( $aggregation['taxonomy'] ) {
816
			case 'post_tag':
817
				$field = 'tag';
818
				break;
819
820
			case 'category':
821
				$field = 'category';
822
				break;
823
824
			default:
825
				$field = 'taxonomy.' . $aggregation['taxonomy'];
826
				break;
827
		}
828
829
		$builder->add_aggs( $label, array(
830
			'terms' => array(
831
				'field' => $field . '.slug',
832
				'size' => min( (int) $aggregation['count'], $this->max_aggregations_count ),
833
			),
834
		));
835
	}
836
837
	/**
838
	 * Given an individual post_type aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
839
	 *
840
	 * @module search
841
	 *
842
	 * @param array $aggregation The aggregation to add to the query builder
843
	 * @param string $label The 'label' (unique id) for this aggregation
844
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
845
	 */
846
	public function add_post_type_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
847
		$builder->add_aggs( $label, array(
848
			'terms' => array(
849
				'field' => 'post_type',
850
				'size' => min( (int) $aggregation['count'], $this->max_aggregations_count ),
851
			),
852
		));
853
	}
854
855
	/**
856
	 * Given an individual date_histogram aggregation, add it to the Jetpack_WPES_Query_Builder object for use in ES
857
	 *
858
	 * @module search
859
	 *
860
	 * @param array $aggregation The aggregation to add to the query builder
861
	 * @param string $label The 'label' (unique id) for this aggregation
862
	 * @param Jetpack_WPES_Query_Builder $builder The builder instance that is creating the ES query
863
	 */
864
	public function add_date_histogram_aggregation_to_es_query_builder( array $aggregation, $label, Jetpack_WPES_Query_Builder $builder ) {
865
		$builder->add_aggs( $label, array(
866
			'date_histogram' => array(
867
				'interval' => $aggregation['interval'],
868
				'field'    => ( ! empty( $aggregation['field'] ) && 'post_date_gmt' == $aggregation['field'] ) ? 'date_gmt' : 'date',
869
			),
870
		));
871
	}
872
873
	/**
874
	 * And an existing filter object with a list of additional filters.
875
	 *
876
	 * Attempts to optimize the filters somewhat.
877
	 *
878
	 * @module search
879
	 *
880
	 * @param array $curr_filter The existing filters to build upon
881
	 * @param array $filters The new filters to add
882
	 *
883
	 * @return array The resulting merged filters
884
	 */
885
	public static function and_es_filters( array $curr_filter, array $filters ) {
886
		if ( ! is_array( $curr_filter ) || isset( $curr_filter['match_all'] ) ) {
887
			if ( 1 === count( $filters ) ) {
888
				return $filters[0];
889
			}
890
891
			return array(
892
				'and' => $filters,
893
			);
894
		}
895
896
		return array(
897
			'and' => array_merge( array( $curr_filter ), $filters ),
898
		);
899
	}
900
901
	/**
902
	 * Add a recency score to a given Jetpack_WPES_Query_Builder object, for emphasizing newer posts in results
903
	 *
904
	 * Internally uses a gauss decay function
905
	 *
906
	 * @module search
907
	 *
908
	 * @param Jetpack_WPES_Query_Builder $builder The Jetpack_WPES_Query_Builder to add the recency score to
909
	 *
910
	 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#function-decay
911
	 */
912
	public static function score_query_by_recency( Jetpack_WPES_Query_Builder &$builder ) {
913
		//Newer content gets weighted slightly higher
914
		$date_scale  = '360d';
915
		$date_decay  = 0.9;
916
		$date_origin = date( 'Y-m-d' );
917
918
		$builder->add_decay( 'gauss', array(
919
			'date_gmt' => array(
920
				'origin' => $date_origin,
921
				'scale'  => $date_scale,
922
				'decay'  => $date_decay,
923
			),
924
		));
925
	}
926
927
	/**
928
	 * Set the available filters for the search
929
	 *
930
	 * These get rendered via the Jetpack_Search_Widget_Filters() widget
931
	 *
932
	 * Behind the scenes, these are implemented using Elasticsearch Aggregations.
933
	 *
934
	 * If you do not require counts of how many documents match each filter, please consider using regular WP Query
935
	 * arguments instead, such as via the jetpack_search_es_wp_query_args filter
936
	 *
937
	 * @module search
938
	 *
939
	 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
940
	 *
941
	 * @param array $aggregations Array of filters (aggregations) to apply to the search
942
	 */
943
	public function set_filters( array $aggregations ) {
944
		$this->aggregations = $aggregations;
945
	}
946
947
	/**
948
	 * Set the search's facets (deprecated)
949
	 *
950
	 * @module search
951
	 *
952
	 * @deprecated 5.0 Please use Jetpack_Search::set_filters() instead
953
	 *
954
	 * @see Jetpack_Search::set_filters()
955
	 *
956
	 * @param array $facets Array of facets to apply to the search
957
	 */
958
	public function set_facets( array $facets ) {
959
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::set_filters()' );
960
961
		$this->set_filters( $facets );
962
	}
963
964
	/**
965
	 * Get the raw Aggregation results from the ES response
966
	 *
967
	 * @module search
968
	 *
969
	 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html
970
	 *
971
	 * @return array Array of Aggregations performed on the search
972
	 */
973
	public function get_search_aggregations_results() {
974
		$aggregations = array();
975
976
		$search_result = $this->get_search_result();
977
978
		if ( ! empty( $search_result ) && ! empty( $search_result['aggregations'] ) ) {
979
			$aggregations = $search_result['aggregations'];
980
		}
981
982
		return $aggregations;
983
	}
984
985
	/**
986
	 * Get the raw Facet results from the ES response
987
	 *
988
	 * @module search
989
	 *
990
	 * @deprecated 5.0 Please use Jetpack_Search::get_search_aggregations_results() instead
991
	 *
992
	 * @see Jetpack_Search::get_search_aggregations_results()
993
	 *
994
	 * @return array Array of Facets performed on the search
995
	 */
996
	public function get_search_facets() {
997
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_search_aggregations_results()' );
998
999
		return $this->get_search_aggregations_results();
1000
	}
1001
1002
	/**
1003
	 * Get the results of the Filters performed, including the number of matching documents
1004
	 *
1005
	 * Returns an array of Filters (keyed by $label, as passed to Jetpack_Search::set_filters()), containing the Filter and all resulting
1006
	 * matching buckets, the url for applying/removing each bucket, etc.
1007
	 *
1008
	 * NOTE - if this is called before the search is performed, an empty array will be returned. Use the $aggregations class
1009
	 * member if you need to access the raw filters set in Jetpack_Search::set_filters()
1010
	 *
1011
	 * @module search
1012
	 *
1013
	 * @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...
1014
	 *
1015
	 * @return array Array of Filters applied and info about them
1016
	 */
1017
	public function get_filters( WP_Query $query = null ) {
1018
		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...
1019
			global $wp_query;
1020
1021
			$query = $wp_query;
1022
		}
1023
1024
		$aggregation_data = $this->aggregations;
1025
1026
		if ( empty( $aggregation_data ) ) {
1027
			return $aggregation_data;
1028
		}
1029
1030
		$aggregation_results = $this->get_search_aggregations_results();
1031
1032
		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...
1033
			return $aggregation_data;
1034
		}
1035
1036
		// NOTE - Looping over the _results_, not the original configured aggregations, so we get the 'real' data from ES
1037
		foreach ( $aggregation_results as $label => $aggregation ) {
1038
			if ( empty( $aggregation ) ) {
1039
				continue;
1040
			}
1041
1042
			$type = $this->aggregations[ $label ]['type'];
1043
1044
			$aggregation_data[ $label ]['buckets'] = array();
1045
1046
			$existing_term_slugs = array();
1047
1048
			$tax_query_var = null;
1049
1050
			// Figure out which terms are active in the query, for this taxonomy
1051
			if ( 'taxonomy' === $this->aggregations[ $label ]['type'] ) {
1052
				$tax_query_var = $this->get_taxonomy_query_var(  $this->aggregations[ $label ]['taxonomy'] );
1053
1054
				if ( ! empty( $query->tax_query ) && ! empty( $query->tax_query->queries ) && is_array( $query->tax_query->queries ) ) {
1055
					foreach( $query->tax_query->queries as $tax_query ) {
1056
						if ( $this->aggregations[ $label ]['taxonomy'] === $tax_query['taxonomy'] &&
1057
						     'slug' === $tax_query['field'] &&
1058
						     is_array( $tax_query['terms'] ) ) {
1059
							$existing_term_slugs = array_merge( $existing_term_slugs, $tax_query['terms'] );
1060
						}
1061
					}
1062
				}
1063
			}
1064
1065
			// Now take the resulting found aggregation items and generate the additional info about them, such as
1066
			// activation/deactivation url, name, count, etc
1067
			$buckets = array();
1068
1069
			if ( ! empty( $aggregation['buckets'] ) ) {
1070
				$buckets = (array) $aggregation['buckets'];
1071
			}
1072
1073
			// Some aggregation types like date_histogram don't support the max results parameter
1074
			if ( is_int( $this->aggregations[ $label ]['count'] ) && count( $buckets ) > $this->aggregations[ $label ]['count'] ) {
1075
				$buckets = array_slice( $buckets, 0, $this->aggregations[ $label ]['count'] );
1076
			}
1077
1078
			foreach ( $buckets as $item ) {
1079
				$query_vars = array();
1080
				$active     = false;
1081
				$remove_url = null;
1082
				$name       = '';
1083
1084
				// What type was the original aggregation?
1085
				switch ( $type ) {
1086
					case 'taxonomy':
1087
						$taxonomy = $this->aggregations[ $label ]['taxonomy'];
1088
1089
						$term = get_term_by( 'slug', $item['key'], $taxonomy );
1090
1091
						if ( ! $term || ! $tax_query_var ) {
1092
							continue 2; // switch() is considered a looping structure
1093
						}
1094
1095
						$query_vars = array(
1096
							$tax_query_var => implode( '+', array_merge( $existing_term_slugs, array( $term->slug ) ) ),
1097
						);
1098
1099
						$name = $term->name;
1100
1101
						// Let's determine if this term is active or not
1102
1103
						if ( in_array( $item['key'], $existing_term_slugs, true ) ) {
1104
							$active = true;
1105
1106
							$slug_count = count( $existing_term_slugs );
1107
1108 View Code Duplication
							if ( $slug_count > 1 ) {
1109
								$remove_url = add_query_arg( $tax_query_var, urlencode( implode( '+', array_diff( $existing_term_slugs, array( $item['key'] ) ) ) ) );
1110
							} else {
1111
								$remove_url = remove_query_arg( $tax_query_var );
1112
							}
1113
						}
1114
1115
						break;
1116
1117
					case 'post_type':
1118
						$post_type = get_post_type_object( $item['key'] );
1119
1120
						if ( ! $post_type || $post_type->exclude_from_search ) {
1121
							continue 2;  // switch() is considered a looping structure
1122
						}
1123
1124
						$query_vars = array(
1125
							'post_type' => $item['key'],
1126
						);
1127
1128
						$name = $post_type->labels->singular_name;
1129
1130
						// Is this post type active on this search?
1131
						$post_types = $query->get( 'post_type' );
1132
1133
						if ( ! is_array( $post_types ) ) {
1134
							$post_types = array( $post_types );
1135
						}
1136
1137
						if ( in_array( $item['key'], $post_types ) ) {
1138
							$active = true;
1139
1140
							$post_type_count = count( $post_types );
1141
1142
							// 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
1143 View Code Duplication
							if ( $post_type_count > 1 ) {
1144
								$remove_url = add_query_arg( 'post_type', urlencode_deep( array_diff( $post_types, array( $item['key'] ) ) ) );
1145
							} else {
1146
								$remove_url = remove_query_arg( 'post_type' );
1147
							}
1148
						}
1149
1150
						break;
1151
1152
					case 'date_histogram':
1153
						$timestamp = $item['key'] / 1000;
1154
1155
						$current_year  = $query->get( 'year' );
1156
						$current_month = $query->get( 'monthnum' );
1157
						$current_day   = $query->get( 'day' );
1158
1159
						switch ( $this->aggregations[ $label ]['interval'] ) {
1160
							case 'year':
1161
								$year = (int) date( 'Y', $timestamp );
1162
1163
								$query_vars = array(
1164
									'year'     => $year,
1165
									'monthnum' => false,
1166
									'day'      => false,
1167
								);
1168
1169
								$name = $year;
1170
1171
								// Is this year currently selected?
1172
								if ( ! empty( $current_year ) && (int) $current_year === $year ) {
1173
									$active = true;
1174
1175
									$remove_url = remove_query_arg( array( 'year', 'monthnum', 'day' ) );
1176
								}
1177
1178
								break;
1179
1180
							case 'month':
1181
								$year  = (int) date( 'Y', $timestamp );
1182
								$month = (int) date( 'n', $timestamp );
1183
1184
								$query_vars = array(
1185
									'year'     => $year,
1186
									'monthnum' => $month,
1187
									'day'      => false,
1188
								);
1189
1190
								$name = date( 'F Y', $timestamp );
1191
1192
								// Is this month currently selected?
1193
								if ( ! empty( $current_year ) && (int) $current_year === $year &&
1194
								     ! empty( $current_month ) && (int) $current_month === $month ) {
1195
									$active = true;
1196
1197
									$remove_url = remove_query_arg( array( 'monthnum', 'day' ) );
1198
								}
1199
1200
								break;
1201
1202
							case 'day':
1203
								$year  = (int) date( 'Y', $timestamp );
1204
								$month = (int) date( 'n', $timestamp );
1205
								$day   = (int) date( 'j', $timestamp );
1206
1207
								$query_vars = array(
1208
									'year'     => $year,
1209
									'monthnum' => $month,
1210
									'day'      => $day,
1211
								);
1212
1213
								$name = date( 'F jS, Y', $timestamp );
1214
1215
								// Is this day currently selected?
1216
								if ( ! empty( $current_year ) && (int) $current_year === $year &&
1217
								     ! empty( $current_month ) && (int) $current_month === $month &&
1218
								     ! empty( $current_day ) && (int) $current_day === $day ) {
1219
									$active = true;
1220
1221
									$remove_url = remove_query_arg( array( 'day' ) );
1222
								}
1223
1224
								break;
1225
1226
							default:
1227
								continue 3; // switch() is considered a looping structure
1228
						} // End switch().
1229
1230
						break;
1231
1232
					default:
1233
						//continue 2; // switch() is considered a looping structure
1234
				} // End switch().
1235
1236
				// Need to urlencode param values since add_query_arg doesn't
1237
				$url_params = urlencode_deep( $query_vars );
1238
1239
				$aggregation_data[ $label ]['buckets'][] = array(
1240
					'url'        => add_query_arg( $url_params ),
1241
					'query_vars' => $query_vars,
1242
					'name'       => $name,
1243
					'count'      => $item['doc_count'],
1244
					'active'     => $active,
1245
					'remove_url' => $remove_url,
1246
					'type'       => $type,
1247
					'type_label' => $label,
1248
				);
1249
			} // End foreach().
1250
		} // End foreach().
1251
1252
		return $aggregation_data;
1253
	}
1254
1255
	/**
1256
	 * Get the results of the Facets performed
1257
	 *
1258
	 * @module search
1259
	 *
1260
	 * @deprecated 5.0 Please use Jetpack_Search::get_filters() instead
1261
	 *
1262
	 * @see Jetpack_Search::get_filters()
1263
	 *
1264
	 * @return array $facets Array of Facets applied and info about them
1265
	 */
1266
	public function get_search_facet_data() {
1267
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_filters()' );
1268
1269
		return $this->get_filters();
1270
	}
1271
1272
	/**
1273
	 * Get the Filters that are currently applied to this search
1274
	 *
1275
	 * @module search
1276
	 *
1277
	 * @return array Array if Filters that were applied
1278
	 */
1279
	public function get_active_filter_buckets() {
1280
		$active_buckets = array();
1281
1282
		$filters = $this->get_filters();
1283
1284
		if ( ! is_array( $filters ) ) {
1285
			return $active_buckets;
1286
		}
1287
1288
		foreach( $filters as $filter ) {
1289
			if ( isset( $filters['buckets'] ) && is_array( $filter['buckets'] ) ) {
1290
				foreach( $filter['buckets'] as $item ) {
1291
					if ( isset( $item['active'] ) && $item['active'] ) {
1292
						$active_buckets[] = $item;
1293
					}
1294
				}
1295
			}
1296
		}
1297
1298
		return $active_buckets;
1299
	}
1300
1301
	/**
1302
	 * Get the Filters that are currently applied to this search
1303
	 *
1304
	 * @module search
1305
	 *
1306
	 * @return array Array if Filters that were applied
1307
	 */
1308
	public function get_current_filters() {
1309
		_deprecated_function( __METHOD__, 'jetpack-5.0', 'Jetpack_Search::get_active_filter_buckets()' );
1310
1311
		return $this->get_active_filter_buckets();
1312
	}
1313
1314
	/**
1315
	 * Calculate the right query var to use for a given taxonomy
1316
	 *
1317
	 * Allows custom code to modify the GET var that is used to represent a given taxonomy, via the jetpack_search_taxonomy_query_var filter
1318
	 *
1319
	 * @module search
1320
	 *
1321
	 * @param string $taxonomy_name The name of the taxonomy for which to get the query var
1322
	 *
1323
	 * @return bool|string The query var to use for this taxonomy, or false if none found
1324
	 */
1325
	public function get_taxonomy_query_var( $taxonomy_name ) {
1326
		$taxonomy = get_taxonomy( $taxonomy_name );
1327
1328
		if ( ! $taxonomy || is_wp_error( $taxonomy ) ) {
1329
			return false;
1330
		}
1331
1332
		/**
1333
		 * Modify the query var to use for a given taxonomy
1334
		 *
1335
		 * @module search
1336
		 *
1337
		 * @since 5.0.0
1338
		 *
1339
		 * @param string $query_var The current query_var for the taxonomy
1340
		 * @param string $taxonomy_name The taxonomy name
1341
		 */
1342
		return apply_filters( 'jetpack_search_taxonomy_query_var', $taxonomy->query_var, $taxonomy_name );
1343
	}
1344
1345
	/**
1346
	 * Takes an array of aggregation results, and ensures the array key ordering matches the key order in $desired
1347
	 * which is the input order
1348
	 *
1349
	 * Necessary because ES does not always return Aggs in the same order that you pass them in, and it should be possible
1350
	 * to control the display order easily
1351
	 *
1352
	 * @module search
1353
	 *
1354
	 * @param array $aggregations Agg results to be reordered
1355
	 * @param array $desired Array with keys representing the desired ordering
1356
	 *
1357
	 * @return array A new array with reordered keys, matching those in $desired
1358
	 */
1359
	public function fix_aggregation_ordering( array $aggregations, array $desired ) {
1360
		if ( empty( $aggregations ) || empty( $desired ) ) {
1361
			return $aggregations;
1362
		}
1363
1364
		$reordered = array();
1365
1366
		foreach( array_keys( $desired ) as $agg_name ) {
1367
			if ( isset( $aggregations[ $agg_name ] ) ) {
1368
				$reordered[ $agg_name ] = $aggregations[ $agg_name ];
1369
			}
1370
		}
1371
1372
		return $reordered;
1373
	}
1374
}
1375