Completed
Push — update/refactor-search-php ( d6a69c )
by
unknown
07:06
created

Jetpack_Search_Helpers::site_has_vip_index()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 17
rs 9.7
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A Jetpack_Search_Helpers::get_max_offset() 0 3 2
1
<?php
2
/**
3
 * Jetpack Search: Jetpack_Search_Helpers class
4
 *
5
 * @package    Jetpack
6
 * @subpackage Jetpack Search
7
 * @since      5.8.0
8
 */
9
10
use Automattic\Jetpack\Constants;
11
12
/**
13
 * Various helper functions for reuse throughout the Jetpack Search code.
14
 *
15
 * @since 5.8.0
16
 */
17
class Jetpack_Search_Helpers {
18
19
	/**
20
	 * The search widget's base ID.
21
	 *
22
	 * @since 5.8.0
23
	 * @var string
24
	 */
25
	const FILTER_WIDGET_BASE = 'jetpack-search-filters';
26
27
	/**
28
	 * Create a URL for the current search that doesn't include the "paged" parameter.
29
	 *
30
	 * @since 5.8.0
31
	 *
32
	 * @return string The search URL.
33
	 */
34
	static function get_search_url() {
35
		$query_args = stripslashes_deep( $_GET );
36
37
		// Handle the case where a permastruct is being used, such as /search/{$query}
38
		if ( ! isset( $query_args['s'] ) ) {
39
			$query_args['s'] = get_search_query();
40
		}
41
42
		if ( isset( $query_args['paged'] ) ) {
43
			unset( $query_args['paged'] );
44
		}
45
46
		$query = http_build_query( $query_args );
47
48
		return home_url( "?{$query}" );
49
	}
50
51
	/**
52
	 * Wraps add_query_arg() with the URL defaulting to the current search URL.
53
	 *
54
	 * @see   add_query_arg()
55
	 *
56
	 * @since 5.8.0
57
	 *
58
	 * @param string|array $key   Either a query variable key, or an associative array of query variables.
59
	 * @param string       $value Optional. A query variable value.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $value not be false|string?

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...
60
	 * @param bool|string  $url   Optional. A URL to act upon. Defaults to the current search URL.
61
	 *
62
	 * @return string New URL query string (unescaped).
63
	 */
64
	static function add_query_arg( $key, $value = false, $url = false ) {
65
		$url = empty( $url ) ? self::get_search_url() : $url;
66
		if ( is_array( $key ) ) {
67
			return add_query_arg( $key, $url );
68
		}
69
70
		return add_query_arg( $key, $value, $url );
71
	}
72
73
	/**
74
	 * Wraps remove_query_arg() with the URL defaulting to the current search URL.
75
	 *
76
	 * @see   remove_query_arg()
77
	 *
78
	 * @since 5.8.0
79
	 *
80
	 * @param string|array $key   Query key or keys to remove.
81
	 * @param bool|string  $query Optional. A URL to act upon.  Defaults to the current search URL.
0 ignored issues
show
Bug introduced by
There is no parameter named $query. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
82
	 *
83
	 * @return string New URL query string (unescaped).
84
	 */
85
	static function remove_query_arg( $key, $url = false ) {
86
		$url = empty( $url ) ? self::get_search_url() : $url;
87
88
		return remove_query_arg( $key, $url );
89
	}
90
91
	/**
92
	 * Returns the name of the search widget's option.
93
	 *
94
	 * @since 5.8.0
95
	 *
96
	 * @return string The search widget option name.
97
	 */
98
	static function get_widget_option_name() {
99
		return sprintf( 'widget_%s', self::FILTER_WIDGET_BASE );
100
	}
101
102
	/**
103
	 * Returns the search widget instances from the widget's option.
104
	 *
105
	 * @since 5.8.0
106
	 *
107
	 * @return array The widget options.
108
	 */
109
	static function get_widgets_from_option() {
110
		$widget_options = get_option( self::get_widget_option_name(), array() );
111
112
		// We don't need this
113
		if ( ! empty( $widget_options ) && isset( $widget_options['_multiwidget'] ) ) {
114
			unset( $widget_options['_multiwidget'] );
115
		}
116
117
		return $widget_options;
118
	}
119
120
	/**
121
	 * Returns the widget ID (widget base plus the numeric ID).
122
	 *
123
	 * @param int $number The widget's numeric ID.
124
	 *
125
	 * @return string The widget's numeric ID prefixed with the search widget base.
126
	 */
127
	static function build_widget_id( $number ) {
128
		return sprintf( '%s-%d', self::FILTER_WIDGET_BASE, $number );
129
	}
130
131
	/**
132
	 * Wrapper for is_active_widget() with the other parameters automatically supplied.
133
	 *
134
	 * @see   is_active_widget()
135
	 *
136
	 * @since 5.8.0
137
	 *
138
	 * @param int $widget_id Widget ID.
139
	 *
140
	 * @return bool Whether the widget is active or not.
141
	 */
142
	static function is_active_widget( $widget_id ) {
143
		return (bool) is_active_widget( false, $widget_id, self::FILTER_WIDGET_BASE, true );
144
	}
145
146
	/**
147
	 * Returns an array of the filters from all active search widgets.
148
	 *
149
	 * @since 5.8.0
150
	 *
151
	 * @return array Active filters.
152
	 */
153
	static function get_filters_from_widgets() {
154
		$filters = array();
155
156
		$widget_options = self::get_widgets_from_option();
157
		if ( empty( $widget_options ) ) {
158
			return $filters;
159
		}
160
161
		foreach ( (array) $widget_options as $number => $settings ) {
162
			$widget_id = self::build_widget_id( $number );
163
			if ( ! self::is_active_widget( $widget_id ) || empty( $settings['filters'] ) ) {
164
				continue;
165
			}
166
167
			foreach ( (array) $settings['filters'] as $widget_filter ) {
168
				$widget_filter['widget_id'] = $widget_id;
169
170
				if ( empty( $widget_filter['name'] ) ) {
171
					$widget_filter['name'] = self::generate_widget_filter_name( $widget_filter );
172
				}
173
174
				$key = sprintf( '%s_%d', $widget_filter['type'], count( $filters ) );
175
176
				$filters[ $key ] = $widget_filter;
177
			}
178
		}
179
180
		return $filters;
181
	}
182
183
	/**
184
	 * Get the localized default label for a date filter.
185
	 *
186
	 * @since 5.8.0
187
	 *
188
	 * @param string $type       Date type, either year or month.
189
	 * @param bool   $is_updated Whether the filter was updated or not (adds "Updated" to the end).
190
	 *
191
	 * @return string The filter label.
192
	 */
193
	static function get_date_filter_type_name( $type, $is_updated = false ) {
194
		switch ( $type ) {
195 View Code Duplication
			case 'year':
196
				$string = ( $is_updated )
197
					? esc_html_x( 'Year Updated', 'label for filtering posts', 'jetpack' )
198
					: esc_html_x( 'Year', 'label for filtering posts', 'jetpack' );
199
				break;
200
			case 'month':
201 View Code Duplication
			default:
202
				$string = ( $is_updated )
203
					? esc_html_x( 'Month Updated', 'label for filtering posts', 'jetpack' )
204
					: esc_html_x( 'Month', 'label for filtering posts', 'jetpack' );
205
				break;
206
		}
207
208
		return $string;
209
	}
210
211
	/**
212
	 * Creates a default name for a filter. Used when the filter label is blank.
213
	 *
214
	 * @since 5.8.0
215
	 *
216
	 * @param array $widget_filter The filter to generate the title for.
217
	 *
218
	 * @return string The suggested filter name.
219
	 */
220
	static function generate_widget_filter_name( $widget_filter ) {
221
		$name = '';
222
223
		switch ( $widget_filter['type'] ) {
224
			case 'post_type':
225
				$name = _x( 'Post Types', 'label for filtering posts', 'jetpack' );
226
				break;
227
228
			case 'date_histogram':
229
				$modified_fields = array(
230
					'post_modified',
231
					'post_modified_gmt',
232
				);
233
				switch ( $widget_filter['interval'] ) {
234
					case 'year':
235
						$name = self::get_date_filter_type_name(
236
							'year',
237
							in_array( $widget_filter['field'], $modified_fields )
238
						);
239
						break;
240
					case 'month':
241
					default:
242
						$name = self::get_date_filter_type_name(
243
							'month',
244
							in_array( $widget_filter['field'], $modified_fields )
245
						);
246
						break;
247
				}
248
				break;
249
250
			case 'taxonomy':
251
				$tax = get_taxonomy( $widget_filter['taxonomy'] );
252
				if ( ! $tax ) {
253
					break;
254
				}
255
256
				if ( isset( $tax->label ) ) {
257
					$name = $tax->label;
258
				} elseif ( isset( $tax->labels ) && isset( $tax->labels->name ) ) {
259
					$name = $tax->labels->name;
260
				}
261
				break;
262
		}
263
264
		return $name;
265
	}
266
267
	/**
268
	 * Whether we should rerun a search in the customizer preview or not.
269
	 *
270
	 * @since 5.8.0
271
	 *
272
	 * @return bool
273
	 */
274
	static function should_rerun_search_in_customizer_preview() {
275
		// Only update when in a customizer preview and data is being posted.
276
		// Check for $_POST removes an extra update when the customizer loads.
277
		//
278
		// Note: We use $GLOBALS['wp_customize'] here instead of is_customize_preview() to support unit tests.
279
		if ( ! isset( $GLOBALS['wp_customize'] ) || ! $GLOBALS['wp_customize']->is_preview() || empty( $_POST ) ) {
280
			return false;
281
		}
282
283
		return true;
284
	}
285
286
	/**
287
	 * Since PHP's built-in array_diff() works by comparing the values that are in array 1 to the other arrays,
288
	 * if there are less values in array 1, it's possible to get an empty diff where one might be expected.
289
	 *
290
	 * @since 5.8.0
291
	 *
292
	 * @param array $array_1
293
	 * @param array $array_2
294
	 *
295
	 * @return array
296
	 */
297
	static function array_diff( $array_1, $array_2 ) {
298
		// If the array counts are the same, then the order doesn't matter. If the count of
299
		// $array_1 is higher than $array_2, that's also fine. If the count of $array_2 is higher,
300
		// we need to swap the array order though.
301
		if ( count( $array_1 ) !== count( $array_2 ) && count( $array_2 ) > count( $array_1 ) ) {
302
			$temp    = $array_1;
303
			$array_1 = $array_2;
304
			$array_2 = $temp;
305
		}
306
307
		// Disregard keys
308
		return array_values( array_diff( $array_1, $array_2 ) );
309
	}
310
311
	/**
312
	 * Given the widget instance, will return true when selected post types differ from searchable post types.
313
	 *
314
	 * @since 5.8.0
315
	 *
316
	 * @param array $post_types An array of post types.
317
	 *
318
	 * @return bool
319
	 */
320
	static function post_types_differ_searchable( $post_types ) {
321
		if ( empty( $post_types ) ) {
322
			return false;
323
		}
324
325
		$searchable_post_types = get_post_types( array( 'exclude_from_search' => false ) );
326
		$diff_of_searchable    = self::array_diff( $searchable_post_types, (array) $post_types );
327
328
		return ! empty( $diff_of_searchable );
329
	}
330
331
	/**
332
	 * Given the array of post types, will return true when these differ from the current search query.
333
	 *
334
	 * @since 5.8.0
335
	 *
336
	 * @param array $post_types An array of post types.
337
	 *
338
	 * @return bool
339
	 */
340
	static function post_types_differ_query( $post_types ) {
341
		if ( empty( $post_types ) ) {
342
			return false;
343
		}
344
345
		if ( empty( $_GET['post_type'] ) ) {
346
			$post_types_from_query = array();
347
		} elseif ( is_array( $_GET['post_type'] ) ) {
348
			$post_types_from_query = $_GET['post_type'];
349
		} else {
350
			$post_types_from_query = (array) explode( ',', $_GET['post_type'] );
351
		}
352
353
		$post_types_from_query = array_map( 'trim', $post_types_from_query );
354
355
		$diff_query = self::array_diff( (array) $post_types, $post_types_from_query );
356
357
		return ! empty( $diff_query );
358
	}
359
360
	/**
361
	 * Determine what Tracks value should be used when updating a widget.
362
	 *
363
	 * @since 5.8.0
364
	 *
365
	 * @param mixed $old_value The old option value.
366
	 * @param mixed $new_value The new option value.
367
	 *
368
	 * @return array|false False if the widget wasn't updated, otherwise an array of the Tracks action and widget properties.
369
	 */
370
	static function get_widget_tracks_value( $old_value, $new_value ) {
371
		$old_value = (array) $old_value;
372
		if ( isset( $old_value['_multiwidget'] ) ) {
373
			unset( $old_value['_multiwidget'] );
374
		}
375
376
		$new_value = (array) $new_value;
377
		if ( isset( $new_value['_multiwidget'] ) ) {
378
			unset( $new_value['_multiwidget'] );
379
		}
380
381
		$old_keys = array_keys( $old_value );
382
		$new_keys = array_keys( $new_value );
383
384
		if ( count( $new_keys ) > count( $old_keys ) ) { // This is the case for a widget being added
385
			$diff   = self::array_diff( $new_keys, $old_keys );
386
			$action = 'widget_added';
387
			$widget = empty( $diff ) || ! isset( $new_value[ $diff[0] ] )
388
				? false
389
				: $new_value[ $diff[0] ];
390
		} elseif ( count( $old_keys ) > count( $new_keys ) ) { // This is the case for a widget being deleted
391
			$diff   = self::array_diff( $old_keys, $new_keys );
392
			$action = 'widget_deleted';
393
			$widget = empty( $diff ) || ! isset( $old_value[ $diff[0] ] )
394
				? false
395
				: $old_value[ $diff[0] ];
396
		} else {
397
			$action = 'widget_updated';
398
			$widget = false;
399
400
			// This is a bit crazy. Since there can be multiple widgets stored in a single option,
401
			// we need to diff the old and new values to figure out which widget was updated.
402
			foreach ( $new_value as $key => $new_instance ) {
403
				if ( ! isset( $old_value[ $key ] ) ) {
404
					continue;
405
				}
406
				$old_instance = $old_value[ $key ];
407
408
				// First, let's test the keys of each instance
409
				$diff = self::array_diff( array_keys( $new_instance ), array_keys( $old_instance ) );
410
				if ( ! empty( $diff ) ) {
411
					$widget = $new_instance;
412
					break;
413
				}
414
415
				// Next, lets's loop over each value and compare it
416
				foreach ( $new_instance as $k => $v ) {
417
					if ( is_scalar( $v ) && (string) $v !== (string) $old_instance[ $k ] ) {
418
						$widget = $new_instance;
419
						break;
420
					}
421
422
					if ( 'filters' == $k ) {
423
						if ( count( $new_instance['filters'] ) != count( $old_instance['filters'] ) ) {
424
							$widget = $new_instance;
425
							break;
426
						}
427
428
						foreach ( $v as $filter_key => $new_filter_value ) {
429
							$diff = self::array_diff( $new_filter_value, $old_instance['filters'][ $filter_key ] );
430
							if ( ! empty( $diff ) ) {
431
								$widget = $new_instance;
432
								break;
433
							}
434
						}
435
					}
436
				}
437
			}
438
		}
439
440
		if ( empty( $action ) || empty( $widget ) ) {
441
			return false;
442
		}
443
444
		return array(
445
			'action' => $action,
446
			'widget' => self::get_widget_properties_for_tracks( $widget ),
447
		);
448
	}
449
450
	/**
451
	 * Creates the widget properties for sending to Tracks.
452
	 *
453
	 * @since 5.8.0
454
	 *
455
	 * @param array $widget The widget instance.
456
	 *
457
	 * @return array The widget properties.
458
	 */
459
	static function get_widget_properties_for_tracks( $widget ) {
460
		$sanitized = array();
461
462
		foreach ( (array) $widget as $key => $value ) {
463
			if ( '_multiwidget' == $key ) {
464
				continue;
465
			}
466
467
			if ( is_scalar( $value ) ) {
468
				$key               = str_replace( '-', '_', sanitize_key( $key ) );
469
				$key               = "widget_{$key}";
470
				$sanitized[ $key ] = $value;
471
			}
472
		}
473
474
		$filters_properties = ! empty( $widget['filters'] )
475
			? self::get_filter_properties_for_tracks( $widget['filters'] )
476
			: array();
477
478
		return array_merge( $sanitized, $filters_properties );
479
	}
480
481
	/**
482
	 * Creates the filter properties for sending to Tracks.
483
	 *
484
	 * @since 5.8.0
485
	 *
486
	 * @param array $filters An array of filters.
487
	 *
488
	 * @return array The filter properties.
489
	 */
490
	static function get_filter_properties_for_tracks( $filters ) {
491
		if ( empty( $filters ) ) {
492
			return $filters;
493
		}
494
495
		$filters_properties = array(
496
			'widget_filter_count' => count( $filters ),
497
		);
498
499
		foreach ( $filters as $filter ) {
500
			if ( empty( $filter['type'] ) ) {
501
				continue;
502
			}
503
504
			$key = sprintf( 'widget_filter_type_%s', $filter['type'] );
505
			if ( isset( $filters_properties[ $key ] ) ) {
506
				$filters_properties[ $key ] ++;
507
			} else {
508
				$filters_properties[ $key ] = 1;
509
			}
510
		}
511
512
		return $filters_properties;
513
	}
514
515
	/**
516
	 * Gets the active post types given a set of filters.
517
	 *
518
	 * @since 5.8.0
519
	 *
520
	 * @param array $filters The active filters for the current query.
521
	 *
522
	 * @return array The active post types.
523
	 */
524
	public static function get_active_post_types( $filters ) {
525
		$active_post_types = array();
526
527
		foreach ( $filters as $item ) {
528
			if ( ( 'post_type' == $item['type'] ) && isset( $item['query_vars']['post_type'] ) ) {
529
				$active_post_types[] = $item['query_vars']['post_type'];
530
			}
531
		}
532
533
		return $active_post_types;
534
	}
535
536
	/**
537
	 * Sets active to false on all post type buckets.
538
	 *
539
	 * @since 5.8.0
540
	 *
541
	 * @param array $filters The available filters for the current query.
542
	 *
543
	 * @return array The filters for the current query with modified active field.
544
	 */
545
	public static function remove_active_from_post_type_buckets( $filters ) {
546
		$modified = $filters;
547
		foreach ( $filters as $key => $filter ) {
548
			if ( 'post_type' === $filter['type'] && ! empty( $filter['buckets'] ) ) {
549
				foreach ( $filter['buckets'] as $k => $bucket ) {
550
					$bucket['active']                  = false;
551
					$modified[ $key ]['buckets'][ $k ] = $bucket;
552
				}
553
			}
554
		}
555
556
		return $modified;
557
	}
558
559
	/**
560
	 * Given a url and an array of post types, will ensure that the post types are properly applied to the URL as args.
561
	 *
562
	 * @since 5.8.0
563
	 *
564
	 * @param string $url        The URL to add post types to.
565
	 * @param array  $post_types An array of post types that should be added to the URL.
566
	 *
567
	 * @return string The URL with added post types.
568
	 */
569
	public static function add_post_types_to_url( $url, $post_types ) {
570
		$url = Jetpack_Search_Helpers::remove_query_arg( 'post_type', $url );
0 ignored issues
show
Documentation introduced by
$url is of type string, but the function expects a boolean.

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...
571
		if ( empty( $post_types ) ) {
572
			return $url;
573
		}
574
575
		$url = Jetpack_Search_Helpers::add_query_arg(
576
			'post_type',
577
			implode( ',', $post_types ),
578
			$url
579
		);
580
581
		return $url;
582
	}
583
584
	/**
585
	 * Since we provide support for the widget restricting post types by adding the selected post types as
586
	 * active filters, if removing a post type filter would result in there no longer be post_type args in the URL,
587
	 * we need to be sure to add them back.
588
	 *
589
	 * @since 5.8.0
590
	 *
591
	 * @param array $filters    An array of possible filters for the current query.
592
	 * @param array $post_types The post types to ensure are on the link.
593
	 *
594
	 * @return array The updated array of filters with post typed added to the remove URLs.
595
	 */
596
	public static function ensure_post_types_on_remove_url( $filters, $post_types ) {
597
		$modified = $filters;
598
599
		foreach ( (array) $filters as $filter_key => $filter ) {
600
			if ( 'post_type' !== $filter['type'] || empty( $filter['buckets'] ) ) {
601
				$modified[ $filter_key ] = $filter;
602
				continue;
603
			}
604
605
			foreach ( (array) $filter['buckets'] as $bucket_key => $bucket ) {
606
				if ( empty( $bucket['remove_url'] ) ) {
607
					continue;
608
				}
609
610
				$parsed = wp_parse_url( $bucket['remove_url'] );
611
				if ( ! $parsed ) {
612
					continue;
613
				}
614
615
				$query = array();
616
				if ( ! empty( $parsed['query'] ) ) {
617
					wp_parse_str( $parsed['query'], $query );
618
				}
619
620
				if ( empty( $query['post_type'] ) ) {
621
					$modified[ $filter_key ]['buckets'][ $bucket_key ]['remove_url'] = self::add_post_types_to_url(
622
						$bucket['remove_url'],
623
						$post_types
624
					);
625
				}
626
			}
627
		}
628
629
		return $modified;
630
	}
631
632
	/**
633
	 * Wraps a WordPress filter called "jetpack_search_disable_widget_filters" that allows
634
	 * developers to disable filters supplied by the search widget. Useful if filters are
635
	 * being defined at the code level.
636
	 *
637
	 * @since 5.8.0
638
	 *
639
	 * @return bool
640
	 */
641
	public static function are_filters_by_widget_disabled() {
642
		/**
643
		 * Allows developers to disable filters being set by widget, in favor of manually
644
		 * setting filters via `Jetpack_Search::set_filters()`.
645
		 *
646
		 * @module search
647
		 *
648
		 * @since  5.7.0
649
		 *
650
		 * @param bool false
651
		 */
652
		return apply_filters( 'jetpack_search_disable_widget_filters', false );
653
	}
654
655
	/**
656
	 * Returns the maximum posts per page for a search query.
657
	 *
658
	 * @since 5.8.0
659
	 *
660
	 * @return int
661
	 */
662
	public static function get_max_posts_per_page() {
663
		return self::site_has_vip_index() ? 1000 : 100;
0 ignored issues
show
Bug introduced by
The method site_has_vip_index() does not seem to exist on object<Jetpack_Search_Helpers>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
664
	}
665
666
	/**
667
	 * Returns the maximum offset for a search query.
668
	 *
669
	 * @since 5.8.0
670
	 *
671
	 * @return int
672
	 */
673
	public static function get_max_offset() {
674
		return self::site_has_vip_index() ? 9000 : 1000;
0 ignored issues
show
Bug introduced by
The method site_has_vip_index() does not seem to exist on object<Jetpack_Search_Helpers>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
675
	}
676
}
677