Completed
Push — add/handling-connection-errors ( 06a037...ad1b5d )
by
unknown
42:28 queued 26:50
created

inject_javascript_options()   C

Complexity

Conditions 11
Paths 80

Size

Total Lines 85

Duplication

Lines 8
Ratio 9.41 %

Importance

Changes 0
Metric Value
cc 11
nc 80
nop 0
dl 8
loc 85
rs 6.1806
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Jetpack Search: Instant Front-End Search and Filtering
4
 *
5
 * @since 8.3.0
6
 * @package jetpack
7
 */
8
9
use Automattic\Jetpack\Connection\Client;
10
use Automattic\Jetpack\Constants;
11
12
/**
13
 * Class to load Instant Search experience on the site.
14
 *
15
 * @since 8.3.0
16
 */
17
class Jetpack_Instant_Search extends Jetpack_Search {
18
19
	/**
20
	 * Loads the php for this version of search
21
	 *
22
	 * @since 8.3.0
23
	 */
24
	public function load_php() {
25
		$this->base_load_php();
26
27
		if ( class_exists( 'WP_Customize_Manager' ) ) {
28
			require_once dirname( __FILE__ ) . '/class-jetpack-search-customize.php';
29
			new Jetpack_Search_Customize();
30
		}
31
	}
32
33
	/**
34
	 * Setup the various hooks needed for the plugin to take over search duties.
35
	 *
36
	 * @since 5.0.0
37
	 */
38 View Code Duplication
	public function init_hooks() {
39
		if ( ! is_admin() ) {
40
			add_filter( 'posts_pre_query', array( $this, 'filter__posts_pre_query' ), 10, 2 );
41
			add_action( 'parse_query', array( $this, 'action__parse_query' ), 10, 1 );
42
43
			add_action( 'init', array( $this, 'set_filters_from_widgets' ) );
44
45
			add_action( 'wp_enqueue_scripts', array( $this, 'load_assets' ) );
46
			add_action( 'wp_footer', array( $this, 'print_instant_search_sidebar' ) );
47
		} else {
48
			add_action( 'update_option', array( $this, 'track_widget_updates' ), 10, 3 );
49
		}
50
51
		add_action( 'widgets_init', array( $this, 'register_jetpack_instant_sidebar' ) );
52
		add_action( 'jetpack_deactivate_module_search', array( $this, 'move_search_widgets_to_inactive' ) );
53
	}
54
55
	/**
56
	 * Loads assets for Jetpack Instant Search Prototype featuring Search As You Type experience.
57
	 */
58
	public function load_assets() {
59
		$script_relative_path = '_inc/build/instant-search/jp-search.bundle.js';
60
		$style_relative_path  = '_inc/build/instant-search/instant-search.min.css';
61
		if ( ! file_exists( JETPACK__PLUGIN_DIR . $script_relative_path ) || ! file_exists( JETPACK__PLUGIN_DIR . $style_relative_path ) ) {
62
			return;
63
		}
64
65
		$script_version = Jetpack_Search_Helpers::get_asset_version( $script_relative_path );
66
		$script_path    = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
67
		wp_enqueue_script( 'jetpack-instant-search', $script_path, array(), $script_version, true );
68
		wp_set_script_translations( 'jetpack-instant-search', 'jetpack' );
69
		$this->load_and_initialize_tracks();
70
		$this->inject_javascript_options();
71
72
		$style_version = Jetpack_Search_Helpers::get_asset_version( $style_relative_path );
73
		$style_path    = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
74
		wp_enqueue_style( 'jetpack-instant-search', $style_path, array(), $style_version );
75
	}
76
77
	/**
78
	 * Passes all options to the JS app.
79
	 */
80
	protected function inject_javascript_options() {
81
		$widget_options = Jetpack_Search_Helpers::get_widgets_from_option();
82
		if ( is_array( $widget_options ) ) {
83
			$widget_options = end( $widget_options );
0 ignored issues
show
Unused Code introduced by
$widget_options is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
84
		}
85
86
		$overlay_widget_ids      = is_active_sidebar( 'jetpack-instant-search-sidebar' ) ?
87
			wp_get_sidebars_widgets()['jetpack-instant-search-sidebar'] : array();
88
		$filters                 = Jetpack_Search_Helpers::get_filters_from_widgets();
89
		$widgets                 = array();
90
		$widgets_outside_overlay = array();
91
		foreach ( $filters as $key => &$filter ) {
92
			$filter['filter_id'] = $key;
93
94
			if ( in_array( $filter['widget_id'], $overlay_widget_ids, true ) ) {
95 View Code Duplication
				if ( ! isset( $widgets[ $filter['widget_id'] ] ) ) {
96
					$widgets[ $filter['widget_id'] ]['filters']   = array();
97
					$widgets[ $filter['widget_id'] ]['widget_id'] = $filter['widget_id'];
98
				}
99
				$widgets[ $filter['widget_id'] ]['filters'][] = $filter;
100
			} else {
101 View Code Duplication
				if ( ! isset( $widgets_outside_overlay[ $filter['widget_id'] ] ) ) {
102
					$widgets_outside_overlay[ $filter['widget_id'] ]['filters']   = array();
103
					$widgets_outside_overlay[ $filter['widget_id'] ]['widget_id'] = $filter['widget_id'];
104
				}
105
				$widgets_outside_overlay[ $filter['widget_id'] ]['filters'][] = $filter;
106
			}
107
		}
108
		unset( $filter );
109
110
		$post_type_objs   = get_post_types( array( 'exclude_from_search' => false ), 'objects' );
111
		$post_type_labels = array();
112
		foreach ( $post_type_objs as $key => $obj ) {
113
			$post_type_labels[ $key ] = array(
114
				'singular_name' => $obj->labels->singular_name,
115
				'name'          => $obj->labels->name,
116
			);
117
		}
118
119
		$prefix         = Jetpack_Search_Options::OPTION_PREFIX;
120
		$posts_per_page = (int) get_option( 'posts_per_page' );
121
		if ( ( $posts_per_page > 20 ) || ( $posts_per_page <= 0 ) ) {
122
			$posts_per_page = 20;
123
		}
124
125
		$options = array(
126
			'overlayOptions'        => array(
127
				'colorTheme'      => get_option( $prefix . 'color_theme', 'light' ),
128
				'enableInfScroll' => get_option( $prefix . 'inf_scroll', '1' ) === '1',
129
				'enableSort'      => get_option( $prefix . 'enable_sort', '1' ) === '1',
130
				'highlightColor'  => get_option( $prefix . 'highlight_color', '#FFC' ),
131
				'opacity'         => (int) get_option( $prefix . 'opacity', 97 ),
132
				'overlayTrigger'  => get_option( $prefix . 'overlay_trigger', 'immediate' ),
133
				'showPoweredBy'   => get_option( $prefix . 'show_powered_by', '1' ) === '1',
134
			),
135
136
			// core config.
137
			'homeUrl'               => home_url(),
138
			'locale'                => str_replace( '_', '-', Jetpack_Search_Helpers::is_valid_locale( get_locale() ) ? get_locale() : 'en_US' ),
139
			'postsPerPage'          => $posts_per_page,
140
			'siteId'                => $this->jetpack_blog_id,
141
			'postTypes'             => $post_type_labels,
142
143
			// search options.
144
			'defaultSort'           => get_option( $prefix . 'default_sort', 'relevance' ),
145
146
			// widget info.
147
			'widgets'               => array_values( $widgets ),
148
			'widgetsOutsideOverlay' => array_values( $widgets_outside_overlay ),
149
		);
150
151
		/**
152
		 * Customize Instant Search Options.
153
		 *
154
		 * @module search
155
		 *
156
		 * @since 7.7.0
157
		 *
158
		 * @param array $options Array of parameters used in Instant Search queries.
159
		 */
160
		$options = apply_filters( 'jetpack_instant_search_options', $options );
161
162
		// Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
163
		wp_add_inline_script( 'jetpack-instant-search', 'var JetpackInstantSearchOptions=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( $options ) ) . '"));' );
164
	}
165
166
	/**
167
	 * Registers a widget sidebar for Instant Search.
168
	 */
169
	public function register_jetpack_instant_sidebar() {
170
		$args = array(
171
			'name'          => __( 'Jetpack Search Sidebar', 'jetpack' ),
172
			'id'            => 'jetpack-instant-search-sidebar',
173
			'description'   => __( 'Customize the sidebar inside the Jetpack Search overlay', 'jetpack' ),
174
			'class'         => '',
175
			'before_widget' => '<div id="%1$s" class="widget %2$s">',
176
			'after_widget'  => '</div>',
177
			'before_title'  => '<h2 class="widgettitle">',
178
			'after_title'   => '</h2>',
179
		);
180
		register_sidebar( $args );
181
	}
182
183
	/**
184
	 * Prints Instant Search sidebar.
185
	 */
186
	public function print_instant_search_sidebar() {
187
		?>
188
		<div class="jetpack-instant-search__widget-area" style="display: none">
189
			<?php if ( is_active_sidebar( 'jetpack-instant-search-sidebar' ) ) { ?>
190
				<?php dynamic_sidebar( 'jetpack-instant-search-sidebar' ); ?>
191
			<?php } ?>
192
		</div>
193
		<?php
194
	}
195
196
	/**
197
	 * Loads scripts for Tracks analytics library
198
	 */
199
	public function load_and_initialize_tracks() {
200
		wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
201
	}
202
203
	/**
204
	 * Bypass the normal Search query since we will run it with instant search.
205
	 *
206
	 * @since 8.3.0
207
	 *
208
	 * @param array    $posts Current array of posts (still pre-query).
209
	 * @param WP_Query $query The WP_Query being filtered.
210
	 *
211
	 * @return array Array of matching posts.
212
	 */
213
	public function filter__posts_pre_query( $posts, $query ) {
214
		if ( ! $this->should_handle_query( $query ) ) {
215
			// Intentionally not adding the 'jetpack_search_abort' action since this should fire for every request except for search.
216
			return $posts;
217
		}
218
219
		/**
220
		 * Bypass the main query and return dummy data
221
		 *  WP Core doesn't call the set_found_posts and its filters when filtering
222
		 *  posts_pre_query like we do, so need to do these manually.
223
		 */
224
		$query->found_posts   = 1;
225
		$query->max_num_pages = 1;
226
227
		return array();
228
	}
229
230
	/**
231
	 * Run the aggregations API query for any filtering
232
	 *
233
	 * @since 8.3.0
234
	 *
235
	 * @param WP_Query $query The WP_Query being filtered.
236
	 */
237
	public function action__parse_query( $query ) {
238
		if ( ! empty( $this->search_result ) ) {
239
			return;
240
		}
241
242
		if ( is_admin() ) {
243
			return;
244
		}
245
246
		if ( empty( $this->aggregations ) ) {
247
			return;
248
		}
249
250
		jetpack_require_lib( 'jetpack-wpes-query-builder/jetpack-wpes-query-builder' );
251
252
		$builder = new Jetpack_WPES_Query_Builder();
253
		$this->add_aggregations_to_es_query_builder( $this->aggregations, $builder );
254
		$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...
255
			array(
256
				'aggregations' => $builder->build_aggregation(),
257
				'size'         => 0,
258
				'from'         => 0,
259
			)
260
		);
261
	}
262
263
	/**
264
	 * Run an instant search on the WordPress.com public API.
265
	 *
266
	 * @since 8.3.0
267
	 *
268
	 * @param array $args Args conforming to the WP.com v1.3/sites/<blog_id>/search endpoint.
269
	 *
270
	 * @return object|WP_Error The response from the public API, or a WP_Error.
271
	 */
272
	public function instant_api( array $args ) {
273
		global $wp_version;
274
		$start_time = microtime( true );
275
276
		// Cache locally to avoid remote request slowing the page.
277
		$transient_name = 'jetpack_instant_search_cache_' . md5( wp_json_encode( $args ) );
278
		$cache          = get_transient( $transient_name );
279
		if ( false !== $cache ) {
280
			return $cache;
281
		}
282
283
		$service_url = add_query_arg(
284
			$args,
285
			sprintf(
286
				'https://public-api.wordpress.com/rest/v1.3/sites/%d/search',
287
				$this->jetpack_blog_id
288
			)
289
		);
290
291
		$request_args = array(
292
			'timeout'    => 10,
293
			'user-agent' => "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' ),
294
		);
295
296
		$request  = wp_remote_get( esc_url_raw( $service_url ), $request_args );
297
		$end_time = microtime( true );
298
299
		if ( is_wp_error( $request ) ) {
300
			return $request;
301
		}
302
303
		$response_code = wp_remote_retrieve_response_code( $request );
304
		$response      = json_decode( wp_remote_retrieve_body( $request ), true );
305
306 View Code Duplication
		if ( ! $response_code || $response_code < 200 || $response_code >= 300 ) {
307
			/**
308
			 * Fires after a search query request has failed
309
			 *
310
			 * @module search
311
			 *
312
			 * @since  5.6.0
313
			 *
314
			 * @param array Array containing the response code and response from the failed search query
315
			 */
316
			do_action(
317
				'failed_jetpack_search_query',
318
				array(
319
					'response_code' => $response_code,
320
					'json'          => $response,
321
				)
322
			);
323
324
			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...
325
		}
326
327
		$took = is_array( $response ) && ! empty( $response['took'] )
328
			? $response['took']
329
			: null;
330
331
		$query = array(
332
			'args'          => $args,
333
			'response'      => $response,
334
			'response_code' => $response_code,
335
			'elapsed_time'  => ( $end_time - $start_time ) * 1000, // Convert from float seconds to ms.
336
			'es_time'       => $took,
337
			'url'           => $service_url,
338
		);
339
340
		/**
341
		 * Fires after a search request has been performed.
342
		 *
343
		 * Includes the following info in the $query parameter:
344
		 *
345
		 * array args Array of Elasticsearch arguments for the search
346
		 * array response Raw API response, JSON decoded
347
		 * int response_code HTTP response code of the request
348
		 * float elapsed_time Roundtrip time of the search request, in milliseconds
349
		 * float es_time Amount of time Elasticsearch spent running the request, in milliseconds
350
		 * string url API url that was queried
351
		 *
352
		 * @module search
353
		 *
354
		 * @since  5.0.0
355
		 * @since  5.8.0 This action now fires on all queries instead of just successful queries.
356
		 *
357
		 * @param array $query Array of information about the query performed
358
		 */
359
		do_action( 'did_jetpack_search_query', $query );
360
361
		// Update local cache.
362
		set_transient( $transient_name, $response, 1 * HOUR_IN_SECONDS );
363
364
		return $response;
365
	}
366
367
	/**
368
	 * Get the raw Aggregation results from the Elasticsearch response.
369
	 *
370
	 * @since  8.4.0
371
	 *
372
	 * @return array Array of Aggregations performed on the search.
373
	 */
374
	public function get_search_aggregations_results() {
375
		if ( empty( $this->search_result ) || is_wp_error( $this->search_result ) || ! isset( $this->search_result['aggregations'] ) ) {
376
			return array();
377
		}
378
379
		return $this->search_result['aggregations'];
380
	}
381
382
	/**
383
	 * Autoconfig search by adding filter widgets
384
	 *
385
	 * @since  8.3.0
386
	 */
387
	public function auto_config_search() {
388
		if ( ! current_user_can( 'edit_theme_options' ) ) {
389
			return;
390
		}
391
392
		global $wp_registered_sidebars;
393
		$sidebars = get_option( 'sidebars_widgets', array() );
394
		$slug     = Jetpack_Search_Helpers::FILTER_WIDGET_BASE;
395
396
		if ( isset( $sidebars['jetpack-instant-search-sidebar'] ) ) {
397
			foreach ( (array) $sidebars['jetpack-instant-search-sidebar'] as $widget_id ) {
398
				if ( 0 === strpos( $widget_id, $slug ) ) {
399
					// Already configured.
400
					return;
401
				}
402
			}
403
		}
404
405
		$has_sidebar           = isset( $wp_registered_sidebars['sidebar-1'] );
406
		$sidebar_id            = false;
407
		$sidebar_searchbox_idx = false;
408
		if ( $has_sidebar ) {
409
			if ( empty( $sidebars['sidebar-1'] ) ) {
410
				// Adding to an empty sidebar is generally a bad idea.
411
				$has_sidebar = false;
412
			}
413
			foreach ( (array) $sidebars['sidebar-1'] as $idx => $widget_id ) {
414
				if ( 0 === strpos( $widget_id, 'search-' ) ) {
415
					$sidebar_searchbox_idx = $idx;
416
				}
417
				if ( 0 === strpos( $widget_id, $slug ) ) {
418
					$sidebar_id = (int) str_replace( Jetpack_Search_Helpers::FILTER_WIDGET_BASE . '-', '', $widget_id );
419
					break;
420
				}
421
			}
422
		}
423
424
		$next_id         = 1;
425
		$widget_opt_name = Jetpack_Search_Helpers::get_widget_option_name();
426
		$widget_options  = get_option( $widget_opt_name, array() );
427
		foreach ( $widget_options as $id => $w ) {
428
			if ( $id >= $next_id ) {
429
				$next_id = $id + 1;
430
			}
431
		}
432
433
		// Copy sidebar settings to overlay.
434
		if ( ( false !== $sidebar_id ) && isset( $widget_options[ $sidebar_id ] ) ) {
435
			$widget_options[ $next_id ] = $widget_options[ $sidebar_id ];
436
			update_option( $widget_opt_name, $widget_options );
437
438
			if ( ! isset( $sidebars['jetpack-instant-search-sidebar'] ) ) {
439
				$sidebars['jetpack-instant-search-sidebar'] = array();
440
			}
441
			array_unshift( $sidebars['jetpack-instant-search-sidebar'], Jetpack_Search_Helpers::build_widget_id( $next_id ) );
442
			update_option( 'sidebars_widgets', $sidebars );
443
444
			return;
445
		}
446
447
		// Configure overlay and sidebar (if it exists).
448
		$preconfig_opts = $this->get_preconfig_widget_options();
449
		if ( ! isset( $sidebars['jetpack-instant-search-sidebar'] ) ) {
450
			$sidebars['jetpack-instant-search-sidebar'] = array();
451
		}
452
		if ( $has_sidebar ) {
453
			$widget_options[ $next_id ] = $preconfig_opts;
454
			if ( false !== $sidebar_searchbox_idx ) {
455
				// Replace Core search box.
456
				$sidebars['sidebar-1'][ $sidebar_searchbox_idx ] = Jetpack_Search_Helpers::build_widget_id( $next_id );
457
			} else {
458
				// Add to top.
459
				array_unshift( $sidebars['sidebar-1'], Jetpack_Search_Helpers::build_widget_id( $next_id ) );
460
			}
461
			$next_id++;
462
		}
463
		$widget_options[ $next_id ] = $preconfig_opts;
464
		array_unshift( $sidebars['jetpack-instant-search-sidebar'], Jetpack_Search_Helpers::build_widget_id( $next_id ) );
465
466
		update_option( $widget_opt_name, $widget_options );
467
		update_option( 'sidebars_widgets', $sidebars );
468
	}
469
470
	/**
471
	 * Autoconfig search by adding filter widgets
472
	 *
473
	 * @since  8.4.0
474
	 *
475
	 * @return array Array of config settings for search widget.
476
	 */
477
	protected function get_preconfig_widget_options() {
478
		$settings = array(
479
			'title'              => '',
480
			'search_box_enabled' => 1,
481
			'user_sort_enabled'  => 0,
482
			'filters'            => array(),
483
		);
484
485
		$post_types = get_post_types(
486
			array(
487
				'public'   => true,
488
				'_builtin' => false,
489
			)
490
		);
491
492
		if ( ! empty( $post_types ) ) {
493
			$settings['filters'][] = array(
494
				array(
495
					'name'  => '',
496
					'type'  => 'post_type',
497
					'count' => 5,
498
				),
499
			);
500
		}
501
502
		$taxonomies = get_taxonomies(
503
			array(
504
				'public'   => true,
505
				'_builtin' => false,
506
			)
507
		);
508
509
		foreach ( $taxonomies as $t ) {
510
			$settings['filters'][] = array(
511
				'name'     => '',
512
				'type'     => 'taxonomy',
513
				'taxonomy' => $t,
514
				'count'    => 5,
515
			);
516
		}
517
518
		$settings['filters'][] = array(
519
			'name'     => '',
520
			'type'     => 'taxonomy',
521
			'taxonomy' => 'category',
522
			'count'    => 5,
523
		);
524
		$settings['filters'][] = array(
525
			'name'     => '',
526
			'type'     => 'taxonomy',
527
			'taxonomy' => 'post_tag',
528
			'count'    => 5,
529
		);
530
		$settings['filters'][] = array(
531
			'name'     => '',
532
			'type'     => 'date_histogram',
533
			'count'    => 5,
534
			'field'    => 'post_date',
535
			'interval' => 'year',
536
		);
537
538
		return $settings;
539
	}
540
541
}
542