Completed
Push — update/videopress-use-block-pr... ( 3d1273...0b40f6 )
by
unknown
21:10 queued 10:43
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 automattic/jetpack
7
 */
8
9
/**
10
 * Class to load Instant Search experience on the site.
11
 *
12
 * @since 8.3.0
13
 */
14
class Jetpack_Instant_Search extends Jetpack_Search {
15
	/**
16
	 * The name of instant search sidebar
17
	 *
18
	 * @since 9.8.0
19
	 *
20
	 * @var string
21
	 */
22
	const JETPACK_INSTANT_SEARCH_SIDEBAR = 'jetpack-instant-search-sidebar';
23
24
	/**
25
	 * Variable to save old sidebars_widgets value.
26
	 *
27
	 * The value is set when action `after_switch_theme` is applied and cleared on filter `pre_update_option_sidebars_widgets`.
28
	 * The filters mentioned above run on /wp-admin/themes.php?activated=true, a request closely following switching theme.
29
	 *
30
	 * @since 9.8.0
31
	 *
32
	 * @var array
33
	 */
34
	private $old_sidebars_widgets;
35
36
	/**
37
	 * Get singleton instance of Jetpack Instant Search.
38
	 *
39
	 * Instantiates and sets up a new instance if needed, or returns the singleton.
40
	 *
41
	 * @since 9.8.0
42
	 *
43
	 * @return Jetpack_Instant_Search The Jetpack_Instant_Search singleton.
44
	 */
45
	public static function instance() {
46
		if ( ! isset( self::$instance ) ) {
47
			self::$instance = new Jetpack_Instant_Search();
48
			self::$instance->setup();
49
		}
50
51
		return self::$instance;
52
	}
53
54
	/**
55
	 * Loads the php for this version of search
56
	 *
57
	 * @since 8.3.0
58
	 */
59
	public function load_php() {
60
		$this->base_load_php();
61
62
		if ( class_exists( 'WP_Customize_Manager' ) ) {
63
			require_once __DIR__ . '/class-jetpack-search-customize.php';
64
			new Jetpack_Search_Customize();
65
		}
66
	}
67
68
	/**
69
	 * Setup the various hooks needed for the plugin to take over search duties.
70
	 *
71
	 * @since 5.0.0
72
	 */
73
	public function init_hooks() {
74
		if ( ! is_admin() ) {
75
			add_filter( 'posts_pre_query', array( $this, 'filter__posts_pre_query' ), 10, 2 );
76
			add_action( 'parse_query', array( $this, 'action__parse_query' ), 10, 1 );
77
78
			add_action( 'init', array( $this, 'set_filters_from_widgets' ) );
79
80
			add_action( 'wp_enqueue_scripts', array( $this, 'load_assets' ) );
81
			add_action( 'wp_footer', array( $this, 'print_instant_search_sidebar' ) );
82
			add_filter( 'body_class', array( $this, 'add_body_class' ), 10 );
83
		} else {
84
			add_action( 'update_option', array( $this, 'track_widget_updates' ), 10, 3 );
85
			// The priority has to be lower than 10 to run before _wp_sidebars_changed.
86
			// Which migrates widgets from old theme to the new one.
87
			add_action( 'after_switch_theme', array( $this, 'save_old_sidebars_widgets' ), 5, 0 );
88
			add_action( 'pre_update_option_sidebars_widgets', array( $this, 'remove_wp_migrated_widgets' ) );
89
		}
90
91
		add_action( 'widgets_init', array( $this, 'register_jetpack_instant_sidebar' ) );
92
		add_action( 'jetpack_deactivate_module_search', array( $this, 'move_search_widgets_to_inactive' ) );
93
	}
94
95
	/**
96
	 * Loads assets for Jetpack Instant Search Prototype featuring Search As You Type experience.
97
	 */
98
	public function load_assets() {
99
		$this->load_assets_with_parameters( '', JETPACK__PLUGIN_FILE );
100
	}
101
102
	/**
103
	 * Loads assets according to parameters provided.
104
	 *
105
	 * @param string $path_prefix - Prefix for assets' relative paths.
106
	 * @param string $plugin_base_path - Base path for use in plugins_url.
107
	 */
108
	public function load_assets_with_parameters( $path_prefix, $plugin_base_path ) {
109
		$polyfill_relative_path = $path_prefix . '_inc/build/instant-search/jp-search-ie11-polyfill-loader.bundle.js';
110
		$script_relative_path   = $path_prefix . '_inc/build/instant-search/jp-search-main.bundle.js';
111
112
		if (
113
			! file_exists( JETPACK__PLUGIN_DIR . $polyfill_relative_path ) ||
114
			! file_exists( JETPACK__PLUGIN_DIR . $script_relative_path )
115
		) {
116
			return;
117
		}
118
119
		$polyfill_version = Jetpack_Search_Helpers::get_asset_version( $polyfill_relative_path );
120
		$polyfill_path    = plugins_url( $polyfill_relative_path, $plugin_base_path );
121
		wp_enqueue_script( 'jetpack-instant-search-ie11', $polyfill_path, array(), $polyfill_version, true );
122
		$polyfill_payload_path = plugins_url(
123
			$path_prefix . '_inc/build/instant-search/jp-search-ie11-polyfill-payload.bundle.js',
124
			$plugin_base_path
125
		);
126
		$this->inject_polyfill_js_options( $polyfill_payload_path );
127
128
		$script_version = Jetpack_Search_Helpers::get_asset_version( $script_relative_path );
129
		$script_path    = plugins_url( $script_relative_path, $plugin_base_path );
130
		wp_enqueue_script( 'jetpack-instant-search', $script_path, array(), $script_version, true );
131
		wp_set_script_translations( 'jetpack-instant-search', 'jetpack' );
132
		$this->load_and_initialize_tracks();
133
		$this->inject_javascript_options();
134
	}
135
136
	/**
137
	 * Passes all options to the JS app.
138
	 */
139
	protected function inject_javascript_options() {
140
		$widget_options = Jetpack_Search_Helpers::get_widgets_from_option();
141
		if ( is_array( $widget_options ) ) {
142
			$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...
143
		}
144
145
		$overlay_widget_ids      = is_active_sidebar( 'jetpack-instant-search-sidebar' ) ?
146
			wp_get_sidebars_widgets()['jetpack-instant-search-sidebar'] : array();
147
		$filters                 = Jetpack_Search_Helpers::get_filters_from_widgets();
148
		$widgets                 = array();
149
		$widgets_outside_overlay = array();
150
		foreach ( $filters as $key => &$filter ) {
151
			$filter['filter_id'] = $key;
152
153
			if ( in_array( $filter['widget_id'], $overlay_widget_ids, true ) ) {
154 View Code Duplication
				if ( ! isset( $widgets[ $filter['widget_id'] ] ) ) {
155
					$widgets[ $filter['widget_id'] ]['filters']   = array();
156
					$widgets[ $filter['widget_id'] ]['widget_id'] = $filter['widget_id'];
157
				}
158
				$widgets[ $filter['widget_id'] ]['filters'][] = $filter;
159
			} else {
160 View Code Duplication
				if ( ! isset( $widgets_outside_overlay[ $filter['widget_id'] ] ) ) {
161
					$widgets_outside_overlay[ $filter['widget_id'] ]['filters']   = array();
162
					$widgets_outside_overlay[ $filter['widget_id'] ]['widget_id'] = $filter['widget_id'];
163
				}
164
				$widgets_outside_overlay[ $filter['widget_id'] ]['filters'][] = $filter;
165
			}
166
		}
167
		unset( $filter );
168
169
		$has_non_search_widgets = false;
170
		foreach ( $overlay_widget_ids as $overlay_widget_id ) {
171
			if ( strpos( $overlay_widget_id, Jetpack_Search_Helpers::FILTER_WIDGET_BASE ) === false ) {
172
				$has_non_search_widgets = true;
173
				break;
174
			}
175
		}
176
177
		$post_type_objs   = get_post_types( array( 'exclude_from_search' => false ), 'objects' );
178
		$post_type_labels = array();
179
		foreach ( $post_type_objs as $key => $obj ) {
180
			$post_type_labels[ $key ] = array(
181
				'singular_name' => $obj->labels->singular_name,
182
				'name'          => $obj->labels->name,
183
			);
184
		}
185
186
		$prefix         = Jetpack_Search_Options::OPTION_PREFIX;
187
		$posts_per_page = (int) get_option( 'posts_per_page' );
188
		if ( ( $posts_per_page > 20 ) || ( $posts_per_page <= 0 ) ) {
189
			$posts_per_page = 20;
190
		}
191
192
		$excluded_post_types   = get_option( $prefix . 'excluded_post_types' ) ? explode( ',', get_option( $prefix . 'excluded_post_types', '' ) ) : array();
193
		$post_types            = array_values(
194
			get_post_types(
195
				array(
196
					'exclude_from_search' => false,
197
					'public'              => true,
198
				)
199
			)
200
		);
201
		$unexcluded_post_types = array_diff( $post_types, $excluded_post_types );
202
		// NOTE: If all post types are being excluded, ignore the option value.
203
		if ( count( $unexcluded_post_types ) === 0 ) {
204
			$excluded_post_types = array();
205
		}
206
207
		$is_wpcom                  = defined( 'IS_WPCOM' ) && IS_WPCOM;
208
		$is_private_site           = '-1' === get_option( 'blog_public' );
209
		$is_jetpack_photon_enabled = method_exists( 'Jetpack', 'is_module_active' ) && Jetpack::is_module_active( 'photon' );
210
211
		$options = array(
212
			'overlayOptions'        => array(
213
				'colorTheme'      => get_option( $prefix . 'color_theme', 'light' ),
214
				'enableInfScroll' => get_option( $prefix . 'inf_scroll', '1' ) === '1',
215
				'enableSort'      => get_option( $prefix . 'enable_sort', '1' ) === '1',
216
				'highlightColor'  => get_option( $prefix . 'highlight_color', '#FFC' ),
217
				'overlayTrigger'  => get_option( $prefix . 'overlay_trigger', 'immediate' ),
218
				'resultFormat'    => get_option( $prefix . 'result_format', Jetpack_Search_Options::RESULT_FORMAT_MINIMAL ),
219
				'showPoweredBy'   => get_option( $prefix . 'show_powered_by', '1' ) === '1',
220
			),
221
222
			// core config.
223
			'homeUrl'               => home_url(),
224
			'locale'                => str_replace( '_', '-', Jetpack_Search_Helpers::is_valid_locale( get_locale() ) ? get_locale() : 'en_US' ),
225
			'postsPerPage'          => $posts_per_page,
226
			'siteId'                => $this->jetpack_blog_id,
227
			'postTypes'             => $post_type_labels,
228
			'webpackPublicPath'     => plugins_url( '_inc/build/instant-search/', JETPACK__PLUGIN_FILE ),
229
			'isPhotonEnabled'       => ( $is_wpcom || $is_jetpack_photon_enabled ) && ! $is_private_site,
230
231
			// config values related to private site support.
232
			'apiRoot'               => esc_url_raw( rest_url() ),
233
			'apiNonce'              => wp_create_nonce( 'wp_rest' ),
234
			'isPrivateSite'         => $is_private_site,
235
			'isWpcom'               => $is_wpcom,
236
237
			// search options.
238
			'defaultSort'           => get_option( $prefix . 'default_sort', 'relevance' ),
239
			'excludedPostTypes'     => $excluded_post_types,
240
241
			// widget info.
242
			'hasOverlayWidgets'     => count( $overlay_widget_ids ) > 0,
243
			'widgets'               => array_values( $widgets ),
244
			'widgetsOutsideOverlay' => array_values( $widgets_outside_overlay ),
245
			'hasNonSearchWidgets'   => $has_non_search_widgets,
246
		);
247
248
		/**
249
		 * Customize Instant Search Options.
250
		 *
251
		 * @module search
252
		 *
253
		 * @since 7.7.0
254
		 *
255
		 * @param array $options Array of parameters used in Instant Search queries.
256
		 */
257
		$options = apply_filters( 'jetpack_instant_search_options', $options );
258
259
		// Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
260
		wp_add_inline_script( 'jetpack-instant-search', 'var JetpackInstantSearchOptions=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( $options ) ) . '"));', 'before' );
261
	}
262
263
	/**
264
	 * Passes options to the polyfill loader script.
265
	 *
266
	 * @param string $polyfill_payload_path - Absolute path to the IE11 polyfill payload.
267
	 */
268
	protected function inject_polyfill_js_options( $polyfill_payload_path ) {
269
		wp_add_inline_script( 'jetpack-instant-search-ie11', 'var JetpackInstantSearchIe11PolyfillPath=decodeURIComponent("' . rawurlencode( $polyfill_payload_path ) . '");', 'before' );
270
	}
271
272
	/**
273
	 * Registers a widget sidebar for Instant Search.
274
	 */
275
	public function register_jetpack_instant_sidebar() {
276
		$args = array(
277
			'name'          => __( 'Jetpack Search Sidebar', 'jetpack' ),
278
			'id'            => 'jetpack-instant-search-sidebar',
279
			'description'   => __( 'Customize the sidebar inside the Jetpack Search overlay', 'jetpack' ),
280
			'class'         => '',
281
			'before_widget' => '<div id="%1$s" class="widget %2$s">',
282
			'after_widget'  => '</div>',
283
			'before_title'  => '<h2 class="widgettitle">',
284
			'after_title'   => '</h2>',
285
		);
286
		register_sidebar( $args );
287
	}
288
289
	/**
290
	 * Prints Instant Search sidebar.
291
	 */
292
	public function print_instant_search_sidebar() {
293
		?>
294
		<div class="jetpack-instant-search__widget-area" style="display: none">
295
			<?php if ( is_active_sidebar( 'jetpack-instant-search-sidebar' ) ) { ?>
296
				<?php dynamic_sidebar( 'jetpack-instant-search-sidebar' ); ?>
297
			<?php } ?>
298
		</div>
299
		<?php
300
	}
301
302
	/**
303
	 * Loads scripts for Tracks analytics library
304
	 */
305
	public function load_and_initialize_tracks() {
306
		wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
307
	}
308
309
	/**
310
	 * Bypass the normal Search query since we will run it with instant search.
311
	 *
312
	 * @since 8.3.0
313
	 *
314
	 * @param array    $posts Current array of posts (still pre-query).
315
	 * @param WP_Query $query The WP_Query being filtered.
316
	 *
317
	 * @return array Array of matching posts.
318
	 */
319
	public function filter__posts_pre_query( $posts, $query ) {
320
		if ( ! $this->should_handle_query( $query ) ) {
321
			// Intentionally not adding the 'jetpack_search_abort' action since this should fire for every request except for search.
322
			return $posts;
323
		}
324
325
		/**
326
		 * Bypass the main query and return dummy data
327
		 *  WP Core doesn't call the set_found_posts and its filters when filtering
328
		 *  posts_pre_query like we do, so need to do these manually.
329
		 */
330
		$query->found_posts   = 1;
331
		$query->max_num_pages = 1;
332
333
		return array();
334
	}
335
336
	/**
337
	 * Run the aggregations API query for any filtering
338
	 *
339
	 * @since 8.3.0
340
	 */
341
	public function action__parse_query() {
342
		if ( ! empty( $this->search_result ) ) {
343
			return;
344
		}
345
346
		if ( is_admin() ) {
347
			return;
348
		}
349
350
		if ( empty( $this->aggregations ) ) {
351
			return;
352
		}
353
354
		jetpack_require_lib( 'jetpack-wpes-query-builder/jetpack-wpes-query-builder' );
355
356
		$builder = new Jetpack_WPES_Query_Builder();
357
		$this->add_aggregations_to_es_query_builder( $this->aggregations, $builder );
358
		$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...
359
			array(
360
				'aggregations' => $builder->build_aggregation(),
361
				'size'         => 0,
362
				'from'         => 0,
363
			)
364
		);
365
	}
366
367
	/**
368
	 * Run an instant search on the WordPress.com public API.
369
	 *
370
	 * @since 8.3.0
371
	 *
372
	 * @param array $args Args conforming to the WP.com v1.3/sites/<blog_id>/search endpoint.
373
	 *
374
	 * @return object|WP_Error The response from the public API, or a WP_Error.
375
	 */
376
	public function instant_api( array $args ) {
377
		global $wp_version;
378
		$start_time = microtime( true );
379
380
		// Cache locally to avoid remote request slowing the page.
381
		$transient_name = 'jetpack_instant_search_cache_' . md5( wp_json_encode( $args ) );
382
		$cache          = get_transient( $transient_name );
383
		if ( false !== $cache ) {
384
			return $cache;
385
		}
386
387
		$service_url = add_query_arg(
388
			$args,
389
			sprintf(
390
				'https://public-api.wordpress.com/rest/v1.3/sites/%d/search',
391
				$this->jetpack_blog_id
392
			)
393
		);
394
395
		$request_args = array(
396
			'timeout'    => 10,
397
			'user-agent' => "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' ),
398
		);
399
400
		$request  = wp_remote_get( esc_url_raw( $service_url ), $request_args );
401
		$end_time = microtime( true );
402
403
		if ( is_wp_error( $request ) ) {
404
			return $request;
405
		}
406
407
		$response_code = wp_remote_retrieve_response_code( $request );
408
		$response      = json_decode( wp_remote_retrieve_body( $request ), true );
409
410 View Code Duplication
		if ( ! $response_code || $response_code < 200 || $response_code >= 300 ) {
411
			/**
412
			 * Fires after a search query request has failed
413
			 *
414
			 * @module search
415
			 *
416
			 * @since  5.6.0
417
			 *
418
			 * @param array Array containing the response code and response from the failed search query
419
			 */
420
			do_action(
421
				'failed_jetpack_search_query',
422
				array(
423
					'response_code' => $response_code,
424
					'json'          => $response,
425
				)
426
			);
427
428
			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...
429
		}
430
431
		$took = is_array( $response ) && ! empty( $response['took'] )
432
			? $response['took']
433
			: null;
434
435
		$query = array(
436
			'args'          => $args,
437
			'response'      => $response,
438
			'response_code' => $response_code,
439
			'elapsed_time'  => ( $end_time - $start_time ) * 1000, // Convert from float seconds to ms.
440
			'es_time'       => $took,
441
			'url'           => $service_url,
442
		);
443
444
		/**
445
		 * Fires after a search request has been performed.
446
		 *
447
		 * Includes the following info in the $query parameter:
448
		 *
449
		 * array args Array of Elasticsearch arguments for the search
450
		 * array response Raw API response, JSON decoded
451
		 * int response_code HTTP response code of the request
452
		 * float elapsed_time Roundtrip time of the search request, in milliseconds
453
		 * float es_time Amount of time Elasticsearch spent running the request, in milliseconds
454
		 * string url API url that was queried
455
		 *
456
		 * @module search
457
		 *
458
		 * @since  5.0.0
459
		 * @since  5.8.0 This action now fires on all queries instead of just successful queries.
460
		 *
461
		 * @param array $query Array of information about the query performed
462
		 */
463
		do_action( 'did_jetpack_search_query', $query );
464
465
		// Update local cache.
466
		set_transient( $transient_name, $response, 1 * HOUR_IN_SECONDS );
467
468
		return $response;
469
	}
470
471
	/**
472
	 * Get the raw Aggregation results from the Elasticsearch response.
473
	 *
474
	 * @since  8.4.0
475
	 *
476
	 * @return array Array of Aggregations performed on the search.
477
	 */
478
	public function get_search_aggregations_results() {
479
		if ( empty( $this->search_result ) || is_wp_error( $this->search_result ) || ! isset( $this->search_result['aggregations'] ) ) {
480
			return array();
481
		}
482
483
		return $this->search_result['aggregations'];
484
	}
485
486
	/**
487
	 * Automatically configure necessary settings for instant search
488
	 *
489
	 * @since  8.3.0
490
	 */
491
	public function auto_config_search() {
492
		if ( ! current_user_can( 'edit_theme_options' ) ) {
493
			return;
494
		}
495
496
		// Set default result format to "expanded".
497
		update_option( Jetpack_Search_Options::OPTION_PREFIX . 'result_format', Jetpack_Search_Options::RESULT_FORMAT_EXPANDED );
498
499
		$this->auto_config_excluded_post_types();
500
		$this->auto_config_overlay_sidebar_widgets();
501
		$this->auto_config_woo_result_format();
502
	}
503
504
	/**
505
	 * Automatically copy configured search widgets into the overlay sidebar
506
	 *
507
	 * @since  8.8.0
508
	 */
509
	public function auto_config_overlay_sidebar_widgets() {
510
		global $wp_registered_sidebars;
511
		$sidebars = get_option( 'sidebars_widgets', array() );
512
		$slug     = Jetpack_Search_Helpers::FILTER_WIDGET_BASE;
513
514
		if ( isset( $sidebars['jetpack-instant-search-sidebar'] ) ) {
515
			foreach ( (array) $sidebars['jetpack-instant-search-sidebar'] as $widget_id ) {
516
				if ( 0 === strpos( $widget_id, $slug ) ) {
517
					// Already configured.
518
					return;
519
				}
520
			}
521
		}
522
523
		$has_sidebar           = isset( $wp_registered_sidebars['sidebar-1'] );
524
		$sidebar_id            = false;
525
		$sidebar_searchbox_idx = false;
526
		if ( $has_sidebar ) {
527
			if ( empty( $sidebars['sidebar-1'] ) ) {
528
				// Adding to an empty sidebar is generally a bad idea.
529
				$has_sidebar = false;
530
			}
531
			foreach ( (array) $sidebars['sidebar-1'] as $idx => $widget_id ) {
532
				if ( 0 === strpos( $widget_id, 'search-' ) ) {
533
					$sidebar_searchbox_idx = $idx;
534
				}
535
				if ( 0 === strpos( $widget_id, $slug ) ) {
536
					$sidebar_id = (int) str_replace( Jetpack_Search_Helpers::FILTER_WIDGET_BASE . '-', '', $widget_id );
537
					break;
538
				}
539
			}
540
		}
541
542
		$next_id         = 1;
543
		$widget_opt_name = Jetpack_Search_Helpers::get_widget_option_name();
544
		$widget_options  = get_option( $widget_opt_name, array() );
545
		foreach ( $widget_options as $id => $w ) {
546
			if ( $id >= $next_id ) {
547
				$next_id = $id + 1;
548
			}
549
		}
550
551
		// Copy sidebar settings to overlay.
552
		if ( ( false !== $sidebar_id ) && isset( $widget_options[ $sidebar_id ] ) ) {
553
			$widget_options[ $next_id ] = $widget_options[ $sidebar_id ];
554
			update_option( $widget_opt_name, $widget_options );
555
556
			if ( ! isset( $sidebars['jetpack-instant-search-sidebar'] ) ) {
557
				$sidebars['jetpack-instant-search-sidebar'] = array();
558
			}
559
			array_unshift( $sidebars['jetpack-instant-search-sidebar'], Jetpack_Search_Helpers::build_widget_id( $next_id ) );
560
			update_option( 'sidebars_widgets', $sidebars );
561
562
			return;
563
		}
564
565
		// Configure overlay and sidebar (if it exists).
566
		$preconfig_opts = $this->get_preconfig_widget_options();
567
		if ( ! isset( $sidebars['jetpack-instant-search-sidebar'] ) ) {
568
			$sidebars['jetpack-instant-search-sidebar'] = array();
569
		}
570
		if ( $has_sidebar ) {
571
			$widget_options[ $next_id ] = $preconfig_opts;
572
			if ( false !== $sidebar_searchbox_idx ) {
573
				// Replace Core search box.
574
				$sidebars['sidebar-1'][ $sidebar_searchbox_idx ] = Jetpack_Search_Helpers::build_widget_id( $next_id );
575
			} else {
576
				// Add to top.
577
				array_unshift( $sidebars['sidebar-1'], Jetpack_Search_Helpers::build_widget_id( $next_id ) );
578
			}
579
			$next_id++;
580
		}
581
		$widget_options[ $next_id ] = $preconfig_opts;
582
		array_unshift( $sidebars['jetpack-instant-search-sidebar'], Jetpack_Search_Helpers::build_widget_id( $next_id ) );
583
584
		update_option( $widget_opt_name, $widget_options );
585
		update_option( 'sidebars_widgets', $sidebars );
586
	}
587
588
	/**
589
	 * Autoconfig search by adding filter widgets
590
	 *
591
	 * @since  8.4.0
592
	 *
593
	 * @return array Array of config settings for search widget.
594
	 */
595
	protected function get_preconfig_widget_options() {
596
		$settings = array(
597
			'title'   => '',
598
			'filters' => array(),
599
		);
600
601
		$post_types = get_post_types(
602
			array(
603
				'public'   => true,
604
				'_builtin' => false,
605
			)
606
		);
607
608
		if ( ! empty( $post_types ) ) {
609
			$settings['filters'][] = array(
610
				'name'  => '',
611
				'type'  => 'post_type',
612
				'count' => 5,
613
			);
614
		}
615
616
		// Grab a maximum of 3 taxonomies.
617
		$taxonomies = array_slice(
618
			get_taxonomies(
619
				array(
620
					'public'   => true,
621
					'_builtin' => false,
622
				)
623
			),
624
			0,
625
			3
626
		);
627
628
		foreach ( $taxonomies as $t ) {
629
			$settings['filters'][] = array(
630
				'name'     => '',
631
				'type'     => 'taxonomy',
632
				'taxonomy' => $t,
633
				'count'    => 5,
634
			);
635
		}
636
637
		$settings['filters'][] = array(
638
			'name'     => '',
639
			'type'     => 'taxonomy',
640
			'taxonomy' => 'category',
641
			'count'    => 5,
642
		);
643
644
		$settings['filters'][] = array(
645
			'name'     => '',
646
			'type'     => 'taxonomy',
647
			'taxonomy' => 'post_tag',
648
			'count'    => 5,
649
		);
650
651
		$settings['filters'][] = array(
652
			'name'     => '',
653
			'type'     => 'date_histogram',
654
			'count'    => 5,
655
			'field'    => 'post_date',
656
			'interval' => 'year',
657
		);
658
659
		return $settings;
660
	}
661
662
	/**
663
	 * Automatically configure post types to exclude from one of the search widgets
664
	 *
665
	 * @since  8.8.0
666
	 */
667
	public function auto_config_excluded_post_types() {
668
		$post_types         = get_post_types(
669
			array(
670
				'exclude_from_search' => false,
671
				'public'              => true,
672
			)
673
		);
674
		$enabled_post_types = array();
675
		$widget_options     = get_option( Jetpack_Search_Helpers::get_widget_option_name(), array() );
676
677
		// Prior to Jetpack 8.8, post types were enabled via Jetpack Search widgets rather than disabled via the Customizer.
678
		// To continue supporting post types set up in the old way, we iterate through each Jetpack Search
679
		// widget configuration and append each enabled post type to $enabled_post_types.
680
		foreach ( $widget_options as $widget_option ) {
681
			if ( isset( $widget_option['post_types'] ) && is_array( $widget_option['post_types'] ) ) {
682
				foreach ( $widget_option['post_types'] as $enabled_post_type ) {
683
					$enabled_post_types[ $enabled_post_type ] = $enabled_post_type;
684
				}
685
			}
686
		}
687
688
		if ( ! empty( $enabled_post_types ) ) {
689
			$post_types_to_disable = array_diff( $post_types, $enabled_post_types );
690
			update_option( Jetpack_Search_Options::OPTION_PREFIX . 'excluded_post_types', join( ',', $post_types_to_disable ) );
691
		}
692
	}
693
694
	/**
695
	 * Automatically set result format to 'product' if WooCommerce is installed
696
	 *
697
	 * @since  9.6.0
698
	 */
699
	public function auto_config_woo_result_format() {
700
		if ( ! method_exists( 'Jetpack', 'get_active_plugins' ) ) {
701
			return false;
702
		}
703
704
		// Check if WooCommerce plugin is active (based on https://docs.woocommerce.com/document/create-a-plugin/).
705
		if ( ! in_array( 'woocommerce/woocommerce.php', apply_filters( 'active_plugins', Jetpack::get_active_plugins() ), true ) ) {
706
			return false;
707
		}
708
709
		update_option( Jetpack_Search_Options::OPTION_PREFIX . 'result_format', Jetpack_Search_Options::RESULT_FORMAT_PRODUCT );
710
	}
711
712
	/**
713
	 * Save sidebars_widgets option before it's migrated by WordPress
714
	 *
715
	 * @since 9.8.0
716
	 *
717
	 * @param array $old_sidebars_widgets The sidebars_widgets option value to be saved.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $old_sidebars_widgets not be array|null?

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...
718
	 */
719
	public function save_old_sidebars_widgets( $old_sidebars_widgets = null ) {
720
		$this->old_sidebars_widgets = ! is_null( $old_sidebars_widgets ) ? $old_sidebars_widgets : wp_get_sidebars_widgets();
721
	}
722
723
	/**
724
	 * Clean WordPress auto-migrated sidebar widgets from instant search sidebar before saving option sidebars_widgets
725
	 *
726
	 * @since 9.8.0
727
	 *
728
	 * @param array $sidebars_widgets The sidebars_widgets option value to be filtered.
729
	 * @return array The sidebars_widgets option value to be saved
730
	 */
731
	public function remove_wp_migrated_widgets( $sidebars_widgets ) {
732
		// Hook the action only when it is a theme switch i.e. $this->old_sidebars_widgets is not empty.
733
		if ( empty( $this->old_sidebars_widgets ) ) {
734
			return $sidebars_widgets;
735
		}
736
737
		foreach ( $sidebars_widgets as $sidebar => $widgets ) {
738
			if ( 0 === stripos( $sidebar, static::JETPACK_INSTANT_SEARCH_SIDEBAR ) ) {
739
				// An 'equal' would work for the condition, but we want to cover 'less than' as well, just for extra insurance.
740
				// If the new Jetpack sidebar has already got less widgets, no need to run the code following the condition.
741
				if ( count( $sidebars_widgets[ static::JETPACK_INSTANT_SEARCH_SIDEBAR ] ) <= count( $this->old_sidebars_widgets[ static::JETPACK_INSTANT_SEARCH_SIDEBAR ] ) ) {
742
					break;
743
				}
744
745
				$lost_widgets                            = array_diff( $sidebars_widgets[ static::JETPACK_INSTANT_SEARCH_SIDEBAR ], $this->old_sidebars_widgets[ static::JETPACK_INSTANT_SEARCH_SIDEBAR ] );
746
				$sidebars_widgets['wp_inactive_widgets'] = array_merge( $lost_widgets, (array) $sidebars_widgets['wp_inactive_widgets'] );
747
748
				$sidebars_widgets[ static::JETPACK_INSTANT_SEARCH_SIDEBAR ] = $this->old_sidebars_widgets[ static::JETPACK_INSTANT_SEARCH_SIDEBAR ];
749
			}
750
		}
751
		$this->old_sidebars_widgets = null;
0 ignored issues
show
Documentation Bug introduced by
It seems like null of type null is incompatible with the declared type array of property $old_sidebars_widgets.

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...
752
753
		return $sidebars_widgets;
754
	}
755
756
	/**
757
	 * Add current theme name as a body class for easier override
758
	 *
759
	 * @param string[] $classes An array of body class names.
760
	 *
761
	 * @return string[] The array of classes after filtering
762
	 */
763
	public function add_body_class( $classes ) {
764
		$classes[] = 'jps-theme-' . get_stylesheet();
765
		return $classes;
766
	}
767
}
768