Completed
Push — update/refactor-search-php ( 6555aa...dbec44 )
by
unknown
06:41
created

Jetpack_Instant_Search::load_assets()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19

Duplication

Lines 5
Ratio 26.32 %

Importance

Changes 0
Metric Value
cc 3
nc 3
nop 0
dl 5
loc 19
rs 9.6333
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 jetpack
7
 */
8
9
use Automattic\Jetpack\Connection\Client;
10
use Automattic\Jetpack\Constants;
11
12
class Jetpack_Instant_Search extends Jetpack_Search {
13
14
	/**
15
	 * Jetpack_Instant_Search constructor.
16
	 *
17
	 * @since 5.0.0
18
	 *
19
	 * Doesn't do anything. This class needs to be initialized via the instance() method instead.
20
	 */
21
	protected function __construct() {
22
	}
23
24
	/**
25
	 * Loads the php for this version of search
26
	 *
27
	 * @since 8.3.0
28
	 */
29
	public function load_php() {
30
		require_once dirname( __FILE__ ) . '/class.jetpack-search-template-tags.php';
31
		require_once JETPACK__PLUGIN_DIR . 'modules/widgets/search.php';
32
		require_once dirname( __FILE__ ) . '/class.jetpack-search-template-tags.php';
33
34
		if ( class_exists( 'WP_Customize_Manager' ) ) {
35
			require_once dirname( __FILE__ ) . '/class-jetpack-search-customize.php';
36
			new Jetpack_Search_Customize();
37
		}
38
	}
39
40
	/**
41
	 * Setup the various hooks needed for the plugin to take over search duties.
42
	 *
43
	 * @since 5.0.0
44
	 */
45
	public function init_hooks() {
46
		if ( ! is_admin() ) {
47
			add_filter( 'posts_pre_query', array( $this, 'filter__posts_pre_query' ), 10, 2 );
48
			add_action( 'parse_query', array( $this, 'action__parse_query' ), 10, 1 );
49
50
			add_action( 'init', array( $this, 'set_filters_from_widgets' ) );
51
52
			add_action( 'wp_enqueue_scripts', array( $this, 'load_assets' ) );
53
		} else {
54
			add_action( 'update_option', array( $this, 'track_widget_updates' ), 10, 3 );
55
		}
56
57
		add_action( 'jetpack_deactivate_module_search', array( $this, 'move_search_widgets_to_inactive' ) );
58
	}
59
60
	/**
61
	 * Loads assets for Jetpack Instant Search Prototype featuring Search As You Type experience.
62
	 */
63
	public function load_assets() {
64
		$script_relative_path = '_inc/build/instant-search/jp-search.bundle.js';
65
		if ( ! file_exists( JETPACK__PLUGIN_DIR . $script_relative_path ) ) {
66
			return;
67
		}
68
69
		$script_version = self::get_asset_version( $script_relative_path );
70
		$script_path    = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
71
		wp_enqueue_script( 'jetpack-instant-search', $script_path, array(), $script_version, true );
72
		$this->load_and_initialize_tracks();
73
		$this->load_options();
74
75
		$style_relative_path = '_inc/build/instant-search/instant-search.min.css';
76 View Code Duplication
		if ( file_exists( JETPACK__PLUGIN_DIR . $script_relative_path ) ) {
77
			$style_version = self::get_asset_version( $style_relative_path );
78
			$style_path    = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
79
			wp_enqueue_style( 'jetpack-instant-search', $style_path, array(), $style_version );
80
		}
81
	}
82
83
	/**
84
	 * Passes all options to the JS app.
85
	 */
86
	protected function load_options() {
87
		$widget_options = Jetpack_Search_Helpers::get_widgets_from_option();
88
		if ( is_array( $widget_options ) ) {
89
			$widget_options = end( $widget_options );
90
		}
91
92
		$filters = Jetpack_Search_Helpers::get_filters_from_widgets();
93
		$widgets = array();
94
		foreach ( $filters as $key => $filter ) {
95
			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
			$new_filter                                   = $filter;
100
			$new_filter['filter_id']                      = $key;
101
			$widgets[ $filter['widget_id'] ]['filters'][] = $new_filter;
102
		}
103
104
		$post_type_objs   = get_post_types( array(), 'objects' );
105
		$post_type_labels = array();
106
		foreach ( $post_type_objs as $key => $obj ) {
107
			$post_type_labels[ $key ] = array(
108
				'singular_name' => $obj->labels->singular_name,
109
				'name'          => $obj->labels->name,
110
			);
111
		}
112
113
		$prefix  = Jetpack_Search_Options::OPTION_PREFIX;
114
		$options = array(
115
			// overlay options.
116
			'colorTheme'      => (bool) get_option( $prefix . 'color_theme', 'light' ),
117
			'opacity'         => (bool) get_option( $prefix . 'opacity', 97 ),
118
			'closeColor'      => (bool) get_option( $prefix . 'close_color', '#BD3854' ),
119
			'highlightColor'  => (bool) get_option( $prefix . 'highlight_color', '#FFC' ),
120
			'enableInfScroll' => (bool) get_option( $prefix . 'inf_scroll', true ),
121
			'showLogo'        => (bool) get_option( $prefix . 'show_logo', true ),
122
			'showPoweredBy'   => (bool) get_option( $prefix . 'show_powered_by', true ),
123
124
			// core config.
125
			'homeUrl'         => home_url(),
126
			'locale'          => str_replace( '_', '-', get_locale() ),
127
			'postsPerPage'    => get_option( 'posts_per_page' ),
128
			'siteId'          => Jetpack::get_option( 'id' ),
129
130
			// filtering.
131
			'postTypeFilters' => $widget_options['post_types'],
132
			'postTypes'       => $post_type_labels,
133
			'sort'            => $widget_options['sort'],
134
			'widgets'         => array_values( $widgets ),
135
		);
136
137
		/**
138
		 * Customize Instant Search Options.
139
		 *
140
		 * @module search
141
		 *
142
		 * @since 7.7.0
143
		 *
144
		 * @param array $options Array of parameters used in Instant Search queries.
145
		 */
146
		$options = apply_filters( 'jetpack_instant_search_options', $options );
147
148
		wp_localize_script(
149
			'jetpack-instant-search',
150
			'JetpackInstantSearchOptions',
151
			$options
152
		);
153
	}
154
155
	/**
156
	 * Loads scripts for Tracks analytics library
157
	 */
158
	public function load_and_initialize_tracks() {
159
		wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
160
	}
161
162
	/**
163
	 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
164
	 *
165
	 * @param string $file Path of the file we are looking for.
166
	 * @return string $script_version Version number.
167
	 */
168
	public static function get_asset_version( $file ) {
169
		return Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $file )
170
			? filemtime( JETPACK__PLUGIN_DIR . $file )
171
			: JETPACK__VERSION;
172
	}
173
174
	/**
175
	 * Bypass the normal Search query since we will run it with instant search.
176
	 *
177
	 * @since 8.3.0
178
	 *
179
	 * @param array    $posts Current array of posts (still pre-query).
180
	 * @param WP_Query $query The WP_Query being filtered.
181
	 *
182
	 * @return array Array of matching posts.
183
	 */
184
	public function filter__posts_pre_query( $posts, $query ) {
185
		if ( ! $this->should_handle_query( $query ) ) {
186
			// Intentionally not adding the 'jetpack_search_abort' action since this should fire for every request except for search.
187
			return $posts;
188
		}
189
190
		/**
191
		 * Bypass the main query and return dummy data
192
		 *  WP Core doesn't call the set_found_posts and its filters when filtering
193
		 *  posts_pre_query like we do, so need to do these manually.
194
		 */
195
		$query->found_posts   = 1;
196
		$query->max_num_pages = 1;
197
198
		return array(
199
			new WP_Post(
200
				array(
201
					'ID'             => 1,
202
					'post_author'    => 1,
203
					'post_date'      => current_time( 'mysql' ),
204
					'post_date_gmt'  => current_time( 'mysql', 1 ),
205
					'post_title'     => 'Some title or other',
206
					'post_content'   => 'Whatever you want here. Maybe some cat pictures....',
207
					'post_status'    => 'publish',
208
					'comment_status' => 'closed',
209
					'ping_status'    => 'closed',
210
					'post_name'      => 'fake-page',
211
					'post_type'      => 'page',
212
					'filter'         => 'raw',
213
				)
214
			),
215
		);
216
	}
217
218
	/**
219
	 * Run the aggregations API query for any filtering
220
	 *
221
	 * @since 8.3.0
222
	 *
223
	 * @param WP_Query $query The WP_Query being filtered.
224
	 */
225
	public function action__parse_query( $query ) {
226
		if ( ! empty( $this->search_result ) ) {
227
			return;
228
		}
229
230
		if ( is_admin() ) {
231
			return;
232
		}
233
234
		if ( empty( $this->aggregations ) ) {
235
			return;
236
		}
237
238
		jetpack_require_lib( 'jetpack-wpes-query-builder/jetpack-wpes-query-builder' );
239
240
		$builder = new Jetpack_WPES_Query_Builder();
241
		$this->add_aggregations_to_es_query_builder( $this->aggregations, $builder );
242
		$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...
243
			array(
244
				'aggregations' => $builder->build_aggregation(),
245
				'size'         => 0,
246
				'from'         => 0,
247
			)
248
		);
249
	}
250
251
	/**
252
	 * Run an instant search on the WordPress.com public API.
253
	 *
254
	 * @since 8.3.0
255
	 *
256
	 * @param array $args Args conforming to the WP.com v1.3/sites/<blog_id>/search endpoint.
257
	 *
258
	 * @return object|WP_Error The response from the public API, or a WP_Error.
259
	 */
260
	public function instant_api( array $args ) {
261
		$start_time = microtime( true );
262
263
		// Cache locally to avoid remote request slowing the page.
264
		$transient_name = '_jetpack_instant_search_cache_' . md5( wp_json_encode( $args ) );
265
		$cache          = get_transient( $transient_name );
266
		if ( false !== $cache ) {
267
			$end_time = microtime( true );
0 ignored issues
show
Unused Code introduced by
$end_time 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...
268
			return $cache;
269
		}
270
271
		$endpoint     = sprintf( '/sites/%s/search', $this->jetpack_blog_id );
272
		$query_params = urldecode( http_build_query( $args ) );
273
		$service_url  = 'https://public-api.wordpress.com/rest/v1.3' . $endpoint . '?' . $query_params;
274
275
		$request_args = array(
276
			'timeout'    => 10,
277
			'user-agent' => 'jetpack_search',
278
		);
279
280
		$request  = wp_remote_get( $service_url, $request_args );
281
		$end_time = microtime( true );
282
283
		if ( is_wp_error( $request ) ) {
284
			return $request;
285
		}
286
287
		$response_code = wp_remote_retrieve_response_code( $request );
288
		$response      = json_decode( wp_remote_retrieve_body( $request ), true );
289
290 View Code Duplication
		if ( ! $response_code || $response_code < 200 || $response_code >= 300 ) {
291
			/**
292
			 * Fires after a search query request has failed
293
			 *
294
			 * @module search
295
			 *
296
			 * @since  5.6.0
297
			 *
298
			 * @param array Array containing the response code and response from the failed search query
299
			 */
300
			do_action(
301
				'failed_jetpack_search_query',
302
				array(
303
					'response_code' => $response_code,
304
					'json'          => $response,
305
				)
306
			);
307
308
			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...
309
		}
310
311
		$took = is_array( $response ) && ! empty( $response['took'] )
312
			? $response['took']
313
			: null;
314
315
		$query = array(
316
			'args'          => $args,
317
			'response'      => $response,
318
			'response_code' => $response_code,
319
			'elapsed_time'  => ( $end_time - $start_time ) * 1000, // Convert from float seconds to ms.
320
			'es_time'       => $took,
321
			'url'           => $service_url,
322
		);
323
324
		/**
325
		 * Fires after a search request has been performed.
326
		 *
327
		 * Includes the following info in the $query parameter:
328
		 *
329
		 * array args Array of Elasticsearch arguments for the search
330
		 * array response Raw API response, JSON decoded
331
		 * int response_code HTTP response code of the request
332
		 * float elapsed_time Roundtrip time of the search request, in milliseconds
333
		 * float es_time Amount of time Elasticsearch spent running the request, in milliseconds
334
		 * string url API url that was queried
335
		 *
336
		 * @module search
337
		 *
338
		 * @since  5.0.0
339
		 * @since  5.8.0 This action now fires on all queries instead of just successful queries.
340
		 *
341
		 * @param array $query Array of information about the query performed
342
		 */
343
		do_action( 'did_jetpack_search_query', $query );
344
345
		// Update local cache.
346
		set_transient( $transient_name, $response, 1 * HOUR_IN_SECONDS );
347
348
		return $response;
349
	}
350
351
	/**
352
	 * Get the raw Aggregation results from the Elasticsearch response.
353
	 *
354
	 * @since  8.3.0
355
	 *
356
	 * @return array Array of Aggregations performed on the search.
357
	 */
358
	public function get_search_aggregations_results() {
359
		if ( empty( $this->search_result ) || ! isset( $this->search_result['aggregations'] ) ) {
360
			return array();
361
		}
362
363
		return $this->search_result['aggregations'];
364
	}
365
366
367
}
368