Completed
Push — update/refactor-search-php ( d6a69c )
by
unknown
07:06
created

Jetpack_Instant_Search::init_hooks()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

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