Completed
Push — update/add-csstidy-config-sett... ( 30eb4b...0a5859 )
by
unknown
19:15 queued 09:40
created

remove_wp_migrated_widgets()   B

Complexity

Conditions 7
Paths 2

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

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