PluginProbe
ElasticPress / 4.5.1
ElasticPress v4.5.1
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 4.5.1, at includes/classes/Indexable/Term/QueryIntegration.php

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