Completed
Push — update/calendly-embed-code ( 001c02...b82934 )
by
unknown
08:51
created

Jetpack_Instant_Search::load_php()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
nc 2
nop 0
dl 0
loc 9
rs 9.9666
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
/**
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( $overlay_widget_ids );
89
		$widgets            = array();
90
		foreach ( $filters as $key => $filter ) {
91
			if ( ! isset( $widgets[ $filter['widget_id'] ] ) ) {
92
				$widgets[ $filter['widget_id'] ]['filters']   = array();
93
				$widgets[ $filter['widget_id'] ]['widget_id'] = $filter['widget_id'];
94
			}
95
			$new_filter                                   = $filter;
96
			$new_filter['filter_id']                      = $key;
97
			$widgets[ $filter['widget_id'] ]['filters'][] = $new_filter;
98
		}
99
100
		$post_type_objs   = get_post_types( array(), 'objects' );
101
		$post_type_labels = array();
102
		foreach ( $post_type_objs as $key => $obj ) {
103
			$post_type_labels[ $key ] = array(
104
				'singular_name' => $obj->labels->singular_name,
105
				'name'          => $obj->labels->name,
106
			);
107
		}
108
109
		$prefix  = Jetpack_Search_Options::OPTION_PREFIX;
110
		$options = array(
111
			'overlayOptions'  => array(
112
				'colorTheme'      => get_option( $prefix . 'color_theme', 'light' ),
113
				'enableInfScroll' => (bool) get_option( $prefix . 'inf_scroll', false ),
114
				'highlightColor'  => get_option( $prefix . 'highlight_color', '#FFC' ),
115
				'opacity'         => (int) get_option( $prefix . 'opacity', 97 ),
116
				'showPoweredBy'   => (bool) get_option( $prefix . 'show_powered_by', true ),
117
			),
118
119
			// core config.
120
			'homeUrl'         => home_url(),
121
			'locale'          => str_replace( '_', '-', get_locale() ),
122
			'postsPerPage'    => get_option( 'posts_per_page' ),
123
			'siteId'          => Jetpack::get_option( 'id' ),
124
125
			// filtering.
126
			'postTypeFilters' => isset( $widget_options['post_types'] ) ? $widget_options['post_types'] : array(),
127
			'postTypes'       => $post_type_labels,
128
			'sort'            => isset( $widget_options['sort'] ) ? $widget_options['sort'] : null,
129
			'widgets'         => array_values( $widgets ),
130
		);
131
132
		/**
133
		 * Customize Instant Search Options.
134
		 *
135
		 * @module search
136
		 *
137
		 * @since 7.7.0
138
		 *
139
		 * @param array $options Array of parameters used in Instant Search queries.
140
		 */
141
		$options = apply_filters( 'jetpack_instant_search_options', $options );
142
143
		// Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
144
		wp_add_inline_script( 'jetpack-instant-search', 'var JetpackInstantSearchOptions=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( $options ) ) . '"));' );
145
	}
146
147
	/**
148
	 * Registers a widget sidebar for Instant Search.
149
	 */
150
	public function register_jetpack_instant_sidebar() {
151
		$args = array(
152
			'name'          => __( 'Jetpack Search Sidebar', 'jetpack' ),
153
			'id'            => 'jetpack-instant-search-sidebar',
154
			'description'   => __( 'Customize the sidebar inside the Jetpack Search overlay', 'jetpack' ),
155
			'class'         => '',
156
			'before_widget' => '<div id="%1$s" class="widget %2$s">',
157
			'after_widget'  => '</div>',
158
			'before_title'  => '<h2 class="widgettitle">',
159
			'after_title'   => '</h2>',
160
		);
161
		register_sidebar( $args );
162
	}
163
164
	/**
165
	 * Prints Instant Search sidebar.
166
	 */
167
	public function print_instant_search_sidebar() {
168
		?>
169
		<div class="jetpack-instant-search__widget-area" style="display: none">
170
			<?php if ( is_active_sidebar( 'jetpack-instant-search-sidebar' ) ) { ?>
171
				<?php dynamic_sidebar( 'jetpack-instant-search-sidebar' ); ?>
172
			<?php } ?>
173
		</div>
174
		<?php
175
	}
176
177
	/**
178
	 * Loads scripts for Tracks analytics library
179
	 */
180
	public function load_and_initialize_tracks() {
181
		wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
182
	}
183
184
	/**
185
	 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
186
	 *
187
	 * @param string $file Path of the file we are looking for.
188
	 * @return string $script_version Version number.
189
	 */
190
	public static function get_asset_version( $file ) {
191
		return Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $file )
192
			? filemtime( JETPACK__PLUGIN_DIR . $file )
193
			: JETPACK__VERSION;
194
	}
195
196
	/**
197
	 * Bypass the normal Search query since we will run it with instant search.
198
	 *
199
	 * @since 8.3.0
200
	 *
201
	 * @param array    $posts Current array of posts (still pre-query).
202
	 * @param WP_Query $query The WP_Query being filtered.
203
	 *
204
	 * @return array Array of matching posts.
205
	 */
206
	public function filter__posts_pre_query( $posts, $query ) {
207
		if ( ! $this->should_handle_query( $query ) ) {
208
			// Intentionally not adding the 'jetpack_search_abort' action since this should fire for every request except for search.
209
			return $posts;
210
		}
211
212
		/**
213
		 * Bypass the main query and return dummy data
214
		 *  WP Core doesn't call the set_found_posts and its filters when filtering
215
		 *  posts_pre_query like we do, so need to do these manually.
216
		 */
217
		$query->found_posts   = 1;
218
		$query->max_num_pages = 1;
219
220
		return array();
221
	}
222
223
	/**
224
	 * Run the aggregations API query for any filtering
225
	 *
226
	 * @since 8.3.0
227
	 *
228
	 * @param WP_Query $query The WP_Query being filtered.
229
	 */
230
	public function action__parse_query( $query ) {
231
		if ( ! empty( $this->search_result ) ) {
232
			return;
233
		}
234
235
		if ( is_admin() ) {
236
			return;
237
		}
238
239
		if ( empty( $this->aggregations ) ) {
240
			return;
241
		}
242
243
		jetpack_require_lib( 'jetpack-wpes-query-builder/jetpack-wpes-query-builder' );
244
245
		$builder = new Jetpack_WPES_Query_Builder();
246
		$this->add_aggregations_to_es_query_builder( $this->aggregations, $builder );
247
		$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...
248
			array(
249
				'aggregations' => $builder->build_aggregation(),
250
				'size'         => 0,
251
				'from'         => 0,
252
			)
253
		);
254
	}
255
256
	/**
257
	 * Run an instant search on the WordPress.com public API.
258
	 *
259
	 * @since 8.3.0
260
	 *
261
	 * @param array $args Args conforming to the WP.com v1.3/sites/<blog_id>/search endpoint.
262
	 *
263
	 * @return object|WP_Error The response from the public API, or a WP_Error.
264
	 */
265
	public function instant_api( array $args ) {
266
		global $wp_version;
267
		$start_time = microtime( true );
268
269
		// Cache locally to avoid remote request slowing the page.
270
		$transient_name = 'jetpack_instant_search_cache_' . md5( wp_json_encode( $args ) );
271
		$cache          = get_transient( $transient_name );
272
		if ( false !== $cache ) {
273
			return $cache;
274
		}
275
276
		$service_url = add_query_arg(
277
			$args,
278
			sprintf(
279
				'https://public-api.wordpress.com/rest/v1.3/sites/%d/search',
280
				$this->jetpack_blog_id
281
			)
282
		);
283
284
		$request_args = array(
285
			'timeout'    => 10,
286
			'user-agent' => "WordPress/{$wp_version} | Jetpack/" . constant( 'JETPACK__VERSION' ),
287
		);
288
289
		$request  = wp_remote_get( esc_url_raw( $service_url ), $request_args );
290
		$end_time = microtime( true );
291
292
		if ( is_wp_error( $request ) ) {
293
			return $request;
294
		}
295
296
		$response_code = wp_remote_retrieve_response_code( $request );
297
		$response      = json_decode( wp_remote_retrieve_body( $request ), true );
298
299 View Code Duplication
		if ( ! $response_code || $response_code < 200 || $response_code >= 300 ) {
300
			/**
301
			 * Fires after a search query request has failed
302
			 *
303
			 * @module search
304
			 *
305
			 * @since  5.6.0
306
			 *
307
			 * @param array Array containing the response code and response from the failed search query
308
			 */
309
			do_action(
310
				'failed_jetpack_search_query',
311
				array(
312
					'response_code' => $response_code,
313
					'json'          => $response,
314
				)
315
			);
316
317
			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...
318
		}
319
320
		$took = is_array( $response ) && ! empty( $response['took'] )
321
			? $response['took']
322
			: null;
323
324
		$query = array(
325
			'args'          => $args,
326
			'response'      => $response,
327
			'response_code' => $response_code,
328
			'elapsed_time'  => ( $end_time - $start_time ) * 1000, // Convert from float seconds to ms.
329
			'es_time'       => $took,
330
			'url'           => $service_url,
331
		);
332
333
		/**
334
		 * Fires after a search request has been performed.
335
		 *
336
		 * Includes the following info in the $query parameter:
337
		 *
338
		 * array args Array of Elasticsearch arguments for the search
339
		 * array response Raw API response, JSON decoded
340
		 * int response_code HTTP response code of the request
341
		 * float elapsed_time Roundtrip time of the search request, in milliseconds
342
		 * float es_time Amount of time Elasticsearch spent running the request, in milliseconds
343
		 * string url API url that was queried
344
		 *
345
		 * @module search
346
		 *
347
		 * @since  5.0.0
348
		 * @since  5.8.0 This action now fires on all queries instead of just successful queries.
349
		 *
350
		 * @param array $query Array of information about the query performed
351
		 */
352
		do_action( 'did_jetpack_search_query', $query );
353
354
		// Update local cache.
355
		set_transient( $transient_name, $response, 1 * HOUR_IN_SECONDS );
356
357
		return $response;
358
	}
359
360
	/**
361
	 * Get the raw Aggregation results from the Elasticsearch response.
362
	 *
363
	 * @since  8.3.0
364
	 *
365
	 * @return array Array of Aggregations performed on the search.
366
	 */
367
	public function get_search_aggregations_results() {
368
		if ( empty( $this->search_result ) || is_wp_error( $this->search_result ) || ! isset( $this->search_result['aggregations'] ) ) {
369
			return array();
370
		}
371
372
		return $this->search_result['aggregations'];
373
	}
374
375
376
}
377