Completed
Push — instant-search-master ( d38585...c0a8bb )
by
unknown
28:22 queued 22:03
created

Jetpack_Instant_Search   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 371
Duplicated Lines 11.86 %

Coupling/Cohesion

Components 2
Dependencies 6

Importance

Changes 0
Metric Value
dl 44
loc 371
rs 8.96
c 0
b 0
f 0
wmc 43
lcom 2
cbo 6

12 Methods

Rating   Name   Duplication   Size   Complexity  
A load_php() 0 9 2
A init_hooks() 16 16 2
A load_assets() 0 17 3
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 get_asset_version() 0 5 3
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
C inject_javascript_options() 8 77 11

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 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
		require_once dirname( __FILE__ ) . '/class.jetpack-search-template-tags.php';
26
		require_once JETPACK__PLUGIN_DIR . 'modules/widgets/search.php';
27
28
		if ( class_exists( 'WP_Customize_Manager' ) ) {
29
			require_once dirname( __FILE__ ) . '/class-jetpack-search-customize.php';
30
			new Jetpack_Search_Customize();
31
		}
32
	}
33
34
	/**
35
	 * Setup the various hooks needed for the plugin to take over search duties.
36
	 *
37
	 * @since 5.0.0
38
	 */
39 View Code Duplication
	public function init_hooks() {
40
		if ( ! is_admin() ) {
41
			add_filter( 'posts_pre_query', array( $this, 'filter__posts_pre_query' ), 10, 2 );
42
			add_action( 'parse_query', array( $this, 'action__parse_query' ), 10, 1 );
43
44
			add_action( 'init', array( $this, 'set_filters_from_widgets' ) );
45
46
			add_action( 'wp_enqueue_scripts', array( $this, 'load_assets' ) );
47
			add_action( 'wp_footer', array( $this, 'print_instant_search_sidebar' ) );
48
		} else {
49
			add_action( 'update_option', array( $this, 'track_widget_updates' ), 10, 3 );
50
		}
51
52
		add_action( 'widgets_init', array( $this, 'register_jetpack_instant_sidebar' ) );
53
		add_action( 'jetpack_deactivate_module_search', array( $this, 'move_search_widgets_to_inactive' ) );
54
	}
55
56
	/**
57
	 * Loads assets for Jetpack Instant Search Prototype featuring Search As You Type experience.
58
	 */
59
	public function load_assets() {
60
		$script_relative_path = '_inc/build/instant-search/jp-search.bundle.js';
61
		$style_relative_path  = '_inc/build/instant-search/instant-search.min.css';
62
		if ( ! file_exists( JETPACK__PLUGIN_DIR . $script_relative_path ) || ! file_exists( JETPACK__PLUGIN_DIR . $style_relative_path ) ) {
63
			return;
64
		}
65
66
		$script_version = self::get_asset_version( $script_relative_path );
67
		$script_path    = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
68
		wp_enqueue_script( 'jetpack-instant-search', $script_path, array(), $script_version, true );
69
		$this->load_and_initialize_tracks();
70
		$this->inject_javascript_options();
71
72
		$style_version = self::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 );
84
		}
85
86
		$overlay_widget_ids      = array_key_exists( 'jetpack-instant-search-sidebar', get_option( 'sidebars_widgets', array() ) ) ?
87
			get_option( 'sidebars_widgets', array() )['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(), '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
		$options = array(
121
			'overlayOptions'        => array(
122
				'colorTheme'      => get_option( $prefix . 'color_theme', 'light' ),
123
				'enableInfScroll' => (bool) get_option( $prefix . 'inf_scroll', false ),
124
				'highlightColor'  => get_option( $prefix . 'highlight_color', '#FFC' ),
125
				'opacity'         => (int) get_option( $prefix . 'opacity', 97 ),
126
				'showPoweredBy'   => (bool) get_option( $prefix . 'show_powered_by', true ),
127
			),
128
129
			// core config.
130
			'homeUrl'               => home_url(),
131
			'locale'                => str_replace( '_', '-', Jetpack_Search_Helpers::is_valid_locale( get_locale() ) ? get_locale() : 'en_US' ),
132
			'postsPerPage'          => get_option( 'posts_per_page' ),
133
			'siteId'                => Jetpack::get_option( 'id' ),
134
135
			// filtering.
136
			'postTypeFilters'       => isset( $widget_options['post_types'] ) ? $widget_options['post_types'] : array(),
137
			'postTypes'             => $post_type_labels,
138
			'sort'                  => isset( $widget_options['sort'] ) ? $widget_options['sort'] : null,
139
			'widgets'               => array_values( $widgets ),
140
			'widgetsOutsideOverlay' => array_values( $widgets_outside_overlay ),
141
		);
142
143
		/**
144
		 * Customize Instant Search Options.
145
		 *
146
		 * @module search
147
		 *
148
		 * @since 7.7.0
149
		 *
150
		 * @param array $options Array of parameters used in Instant Search queries.
151
		 */
152
		$options = apply_filters( 'jetpack_instant_search_options', $options );
153
154
		// Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
155
		wp_add_inline_script( 'jetpack-instant-search', 'var JetpackInstantSearchOptions=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( $options ) ) . '"));' );
156
	}
157
158
	/**
159
	 * Registers a widget sidebar for Instant Search.
160
	 */
161
	public function register_jetpack_instant_sidebar() {
162
		$args = array(
163
			'name'          => __( 'Jetpack Search Sidebar', 'jetpack' ),
164
			'id'            => 'jetpack-instant-search-sidebar',
165
			'description'   => __( 'Customize the sidebar inside the Jetpack Search overlay', 'jetpack' ),
166
			'class'         => '',
167
			'before_widget' => '<div id="%1$s" class="widget %2$s">',
168
			'after_widget'  => '</div>',
169
			'before_title'  => '<h2 class="widgettitle">',
170
			'after_title'   => '</h2>',
171
		);
172
		register_sidebar( $args );
173
	}
174
175
	/**
176
	 * Prints Instant Search sidebar.
177
	 */
178
	public function print_instant_search_sidebar() {
179
		?>
180
		<div class="jetpack-instant-search__widget-area" style="display: none">
181
			<?php if ( is_active_sidebar( 'jetpack-instant-search-sidebar' ) ) { ?>
182
				<?php dynamic_sidebar( 'jetpack-instant-search-sidebar' ); ?>
183
			<?php } ?>
184
		</div>
185
		<?php
186
	}
187
188
	/**
189
	 * Loads scripts for Tracks analytics library
190
	 */
191
	public function load_and_initialize_tracks() {
192
		wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
193
	}
194
195
	/**
196
	 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
197
	 *
198
	 * @param string $file Path of the file we are looking for.
199
	 * @return string $script_version Version number.
200
	 */
201
	public static function get_asset_version( $file ) {
202
		return Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $file )
203
			? filemtime( JETPACK__PLUGIN_DIR . $file )
204
			: JETPACK__VERSION;
205
	}
206
207
	/**
208
	 * Bypass the normal Search query since we will run it with instant search.
209
	 *
210
	 * @since 8.3.0
211
	 *
212
	 * @param array    $posts Current array of posts (still pre-query).
213
	 * @param WP_Query $query The WP_Query being filtered.
214
	 *
215
	 * @return array Array of matching posts.
216
	 */
217
	public function filter__posts_pre_query( $posts, $query ) {
218
		if ( ! $this->should_handle_query( $query ) ) {
219
			// Intentionally not adding the 'jetpack_search_abort' action since this should fire for every request except for search.
220
			return $posts;
221
		}
222
223
		/**
224
		 * Bypass the main query and return dummy data
225
		 *  WP Core doesn't call the set_found_posts and its filters when filtering
226
		 *  posts_pre_query like we do, so need to do these manually.
227
		 */
228
		$query->found_posts   = 1;
229
		$query->max_num_pages = 1;
230
231
		return array();
232
	}
233
234
	/**
235
	 * Run the aggregations API query for any filtering
236
	 *
237
	 * @since 8.3.0
238
	 *
239
	 * @param WP_Query $query The WP_Query being filtered.
240
	 */
241
	public function action__parse_query( $query ) {
242
		if ( ! empty( $this->search_result ) ) {
243
			return;
244
		}
245
246
		if ( is_admin() ) {
247
			return;
248
		}
249
250
		if ( empty( $this->aggregations ) ) {
251
			return;
252
		}
253
254
		jetpack_require_lib( 'jetpack-wpes-query-builder/jetpack-wpes-query-builder' );
255
256
		$builder = new Jetpack_WPES_Query_Builder();
257
		$this->add_aggregations_to_es_query_builder( $this->aggregations, $builder );
258
		$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...
259
			array(
260
				'aggregations' => $builder->build_aggregation(),
261
				'size'         => 0,
262
				'from'         => 0,
263
			)
264
		);
265
	}
266
267
	/**
268
	 * Run an instant search on the WordPress.com public API.
269
	 *
270
	 * @since 8.3.0
271
	 *
272
	 * @param array $args Args conforming to the WP.com v1.3/sites/<blog_id>/search endpoint.
273
	 *
274
	 * @return object|WP_Error The response from the public API, or a WP_Error.
275
	 */
276
	public function instant_api( array $args ) {
277
		global $wp_version;
278
		$start_time = microtime( true );
279
280
		// Cache locally to avoid remote request slowing the page.
281
		$transient_name = 'jetpack_instant_search_cache_' . md5( wp_json_encode( $args ) );
282
		$cache          = get_transient( $transient_name );
283
		if ( false !== $cache ) {
284
			return $cache;
285
		}
286
287
		$service_url = add_query_arg(
288
			$args,
289
			sprintf(
290
				'https://public-api.wordpress.com/rest/v1.3/sites/%d/search',
291
				$this->jetpack_blog_id
292
			)
293
		);
294
295
		$request_args = array(
296
			'timeout'    => 10,
297
			'user-agent' => "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' ),
298
		);
299
300
		$request  = wp_remote_get( esc_url_raw( $service_url ), $request_args );
301
		$end_time = microtime( true );
302
303
		if ( is_wp_error( $request ) ) {
304
			return $request;
305
		}
306
307
		$response_code = wp_remote_retrieve_response_code( $request );
308
		$response      = json_decode( wp_remote_retrieve_body( $request ), true );
309
310 View Code Duplication
		if ( ! $response_code || $response_code < 200 || $response_code >= 300 ) {
311
			/**
312
			 * Fires after a search query request has failed
313
			 *
314
			 * @module search
315
			 *
316
			 * @since  5.6.0
317
			 *
318
			 * @param array Array containing the response code and response from the failed search query
319
			 */
320
			do_action(
321
				'failed_jetpack_search_query',
322
				array(
323
					'response_code' => $response_code,
324
					'json'          => $response,
325
				)
326
			);
327
328
			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...
329
		}
330
331
		$took = is_array( $response ) && ! empty( $response['took'] )
332
			? $response['took']
333
			: null;
334
335
		$query = array(
336
			'args'          => $args,
337
			'response'      => $response,
338
			'response_code' => $response_code,
339
			'elapsed_time'  => ( $end_time - $start_time ) * 1000, // Convert from float seconds to ms.
340
			'es_time'       => $took,
341
			'url'           => $service_url,
342
		);
343
344
		/**
345
		 * Fires after a search request has been performed.
346
		 *
347
		 * Includes the following info in the $query parameter:
348
		 *
349
		 * array args Array of Elasticsearch arguments for the search
350
		 * array response Raw API response, JSON decoded
351
		 * int response_code HTTP response code of the request
352
		 * float elapsed_time Roundtrip time of the search request, in milliseconds
353
		 * float es_time Amount of time Elasticsearch spent running the request, in milliseconds
354
		 * string url API url that was queried
355
		 *
356
		 * @module search
357
		 *
358
		 * @since  5.0.0
359
		 * @since  5.8.0 This action now fires on all queries instead of just successful queries.
360
		 *
361
		 * @param array $query Array of information about the query performed
362
		 */
363
		do_action( 'did_jetpack_search_query', $query );
364
365
		// Update local cache.
366
		set_transient( $transient_name, $response, 1 * HOUR_IN_SECONDS );
367
368
		return $response;
369
	}
370
371
	/**
372
	 * Get the raw Aggregation results from the Elasticsearch response.
373
	 *
374
	 * @since  8.3.0
375
	 *
376
	 * @return array Array of Aggregations performed on the search.
377
	 */
378
	public function get_search_aggregations_results() {
379
		if ( empty( $this->search_result ) || is_wp_error( $this->search_result ) || ! isset( $this->search_result['aggregations'] ) ) {
380
			return array();
381
		}
382
383
		return $this->search_result['aggregations'];
384
	}
385
386
387
}
388