Completed
Push — feature/replace-yarn-1-with-pn... ( 6bc43e...87dbb1 )
by
unknown
35:33 queued 25:41
created

remove_wp_migrated_widgets()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 4
nop 1
dl 0
loc 24
rs 9.2248
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 );
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