PluginProbe
ElasticPress / 5.3.5
ElasticPress v5.3.5
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / includes / classes / Indexable / Term / QueryIntegration.php

QueryIntegration.php in ElasticPress 5.3.5, at includes/classes/Indexable/Term/QueryIntegration.php

423 lines 11.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Integrate with WP_Term_Query
4 *
5 * @since 3.1
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\Indexable\Term;
10
11 use WP_Term_Query;
12 use ElasticPress\Indexables;
13 use ElasticPress\Utils;
14
15 if ( ! defined( 'ABSPATH' ) ) {
16 exit; // Exit if accessed directly.
17 }
18
19 /**
20 * Query integration class
21 */
22 class QueryIntegration {
23
24 /**
25 * Checks to see if we should be integrating and if so, sets up the appropriate actions and filters.
26 *
27 * @param string $indexable_slug Indexable slug. Optional.
28 *
29 * @since 3.1
30 * @since 3.6.0 Added $indexable_slug
31 */
32 public function __construct( $indexable_slug = 'term' ) {
33 /**
34 * Filter whether to enable query integration during indexing
35 *
36 * @since 4.5.2
37 * @hook ep_enable_query_integration_during_indexing
38 *
39 * @param {bool} $enable To allow query integration during indexing
40 * @param {string} $indexable_slug Indexable slug
41 * @return {bool} New value
42 */
43 $allow_query_integration_during_indexing = apply_filters( 'ep_enable_query_integration_during_indexing', false, $indexable_slug );
44
45 // Ensure that we are currently allowing ElasticPress to override the normal WP_Query
46 // Indexable->is_full_reindexing() is not available at this point yet, so using the IndexHelper version of it.
47 if ( \ElasticPress\IndexHelper::factory()->is_full_reindexing( $indexable_slug, get_current_blog_id() ) && ! $allow_query_integration_during_indexing ) {
48 return;
49 }
50
51 // Add header
52 add_action( 'pre_get_terms', array( $this, 'action_pre_get_terms' ), 5 );
53
54 // Filter term query
55 add_filter( 'terms_pre_query', [ $this, 'maybe_filter_query' ], 10, 2 );
56
57 add_filter( 'rest_post_tag_query', [ $this, 'maybe_set_search_fields' ], 10, 2 );
58 }
59
60 /**
61 * Add EP header
62 *
63 * @param WP_Term_Query $query Query object
64 * @since 3.1
65 * @return void
66 */
67 public function action_pre_get_terms( WP_Term_Query $query ) {
68 if ( ! Indexables::factory()->get( 'term' )->elasticpress_enabled( $query ) || apply_filters( 'ep_skip_term_query_integration', false, $query ) ) {
69 return;
70 }
71
72 if ( ! headers_sent() ) {
73 /**
74 * Manually setting a header as $wp_query isn't yet initialized
75 * when we call: add_filter('wp_headers', 'filter_wp_headers');
76 */
77 header( 'X-ElasticPress-Term-Search: true' );
78 }
79 }
80
81 /**
82 * If WP_Term_Query meets certain conditions, query results from ES
83 *
84 * @param array $results Term results.
85 * @param WP_Term_Query $query Current query.
86 * @since 3.1
87 * @return array
88 */
89 public function maybe_filter_query( $results, WP_Term_Query $query ) {
90 $indexable = Indexables::factory()->get( 'term' );
91
92 if ( ! $indexable->elasticpress_enabled( $query ) || apply_filters( 'ep_skip_term_query_integration', false, $query ) ) {
93 return $results;
94 }
95
96 if ( ! $this->is_searchable( $query ) ) {
97 return $results;
98 }
99
100 $new_terms = apply_filters( 'ep_wp_query_cached_terms', null, $query );
101
102 if ( null === $new_terms ) {
103 $formatted_args = $indexable->format_args( $query->query_vars );
104
105 $scope = 'current';
106
107 $site__in = [];
108 $site__not_in = [];
109
110 if ( ! empty( $query->query_vars['sites'] ) ) {
111 _deprecated_argument( __FUNCTION__, '4.4.0', esc_html__( 'sites is deprecated. Use site__in instead.', 'elasticpress' ) );
112 }
113
114 if ( ! empty( $query->query_vars['site__in'] ) || ! empty( $query->query_vars['sites'] ) ) {
115 $site__in = ! empty( $query->query_vars['site__in'] ) ? (array) $query->query_vars['site__in'] : (array) $query->query_vars['sites'];
116
117 if ( in_array( 'all', $site__in, true ) ) {
118 $scope = 'all';
119 } elseif ( in_array( 'current', $site__in, true ) ) {
120 $site__in = (array) get_current_blog_id();
121 }
122 }
123
124 if ( ! empty( $query->query_vars['site__not_in'] ) ) {
125 $site__not_in = (array) $query->query_vars['site__not_in'];
126 }
127
128 /**
129 * Filter search scope
130 *
131 * @since 3.1
132 *
133 * @param mixed $scope The search scope. Accepts `all` (string), a single
134 * site id (int or string), or an array of site ids (array).
135 */
136 $scope = apply_filters( 'ep_term_search_scope', $scope );
137
138 if ( ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) {
139 $scope = 'current';
140 }
141
142 $index = null;
143
144 if ( 'all' === $scope ) {
145 $index = $indexable->get_network_alias();
146 } elseif ( ! empty( $site__in ) ) {
147 $index = [];
148
149 foreach ( $site__in as $site_id ) {
150 $index[] = $indexable->get_index_name( $site_id );
151 }
152
153 $index = implode( ',', $index );
154 } elseif ( ! empty( $site__not_in ) ) {
155
156 $sites = \get_sites(
157 array(
158 'fields' => 'ids',
159 'site__not_in' => $site__not_in,
160 )
161 );
162 foreach ( $sites as $site_id ) {
163 if ( ! Utils\is_site_indexable( $site_id ) ) {
164 continue;
165 }
166 $index[] = Indexables::factory()->get( 'term' )->get_index_name( $site_id );
167 }
168 $index = implode( ',', $index );
169 }
170
171 $ep_query = $indexable->query_es( $formatted_args, $query->query_vars, $index );
172
173 if ( false === $ep_query ) {
174 $query->elasticsearch_success = false;
175 return $results;
176 }
177
178 /**
179 * Elasticsearch 5 will return found_documents as a number,
180 * ES 7+ will return it as an object with `value`. If any of that is found,
181 * fallback to a simple count of returned documents.
182 *
183 * @since 3.6.3
184 */
185 if ( is_integer( $ep_query['found_documents'] ) ) {
186 $query->found_terms = $ep_query['found_documents'];
187 } elseif ( is_array( $ep_query['found_documents'] ) && isset( $ep_query['found_documents']['value'] ) ) {
188 $query->found_terms = $ep_query['found_documents']['value'];
189 } else {
190 $query->found_terms = count( $ep_query['documents'] );
191 }
192
193 $query->elasticsearch_success = true;
194
195 // Determine how we should format the results from ES based on the fields parameter.
196 $fields = $query->query_vars['fields'];
197
198 $new_terms = [];
199
200 switch ( $fields ) {
201 case 'all_with_object_id':
202 $new_terms = $this->format_hits_as_terms( $ep_query['documents'], $new_terms, $query->query_vars );
203 break;
204
205 case 'count':
206 $new_terms = count( $ep_query['documents'] );
207 break;
208
209 case 'ids':
210 $new_terms = $this->format_hits_as_ids( $ep_query['documents'], $new_terms );
211 break;
212
213 case 'id=>name':
214 $new_terms = $this->format_hits_as_id_name( $ep_query['documents'], $new_terms );
215 break;
216
217 case 'id=>parent':
218 $new_terms = $this->format_hits_as_id_parent( $ep_query['documents'], $new_terms );
219 break;
220
221 case 'id=>slug':
222 $new_terms = $this->format_hits_as_id_slug( $ep_query['documents'], $new_terms );
223 break;
224
225 case 'names':
226 $new_terms = $this->format_hits_as_names( $ep_query['documents'], $new_terms );
227 break;
228
229 case 'tt_ids':
230 $new_terms = $this->format_hits_as_ids( $ep_query['documents'], $new_terms, 'term_taxonomy_id' );
231 break;
232
233 default:
234 $new_terms = $this->format_hits_as_terms( $ep_query['documents'], $new_terms, $query->query_vars );
235 break;
236 }
237
238 if ( ! empty( $query->query_vars['count'] ) ) {
239 $new_terms = count( $ep_query['documents'] );
240 }
241 }
242
243 return $new_terms;
244 }
245
246 /**
247 * Conditionally set search fields for term queries in REST API requests.
248 *
249 * If in an REST API request that came from WordPress edit screen, do not search for term description.
250 *
251 * @param array $prepared_args Array of arguments for get_terms().
252 * @param WP_REST_Request $request The REST API request.
253 * @return array
254 */
255 public function maybe_set_search_fields( $prepared_args, $request ) {
256 $referer = $request->get_header( 'referer' );
257
258 if ( empty( $referer ) || ! preg_match( '/post\.php\?post=([0-9]*)&action=edit/', $referer ) ) {
259 return $prepared_args;
260 }
261
262 $prepared_args['search_fields'] = [ 'name', 'slug' ];
263
264 return $prepared_args;
265 }
266
267 /**
268 * Format the ES hits/results as term objects.
269 *
270 * @param array $terms The terms that should be formatted.
271 * @param array $new_terms Array of terms from cache.
272 * @param array $query_vars Query variables.
273 * @since 3.1
274 * @return array
275 */
276 protected function format_hits_as_terms( $terms, $new_terms, $query_vars ) {
277 foreach ( $terms as $term_array ) {
278 $term = new \stdClass();
279
280 $term->ID = $term_array['term_id'];
281 $term->site_id = get_current_blog_id();
282
283 if ( ! empty( $term_array['site_id'] ) ) {
284 $term->site_id = $term_array['site_id'];
285 }
286
287 $term_return_args = apply_filters(
288 'ep_search_term_return_args',
289 array(
290 'term_id',
291 'name',
292 'slug',
293 'term_group',
294 'term_taxonomy_id',
295 'taxonomy',
296 'description',
297 'parent',
298 'count',
299 'meta',
300 )
301 );
302
303 foreach ( $term_return_args as $key ) {
304 if ( isset( $term_array[ $key ] ) ) {
305 if ( 'count' === $key && ! empty( $query_vars['pad_counts'] ) ) {
306 $term_array[ $key ] += $term_array['hierarchy']['children']['count'];
307 }
308
309 $term->$key = $term_array[ $key ];
310 }
311 }
312
313 $term->elasticsearch = true; // Super useful for debugging.
314
315 $term = new \WP_Term( $term ); // Necessary for WordPress actions that expect WP_Term as the object type.
316
317 if ( $term ) {
318 $new_terms[] = $term;
319 }
320 }
321
322 return $new_terms;
323 }
324
325 /**
326 * Format the ES hits/results as an array of ids.
327 *
328 * @param array $terms The terms that should be formatted.
329 * @param array $new_terms Array of terms from cache.
330 * @param string $type Type of ID to return. Default 'term_id'.
331 * @since 3.1
332 * @return array
333 */
334 protected function format_hits_as_ids( $terms, $new_terms, $type = 'term_id' ) {
335 foreach ( $terms as $term_array ) {
336 $new_terms[] = 'term_id' === $type ? $term_array['term_id'] : $term_array['term_taxonomy_id'];
337 }
338
339 return $new_terms;
340 }
341
342 /**
343 * Format the ES hits/results as an array of term ID and term name.
344 *
345 * @param array $terms The terms that should be formatted.
346 * @param array $new_terms Array of terms from cache.
347 * @since 3.1
348 * @return array Returns an associative array with ids as keys, term names as values
349 */
350 protected function format_hits_as_id_name( $terms, $new_terms ) {
351 foreach ( $terms as $term_array ) {
352 $new_terms[ $term_array['term_id'] ] = $term_array['name'];
353 }
354
355 return $new_terms;
356 }
357
358 /**
359 * Format the ES hits/results as an array of term ID and parent ID.
360 *
361 * @param array $terms The terms that should be formatted.
362 * @param array $new_terms Array of terms from cache.
363 * @since 3.1
364 * @return array Returns an associative array with ids as keys, parent term IDs as values
365 */
366 protected function format_hits_as_id_parent( $terms, $new_terms ) {
367 foreach ( $terms as $term_array ) {
368 $new_terms[ $term_array['term_id'] ] = $term_array['parent'];
369 }
370
371 return $new_terms;
372 }
373
374 /**
375 * Format the ES hits/results as an array of term ID and term slug.
376 *
377 * @param array $terms The terms that should be formatted.
378 * @param array $new_terms Array of terms from cache.
379 * @since 3.1
380 * @return array Returns an associative array with ids as keys, term slugs as values
381 */
382 protected function format_hits_as_id_slug( $terms, $new_terms ) {
383 foreach ( $terms as $term_array ) {
384 $new_terms[ $term_array['term_id'] ] = $term_array['slug'];
385 }
386
387 return $new_terms;
388 }
389
390 /**
391 * Format the ES hits/results as an array of term names.
392 *
393 * @param array $terms The terms that should be formatted.
394 * @param array $new_terms Array of terms from cache.
395 * @since 3.1
396 * @return array Returns an array of term names.
397 */
398 protected function format_hits_as_names( $terms, $new_terms ) {
399 foreach ( $terms as $term_array ) {
400 $new_terms[] = $term_array['name'];
401 }
402
403 return $new_terms;
404 }
405
406 /**
407 * Determine whether ES should be used for the query if all taxonomies are indexable.
408 *
409 * @param \WP_Term_Query $query The WP_Term_Query object.
410 * @return boolean
411 */
412 protected function is_searchable( $query ) {
413
414 $taxonomies = $query->query_vars['taxonomy'];
415 if ( ! $taxonomies ) {
416 return true;
417 }
418
419 $indexable_taxonomies = Indexables::factory()->get( 'term' )->get_indexable_taxonomies();
420 return empty( array_diff( $taxonomies, $indexable_taxonomies ) );
421 }
422 }
423