Completed
Push — improve/rename-anti-spam-in-si... ( e5a567...f08131 )
by
unknown
75:27 queued 67:29
created

load_and_initialize_tracks()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Jetpack Search: Instant Front-End Search and Filtering
4
 *
5
 * @since 8.3.0
6
 * @package jetpack
7
 */
8
9
use Automattic\Jetpack\Connection\Client;
10
use Automattic\Jetpack\Constants;
11
12
/**
13
 * Class to load Instant Search experience on the site.
14
 *
15
 * @since 8.3.0
16
 */
17
class Jetpack_Instant_Search extends Jetpack_Search {
18
19
	/**
20
	 * Loads the php for this version of search
21
	 *
22
	 * @since 8.3.0
23
	 */
24
	public function load_php() {
25
		$this->base_load_php();
26
27
		if ( class_exists( 'WP_Customize_Manager' ) ) {
28
			require_once dirname( __FILE__ ) . '/class-jetpack-search-customize.php';
29
			new Jetpack_Search_Customize();
30
		}
31
	}
32
33
	/**
34
	 * Setup the various hooks needed for the plugin to take over search duties.
35
	 *
36
	 * @since 5.0.0
37
	 */
38 View Code Duplication
	public function init_hooks() {
39
		if ( ! is_admin() ) {
40
			add_filter( 'posts_pre_query', array( $this, 'filter__posts_pre_query' ), 10, 2 );
41
			add_action( 'parse_query', array( $this, 'action__parse_query' ), 10, 1 );
42
43
			add_action( 'init', array( $this, 'set_filters_from_widgets' ) );
44
45
			add_action( 'wp_enqueue_scripts', array( $this, 'load_assets' ) );
46
			add_action( 'wp_footer', array( $this, 'print_instant_search_sidebar' ) );
47
		} else {
48
			add_action( 'update_option', array( $this, 'track_widget_updates' ), 10, 3 );
49
		}
50
51
		add_action( 'widgets_init', array( $this, 'register_jetpack_instant_sidebar' ) );
52
		add_action( 'jetpack_deactivate_module_search', array( $this, 'move_search_widgets_to_inactive' ) );
53
	}
54
55
	/**
56
	 * Loads assets for Jetpack Instant Search Prototype featuring Search As You Type experience.
57
	 */
58
	public function load_assets() {
59
		$script_relative_path = '_inc/build/instant-search/jp-search.bundle.js';
60
		$style_relative_path  = '_inc/build/instant-search/instant-search.min.css';
61
		if ( ! file_exists( JETPACK__PLUGIN_DIR . $script_relative_path ) || ! file_exists( JETPACK__PLUGIN_DIR . $style_relative_path ) ) {
62
			return;
63
		}
64
65
		$script_version = Jetpack_Search_Helpers::get_asset_version( $script_relative_path );
66
		$script_path    = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
67
		wp_enqueue_script( 'jetpack-instant-search', $script_path, array(), $script_version, true );
68
		wp_set_script_translations( 'jetpack-instant-search', 'jetpack' );
69
		$this->load_and_initialize_tracks();
70
		$this->inject_javascript_options();
71
72
		$style_version = Jetpack_Search_Helpers::get_asset_version( $style_relative_path );
73
		$style_path    = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
74
		wp_enqueue_style( 'jetpack-instant-search', $style_path, array(), $style_version );
75
	}
76
77
	/**
78
	 * Passes all options to the JS app.
79
	 */
80
	protected function inject_javascript_options() {
81
		$widget_options = Jetpack_Search_Helpers::get_widgets_from_option();
82
		if ( is_array( $widget_options ) ) {
83
			$widget_options = end( $widget_options );
0 ignored issues
show
Unused Code introduced by
$widget_options 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...
84
		}
85
86
		$overlay_widget_ids      = is_active_sidebar( 'jetpack-instant-search-sidebar' ) ?
87
			wp_get_sidebars_widgets()['jetpack-instant-search-sidebar'] : array();
88
		$filters                 = Jetpack_Search_Helpers::get_filters_from_widgets();
89
		$widgets                 = array();
90
		$widgets_outside_overlay = array();
91
		foreach ( $filters as $key => &$filter ) {
92
			$filter['filter_id'] = $key;
93
94
			if ( in_array( $filter['widget_id'], $overlay_widget_ids, true ) ) {
95 View Code Duplication
				if ( ! isset( $widgets[ $filter['widget_id'] ] ) ) {
96
					$widgets[ $filter['widget_id'] ]['filters']   = array();
97
					$widgets[ $filter['widget_id'] ]['widget_id'] = $filter['widget_id'];
98
				}
99
				$widgets[ $filter['widget_id'] ]['filters'][] = $filter;
100
			} else {
101 View Code Duplication
				if ( ! isset( $widgets_outside_overlay[ $filter['widget_id'] ] ) ) {
102
					$widgets_outside_overlay[ $filter['widget_id'] ]['filters']   = array();
103
					$widgets_outside_overlay[ $filter['widget_id'] ]['widget_id'] = $filter['widget_id'];
104
				}
105
				$widgets_outside_overlay[ $filter['widget_id'] ]['filters'][] = $filter;
106
			}
107
		}
108
		unset( $filter );
109
110
		$post_type_objs   = get_post_types( array( 'exclude_from_search' => false ), 'objects' );
111
		$post_type_labels = array();
112
		foreach ( $post_type_objs as $key => $obj ) {
113
			$post_type_labels[ $key ] = array(
114
				'singular_name' => $obj->labels->singular_name,
115
				'name'          => $obj->labels->name,
116
			);
117
		}
118
119
		$prefix         = Jetpack_Search_Options::OPTION_PREFIX;
120
		$posts_per_page = (int) get_option( 'posts_per_page' );
121
		if ( ( $posts_per_page > 20 ) || ( $posts_per_page <= 0 ) ) {
122
			$posts_per_page = 20;
123
		}
124
125
		$options = array(
126
			'overlayOptions'        => array(
127
				'colorTheme'      => get_option( $prefix . 'color_theme', 'light' ),
128
				'enableInfScroll' => get_option( $prefix . 'inf_scroll', '1' ) === '1',
129
				'enableSort'      => get_option( $prefix . 'enable_sort', '1' ) === '1',
130
				'highlightColor'  => get_option( $prefix . 'highlight_color', '#FFC' ),
131
				'opacity'         => (int) get_option( $prefix . 'opacity', 97 ),
132
				'overlayTrigger'  => get_option( $prefix . 'overlay_trigger', 'immediate' ),
133
				'resultFormat'    => get_option( $prefix . 'result_format', 'minimal' ),
134
				'showPoweredBy'   => get_option( $prefix . 'show_powered_by', '1' ) === '1',
135
			),
136
137
			// core config.
138
			'homeUrl'               => home_url(),
139
			'locale'                => str_replace( '_', '-', Jetpack_Search_Helpers::is_valid_locale( get_locale() ) ? get_locale() : 'en_US' ),
140
			'postsPerPage'          => $posts_per_page,
141
			'siteId'                => $this->jetpack_blog_id,
142
			'postTypes'             => $post_type_labels,
143
144
			// search options.
145
			'defaultSort'           => get_option( $prefix . 'default_sort', 'relevance' ),
146
			'excludedPostTypes'     => explode( ',', get_option( $prefix . 'excluded_post_types', '' ) ),
147
148
			// widget info.
149
			'hasOverlayWidgets'     => count( $overlay_widget_ids ) > 0,
150
			'widgets'               => array_values( $widgets ),
151
			'widgetsOutsideOverlay' => array_values( $widgets_outside_overlay ),
152
		);
153
154
		/**
155
		 * Customize Instant Search Options.
156
		 *
157
		 * @module search
158
		 *
159
		 * @since 7.7.0
160
		 *
161
		 * @param array $options Array of parameters used in Instant Search queries.
162
		 */
163
		$options = apply_filters( 'jetpack_instant_search_options', $options );
164
165
		// Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
166
		wp_add_inline_script( 'jetpack-instant-search', 'var JetpackInstantSearchOptions=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( $options ) ) . '"));' );
167
	}
168
169
	/**
170
	 * Registers a widget sidebar for Instant Search.
171
	 */
172
	public function register_jetpack_instant_sidebar() {
173
		$args = array(
174
			'name'          => __( 'Jetpack Search Sidebar', 'jetpack' ),
175
			'id'            => 'jetpack-instant-search-sidebar',
176
			'description'   => __( 'Customize the sidebar inside the Jetpack Search overlay', 'jetpack' ),
177
			'class'         => '',
178
			'before_widget' => '<div id="%1$s" class="widget %2$s">',
179
			'after_widget'  => '</div>',
180
			'before_title'  => '<h2 class="widgettitle">',
181
			'after_title'   => '</h2>',
182
		);
183
		register_sidebar( $args );
184
	}
185
186
	/**
187
	 * Prints Instant Search sidebar.
188
	 */
189
	public function print_instant_search_sidebar() {
190
		?>
191
		<div class="jetpack-instant-search__widget-area" style="display: none">
192
			<?php if ( is_active_sidebar( 'jetpack-instant-search-sidebar' ) ) { ?>
193
				<?php dynamic_sidebar( 'jetpack-instant-search-sidebar' ); ?>
194
			<?php } ?>
195
		</div>
196
		<?php
197
	}
198
199
	/**
200
	 * Loads scripts for Tracks analytics library
201
	 */
202
	public function load_and_initialize_tracks() {
203
		wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
204
	}
205
206
	/**
207
	 * Bypass the normal Search query since we will run it with instant search.
208
	 *
209
	 * @since 8.3.0
210
	 *
211
	 * @param array    $posts Current array of posts (still pre-query).
212
	 * @param WP_Query $query The WP_Query being filtered.
213
	 *
214
	 * @return array Array of matching posts.
215
	 */
216
	public function filter__posts_pre_query( $posts, $query ) {
217
		if ( ! $this->should_handle_query( $query ) ) {
218
			// Intentionally not adding the 'jetpack_search_abort' action since this should fire for every request except for search.
219
			return $posts;
220
		}
221
222
		/**
223
		 * Bypass the main query and return dummy data
224
		 *  WP Core doesn't call the set_found_posts and its filters when filtering
225
		 *  posts_pre_query like we do, so need to do these manually.
226
		 */
227
		$query->found_posts   = 1;
228
		$query->max_num_pages = 1;
229
230
		return array();
231
	}
232
233
	/**
234
	 * Run the aggregations API query for any filtering
235
	 *
236
	 * @since 8.3.0
237
	 *
238
	 * @param WP_Query $query The WP_Query being filtered.
239
	 */
240
	public function action__parse_query( $query ) {
241
		if ( ! empty( $this->search_result ) ) {
242
			return;
243
		}
244
245
		if ( is_admin() ) {
246
			return;
247
		}
248
249
		if ( empty( $this->aggregations ) ) {
250
			return;
251
		}
252
253
		jetpack_require_lib( 'jetpack-wpes-query-builder/jetpack-wpes-query-builder' );
254
255
		$builder = new Jetpack_WPES_Query_Builder();
256
		$this->add_aggregations_to_es_query_builder( $this->aggregations, $builder );
257
		$this->search_result = $this->instant_api(
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->instant_api(array...ze' => 0, 'from' => 0)) of type object is incompatible with the declared type array of property $search_result.

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

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

Loading history...
258
			array(
259
				'aggregations' => $builder->build_aggregation(),
260
				'size'         => 0,
261
				'from'         => 0,
262
			)
263
		);
264
	}
265
266
	/**
267
	 * Run an instant search on the WordPress.com public API.
268
	 *
269
	 * @since 8.3.0
270
	 *
271
	 * @param array $args Args conforming to the WP.com v1.3/sites/<blog_id>/search endpoint.
272
	 *
273
	 * @return object|WP_Error The response from the public API, or a WP_Error.
274
	 */
275
	public function instant_api( array $args ) {
276
		global $wp_version;
277
		$start_time = microtime( true );
278
279
		// Cache locally to avoid remote request slowing the page.
280
		$transient_name = 'jetpack_instant_search_cache_' . md5( wp_json_encode( $args ) );
281
		$cache          = get_transient( $transient_name );
282
		if ( false !== $cache ) {
283
			return $cache;
284
		}
285
286
		$service_url = add_query_arg(
287
			$args,
288
			sprintf(
289
				'https://public-api.wordpress.com/rest/v1.3/sites/%d/search',
290
				$this->jetpack_blog_id
291
			)
292
		);
293
294
		$request_args = array(
295
			'timeout'    => 10,
296
			'user-agent' => "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' ),
297
		);
298
299
		$request  = wp_remote_get( esc_url_raw( $service_url ), $request_args );
300
		$end_time = microtime( true );
301
302
		if ( is_wp_error( $request ) ) {
303
			return $request;
304
		}
305
306
		$response_code = wp_remote_retrieve_response_code( $request );
307
		$response      = json_decode( wp_remote_retrieve_body( $request ), true );
308
309 View Code Duplication
		if ( ! $response_code || $response_code < 200 || $response_code >= 300 ) {
310
			/**
311
			 * Fires after a search query request has failed
312
			 *
313
			 * @module search
314
			 *
315
			 * @since  5.6.0
316
			 *
317
			 * @param array Array containing the response code and response from the failed search query
318
			 */
319
			do_action(
320
				'failed_jetpack_search_query',
321
				array(
322
					'response_code' => $response_code,
323
					'json'          => $response,
324
				)
325
			);
326
327
			return new WP_Error( 'invalid_search_api_response', 'Invalid response from API - ' . $response_code );
0 ignored issues
show
Unused Code introduced by
The call to WP_Error::__construct() has too many arguments starting with 'invalid_search_api_response'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
328
		}
329
330
		$took = is_array( $response ) && ! empty( $response['took'] )
331
			? $response['took']
332
			: null;
333
334
		$query = array(
335
			'args'          => $args,
336
			'response'      => $response,
337
			'response_code' => $response_code,
338
			'elapsed_time'  => ( $end_time - $start_time ) * 1000, // Convert from float seconds to ms.
339
			'es_time'       => $took,
340
			'url'           => $service_url,
341
		);
342
343
		/**
344
		 * Fires after a search request has been performed.
345
		 *
346
		 * Includes the following info in the $query parameter:
347
		 *
348
		 * array args Array of Elasticsearch arguments for the search
349
		 * array response Raw API response, JSON decoded
350
		 * int response_code HTTP response code of the request
351
		 * float elapsed_time Roundtrip time of the search request, in milliseconds
352
		 * float es_time Amount of time Elasticsearch spent running the request, in milliseconds
353
		 * string url API url that was queried
354
		 *
355
		 * @module search
356
		 *
357
		 * @since  5.0.0
358
		 * @since  5.8.0 This action now fires on all queries instead of just successful queries.
359
		 *
360
		 * @param array $query Array of information about the query performed
361
		 */
362
		do_action( 'did_jetpack_search_query', $query );
363
364
		// Update local cache.
365
		set_transient( $transient_name, $response, 1 * HOUR_IN_SECONDS );
366
367
		return $response;
368
	}
369
370
	/**
371
	 * Get the raw Aggregation results from the Elasticsearch response.
372
	 *
373
	 * @since  8.4.0
374
	 *
375
	 * @return array Array of Aggregations performed on the search.
376
	 */
377
	public function get_search_aggregations_results() {
378
		if ( empty( $this->search_result ) || is_wp_error( $this->search_result ) || ! isset( $this->search_result['aggregations'] ) ) {
379
			return array();
380
		}
381
382
		return $this->search_result['aggregations'];
383
	}
384
385
	/**
386
	 * Automatically configure necessary settings for instant search
387
	 *
388
	 * @since  8.3.0
389
	 */
390
	public function auto_config_search() {
391
		if ( ! current_user_can( 'edit_theme_options' ) ) {
392
			return;
393
		}
394
395
		// Set default result format to "expanded".
396
		update_option( Jetpack_Search_Options::OPTION_PREFIX . 'result_format', 'expanded' );
397
398
		$this->auto_config_excluded_post_types();
399
		$this->auto_config_overlay_sidebar_widgets();
400
	}
401
402
	/**
403
	 * Automatically copy configured search widgets into the overlay sidebar
404
	 *
405
	 * @since  8.8.0
406
	 */
407
	public function auto_config_overlay_sidebar_widgets() {
408
		global $wp_registered_sidebars;
409
		$sidebars = get_option( 'sidebars_widgets', array() );
410
		$slug     = Jetpack_Search_Helpers::FILTER_WIDGET_BASE;
411
412
		if ( isset( $sidebars['jetpack-instant-search-sidebar'] ) ) {
413
			foreach ( (array) $sidebars['jetpack-instant-search-sidebar'] as $widget_id ) {
414
				if ( 0 === strpos( $widget_id, $slug ) ) {
415
					// Already configured.
416
					return;
417
				}
418
			}
419
		}
420
421
		$has_sidebar           = isset( $wp_registered_sidebars['sidebar-1'] );
422
		$sidebar_id            = false;
423
		$sidebar_searchbox_idx = false;
424
		if ( $has_sidebar ) {
425
			if ( empty( $sidebars['sidebar-1'] ) ) {
426
				// Adding to an empty sidebar is generally a bad idea.
427
				$has_sidebar = false;
428
			}
429
			foreach ( (array) $sidebars['sidebar-1'] as $idx => $widget_id ) {
430
				if ( 0 === strpos( $widget_id, 'search-' ) ) {
431
					$sidebar_searchbox_idx = $idx;
432
				}
433
				if ( 0 === strpos( $widget_id, $slug ) ) {
434
					$sidebar_id = (int) str_replace( Jetpack_Search_Helpers::FILTER_WIDGET_BASE . '-', '', $widget_id );
435
					break;
436
				}
437
			}
438
		}
439
440
		$next_id         = 1;
441
		$widget_opt_name = Jetpack_Search_Helpers::get_widget_option_name();
442
		$widget_options  = get_option( $widget_opt_name, array() );
443
		foreach ( $widget_options as $id => $w ) {
444
			if ( $id >= $next_id ) {
445
				$next_id = $id + 1;
446
			}
447
		}
448
449
		// Copy sidebar settings to overlay.
450
		if ( ( false !== $sidebar_id ) && isset( $widget_options[ $sidebar_id ] ) ) {
451
			$widget_options[ $next_id ] = $widget_options[ $sidebar_id ];
452
			update_option( $widget_opt_name, $widget_options );
453
454
			if ( ! isset( $sidebars['jetpack-instant-search-sidebar'] ) ) {
455
				$sidebars['jetpack-instant-search-sidebar'] = array();
456
			}
457
			array_unshift( $sidebars['jetpack-instant-search-sidebar'], Jetpack_Search_Helpers::build_widget_id( $next_id ) );
458
			update_option( 'sidebars_widgets', $sidebars );
459
460
			return;
461
		}
462
463
		// Configure overlay and sidebar (if it exists).
464
		$preconfig_opts = $this->get_preconfig_widget_options();
465
		if ( ! isset( $sidebars['jetpack-instant-search-sidebar'] ) ) {
466
			$sidebars['jetpack-instant-search-sidebar'] = array();
467
		}
468
		if ( $has_sidebar ) {
469
			$widget_options[ $next_id ] = $preconfig_opts;
470
			if ( false !== $sidebar_searchbox_idx ) {
471
				// Replace Core search box.
472
				$sidebars['sidebar-1'][ $sidebar_searchbox_idx ] = Jetpack_Search_Helpers::build_widget_id( $next_id );
473
			} else {
474
				// Add to top.
475
				array_unshift( $sidebars['sidebar-1'], Jetpack_Search_Helpers::build_widget_id( $next_id ) );
476
			}
477
			$next_id++;
478
		}
479
		$widget_options[ $next_id ] = $preconfig_opts;
480
		array_unshift( $sidebars['jetpack-instant-search-sidebar'], Jetpack_Search_Helpers::build_widget_id( $next_id ) );
481
482
		update_option( $widget_opt_name, $widget_options );
483
		update_option( 'sidebars_widgets', $sidebars );
484
	}
485
486
	/**
487
	 * Autoconfig search by adding filter widgets
488
	 *
489
	 * @since  8.4.0
490
	 *
491
	 * @return array Array of config settings for search widget.
492
	 */
493
	protected function get_preconfig_widget_options() {
494
		$settings = array(
495
			'title'   => '',
496
			'filters' => array(),
497
		);
498
499
		$post_types = get_post_types(
500
			array(
501
				'public'   => true,
502
				'_builtin' => false,
503
			)
504
		);
505
506
		if ( ! empty( $post_types ) ) {
507
			$settings['filters'][] = array(
508
				array(
509
					'name'  => '',
510
					'type'  => 'post_type',
511
					'count' => 5,
512
				),
513
			);
514
		}
515
516
		$taxonomies = get_taxonomies(
517
			array(
518
				'public'   => true,
519
				'_builtin' => false,
520
			)
521
		);
522
523
		foreach ( $taxonomies as $t ) {
524
			$settings['filters'][] = array(
525
				'name'     => '',
526
				'type'     => 'taxonomy',
527
				'taxonomy' => $t,
528
				'count'    => 5,
529
			);
530
		}
531
532
		$settings['filters'][] = array(
533
			'name'     => '',
534
			'type'     => 'taxonomy',
535
			'taxonomy' => 'category',
536
			'count'    => 5,
537
		);
538
		$settings['filters'][] = array(
539
			'name'     => '',
540
			'type'     => 'taxonomy',
541
			'taxonomy' => 'post_tag',
542
			'count'    => 5,
543
		);
544
		$settings['filters'][] = array(
545
			'name'     => '',
546
			'type'     => 'date_histogram',
547
			'count'    => 5,
548
			'field'    => 'post_date',
549
			'interval' => 'year',
550
		);
551
		return $settings;
552
	}
553
	/**
554
	 * Automatically configure post types to exclude from one of the search widgets
555
	 *
556
	 * @since  8.8.0
557
	 */
558
	public function auto_config_excluded_post_types() {
559
		$post_types         = get_post_types(
560
			array(
561
				'exclude_from_search' => false,
562
				'public'              => true,
563
			)
564
		);
565
		$enabled_post_types = array();
566
		$widget_options     = get_option( Jetpack_Search_Helpers::get_widget_option_name(), array() );
567
568
		// Iterate through each Jetpack Search widget configuration and append each enabled post type
569
		// to $enabled_post_types.
570
		foreach ( $widget_options as $widget_option ) {
571
			if ( isset( $widget_option['post_types'] ) && is_array( $widget_option['post_types'] ) ) {
572
				foreach ( $widget_option['post_types'] as $enabled_post_type ) {
573
					$enabled_post_types[ $enabled_post_type ] = $enabled_post_type;
574
				}
575
			}
576
		}
577
578
		$post_types_to_disable = array_diff( $post_types, $enabled_post_types );
579
		update_option( Jetpack_Search_Options::OPTION_PREFIX . 'excluded_post_types', join( ',', $post_types_to_disable ) );
580
	}
581
}
582