Completed
Push — add/private-site-mode ( 81d3a8...c37ff2 )
by
unknown
25:46 queued 11:45
created

Jetpack_Instant_Search   F

Complexity

Total Complexity 74

Size/Duplication

Total Lines 629
Duplicated Lines 7 %

Coupling/Cohesion

Components 2
Dependencies 5

Importance

Changes 0
Metric Value
dl 44
loc 629
rs 2.451
c 0
b 0
f 0
wmc 74
lcom 2
cbo 5

17 Methods

Rating   Name   Duplication   Size   Complexity  
A load_php() 0 8 2
A init_hooks() 16 16 2
A load_assets() 0 3 1
A load_assets_with_parameters() 0 33 4
F inject_javascript_options() 8 109 14
A inject_polyfill_js_options() 0 3 1
A register_jetpack_instant_sidebar() 0 13 1
A print_instant_search_sidebar() 0 9 2
A load_and_initialize_tracks() 0 3 1
A filter__posts_pre_query() 0 16 2
A action__parse_query() 0 25 4
C instant_api() 20 94 8
A get_search_aggregations_results() 0 7 4
A auto_config_search() 0 11 2
F auto_config_overlay_sidebar_widgets() 0 78 17
B get_preconfig_widget_options() 0 68 3
B auto_config_excluded_post_types() 0 26 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Jetpack_Instant_Search often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Jetpack_Instant_Search, and based on these observations, apply Extract Interface, too.

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