Completed
Push — renovate/glob-7.x ( 8eddcc...56cd69 )
by
unknown
34:58 queued 27:53
created

get_search_aggregations_results()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 2
nop 0
dl 0
loc 7
rs 10
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
	 * Loads the php for this version of search
16
	 *
17
	 * @since 8.3.0
18
	 */
19
	public function load_php() {
20
		require_once dirname( __FILE__ ) . '/class.jetpack-search-template-tags.php';
21
		require_once JETPACK__PLUGIN_DIR . 'modules/widgets/search.php';
22
23
		if ( class_exists( 'WP_Customize_Manager' ) ) {
24
			require_once dirname( __FILE__ ) . '/class-jetpack-search-customize.php';
25
			new Jetpack_Search_Customize();
26
		}
27
	}
28
29
	/**
30
	 * Setup the various hooks needed for the plugin to take over search duties.
31
	 *
32
	 * @since 5.0.0
33
	 */
34 View Code Duplication
	public function init_hooks() {
35
		if ( ! is_admin() ) {
36
			add_filter( 'posts_pre_query', array( $this, 'filter__posts_pre_query' ), 10, 2 );
37
			add_action( 'parse_query', array( $this, 'action__parse_query' ), 10, 1 );
38
39
			add_action( 'init', array( $this, 'set_filters_from_widgets' ) );
40
41
			add_action( 'wp_enqueue_scripts', array( $this, 'load_assets' ) );
42
			add_action( 'wp_footer', array( $this, 'print_instant_search_sidebar' ) );
43
		} else {
44
			add_action( 'update_option', array( $this, 'track_widget_updates' ), 10, 3 );
45
		}
46
47
		add_action( 'widgets_init', array( $this, 'register_jetpack_instant_sidebar' ) );
48
		add_action( 'jetpack_deactivate_module_search', array( $this, 'move_search_widgets_to_inactive' ) );
49
	}
50
51
	/**
52
	 * Loads assets for Jetpack Instant Search Prototype featuring Search As You Type experience.
53
	 */
54
	public function load_assets() {
55
		$script_relative_path = '_inc/build/instant-search/jp-search.bundle.js';
56
		$style_relative_path  = '_inc/build/instant-search/instant-search.min.css';
57
		if ( ! file_exists( JETPACK__PLUGIN_DIR . $script_relative_path ) || ! file_exists( JETPACK__PLUGIN_DIR . $style_relative_path ) ) {
58
			return;
59
		}
60
61
		$script_version = self::get_asset_version( $script_relative_path );
62
		$script_path    = plugins_url( $script_relative_path, JETPACK__PLUGIN_FILE );
63
		wp_enqueue_script( 'jetpack-instant-search', $script_path, array(), $script_version, true );
64
		$this->load_and_initialize_tracks();
65
		$this->inject_javascript_options();
66
67
		$style_version = self::get_asset_version( $style_relative_path );
68
		$style_path    = plugins_url( $style_relative_path, JETPACK__PLUGIN_FILE );
69
		wp_enqueue_style( 'jetpack-instant-search', $style_path, array(), $style_version );
70
	}
71
72
	/**
73
	 * Passes all options to the JS app.
74
	 */
75
	protected function inject_javascript_options() {
76
		$widget_options = Jetpack_Search_Helpers::get_widgets_from_option();
77
		if ( is_array( $widget_options ) ) {
78
			$widget_options = end( $widget_options );
79
		}
80
81
		$overlay_widget_ids = get_option( 'sidebars_widgets', array() )['jetpack-instant-search-sidebar'];
82
		$filters            = Jetpack_Search_Helpers::get_filters_from_widgets( $overlay_widget_ids );
83
		$widgets            = array();
84
		foreach ( $filters as $key => $filter ) {
85
			if ( ! isset( $widgets[ $filter['widget_id'] ] ) ) {
86
				$widgets[ $filter['widget_id'] ]['filters']   = array();
87
				$widgets[ $filter['widget_id'] ]['widget_id'] = $filter['widget_id'];
88
			}
89
			$new_filter                                   = $filter;
90
			$new_filter['filter_id']                      = $key;
91
			$widgets[ $filter['widget_id'] ]['filters'][] = $new_filter;
92
		}
93
94
		$post_type_objs   = get_post_types( array(), 'objects' );
95
		$post_type_labels = array();
96
		foreach ( $post_type_objs as $key => $obj ) {
97
			$post_type_labels[ $key ] = array(
98
				'singular_name' => $obj->labels->singular_name,
99
				'name'          => $obj->labels->name,
100
			);
101
		}
102
103
		$prefix  = Jetpack_Search_Options::OPTION_PREFIX;
104
		$options = array(
105
			'overlayOptions'  => array(
106
				'closeColor'      => get_option( $prefix . 'close_color', '#BD3854' ),
107
				'colorTheme'      => get_option( $prefix . 'color_theme', 'light' ),
108
				'enableInfScroll' => (bool) get_option( $prefix . 'inf_scroll', false ),
109
				'highlightColor'  => get_option( $prefix . 'highlight_color', '#FFC' ),
110
				'opacity'         => (int) get_option( $prefix . 'opacity', 97 ),
111
				'showLogo'        => (bool) get_option( $prefix . 'show_logo', true ),
112
				'showPoweredBy'   => (bool) get_option( $prefix . 'show_powered_by', true ),
113
			),
114
115
			// core config.
116
			'homeUrl'         => home_url(),
117
			'locale'          => str_replace( '_', '-', get_locale() ),
118
			'postsPerPage'    => get_option( 'posts_per_page' ),
119
			'siteId'          => Jetpack::get_option( 'id' ),
120
121
			// filtering.
122
			'postTypeFilters' => $widget_options['post_types'],
123
			'postTypes'       => $post_type_labels,
124
			'sort'            => $widget_options['sort'],
125
			'widgets'         => array_values( $widgets ),
126
		);
127
128
		/**
129
		 * Customize Instant Search Options.
130
		 *
131
		 * @module search
132
		 *
133
		 * @since 7.7.0
134
		 *
135
		 * @param array $options Array of parameters used in Instant Search queries.
136
		 */
137
		$options = apply_filters( 'jetpack_instant_search_options', $options );
138
139
		// Use wp_add_inline_script instead of wp_localize_script, see https://core.trac.wordpress.org/ticket/25280.
140
		wp_add_inline_script( 'jetpack-instant-search', 'var JetpackInstantSearchOptions=JSON.parse(decodeURIComponent("' . rawurlencode( wp_json_encode( $options ) ) . '"));' );
141
	}
142
143
	/**
144
	 * Registers a widget sidebar for Instant Search.
145
	 */
146
	public function register_jetpack_instant_sidebar() {
147
		$args = array(
148
			'name'          => __( 'Jetpack Search Sidebar', 'jetpack' ),
149
			'id'            => 'jetpack-instant-search-sidebar',
150
			'description'   => __( 'Customize the sidebar inside the Jetpack Search overlay', 'jetpack' ),
151
			'class'         => '',
152
			'before_widget' => '<div id="%1$s" class="widget %2$s">',
153
			'after_widget'  => '</div>',
154
			'before_title'  => '<h2 class="widgettitle">',
155
			'after_title'   => '</h2>',
156
		);
157
		register_sidebar( $args );
158
	}
159
160
	/**
161
	 * Prints Instant Search sidebar.
162
	 */
163
	public function print_instant_search_sidebar() {
164
		?>
165
		<div class="jetpack-instant-search__widget-area" style="display: none">
166
			<?php if ( is_active_sidebar( 'jetpack-instant-search-sidebar' ) ) { ?>
167
				<?php dynamic_sidebar( 'jetpack-instant-search-sidebar' ); ?>
168
			<?php } ?>
169
		</div>
170
		<?php
171
	}
172
173
	/**
174
	 * Loads scripts for Tracks analytics library
175
	 */
176
	public function load_and_initialize_tracks() {
177
		wp_enqueue_script( 'jp-tracks', '//stats.wp.com/w.js', array(), gmdate( 'YW' ), true );
178
	}
179
180
	/**
181
	 * Get the version number to use when loading the file. Allows us to bypass cache when developing.
182
	 *
183
	 * @param string $file Path of the file we are looking for.
184
	 * @return string $script_version Version number.
185
	 */
186
	public static function get_asset_version( $file ) {
187
		return Jetpack::is_development_version() && file_exists( JETPACK__PLUGIN_DIR . $file )
188
			? filemtime( JETPACK__PLUGIN_DIR . $file )
189
			: JETPACK__VERSION;
190
	}
191
192
	/**
193
	 * Bypass the normal Search query since we will run it with instant search.
194
	 *
195
	 * @since 8.3.0
196
	 *
197
	 * @param array    $posts Current array of posts (still pre-query).
198
	 * @param WP_Query $query The WP_Query being filtered.
199
	 *
200
	 * @return array Array of matching posts.
201
	 */
202
	public function filter__posts_pre_query( $posts, $query ) {
203
		if ( ! $this->should_handle_query( $query ) ) {
204
			// Intentionally not adding the 'jetpack_search_abort' action since this should fire for every request except for search.
205
			return $posts;
206
		}
207
208
		/**
209
		 * Bypass the main query and return dummy data
210
		 *  WP Core doesn't call the set_found_posts and its filters when filtering
211
		 *  posts_pre_query like we do, so need to do these manually.
212
		 */
213
		$query->found_posts   = 1;
214
		$query->max_num_pages = 1;
215
216
		return array();
217
	}
218
219
	/**
220
	 * Run the aggregations API query for any filtering
221
	 *
222
	 * @since 8.3.0
223
	 *
224
	 * @param WP_Query $query The WP_Query being filtered.
225
	 */
226
	public function action__parse_query( $query ) {
227
		if ( ! empty( $this->search_result ) ) {
228
			return;
229
		}
230
231
		if ( is_admin() ) {
232
			return;
233
		}
234
235
		if ( empty( $this->aggregations ) ) {
236
			return;
237
		}
238
239
		jetpack_require_lib( 'jetpack-wpes-query-builder/jetpack-wpes-query-builder' );
240
241
		$builder = new Jetpack_WPES_Query_Builder();
242
		$this->add_aggregations_to_es_query_builder( $this->aggregations, $builder );
243
		$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...
244
			array(
245
				'aggregations' => $builder->build_aggregation(),
246
				'size'         => 0,
247
				'from'         => 0,
248
			)
249
		);
250
	}
251
252
	/**
253
	 * Run an instant search on the WordPress.com public API.
254
	 *
255
	 * @since 8.3.0
256
	 *
257
	 * @param array $args Args conforming to the WP.com v1.3/sites/<blog_id>/search endpoint.
258
	 *
259
	 * @return object|WP_Error The response from the public API, or a WP_Error.
260
	 */
261
	public function instant_api( array $args ) {
262
		$start_time = microtime( true );
263
264
		// Cache locally to avoid remote request slowing the page.
265
		$transient_name = '_jetpack_instant_search_cache_' . md5( wp_json_encode( $args ) );
266
		$cache          = get_transient( $transient_name );
267
		if ( false !== $cache ) {
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 ) || is_wp_error( $this->search_result ) || ! isset( $this->search_result['aggregations'] ) ) {
360
			return array();
361
		}
362
363
		return $this->search_result['aggregations'];
364
	}
365
366
367
}
368