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

387 lines 10.6 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
98 _deprecated_argument( __FUNCTION__, '4.4.0', esc_html__( 'sites is deprecated. Use site__in instead.', 'elasticpress' ) );
99 $site__in = (array) $query->query_vars['sites'];
100 $scope = 'all' === $query->query_vars['sites'] ? 'all' : $site__in;
101 }
102
103 if ( ! empty( $query->query_vars['site__in'] ) ) {
104 $site__in = (array) $query->query_vars['site__in'];
105 $scope = 'all' === $query->query_vars['site__in'] ? 'all' : $site__in;
106 }
107
108 if ( ! empty( $query->query_vars['site__not_in'] ) ) {
109 $site__not_in = (array) $query->query_vars['site__not_in'];
110 }
111
112 /**
113 * Filter search scope
114 *
115 * @since 3.1
116 *
117 * @param mixed $scope The search scope. Accepts `all` (string), a single
118 * site id (int or string), or an array of site ids (array).
119 */
120 $scope = apply_filters( 'ep_term_search_scope', $scope );
121
122 if ( ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) {
123 $scope = 'current';
124 }
125
126 $index = null;
127
128 if ( 'all' === $scope ) {
129 $index = $indexable->get_network_alias();
130 } elseif ( ! empty( $site__in ) ) {
131 $index = [];
132
133 foreach ( $site__in as $site_id ) {
134 $index[] = $indexable->get_index_name( $site_id );
135 }
136
137 $index = implode( ',', $index );
138 } elseif ( ! empty( $site__not_in ) ) {
139
140 $sites = get_sites(
141 array(
142 'fields' => 'ids',
143 'site__not_in' => $site__not_in,
144 )
145 );
146 foreach ( $sites as $site_id ) {
147 if ( ! Utils\is_site_indexable( $site_id ) ) {
148 continue;
149 }
150 $index[] = Indexables::factory()->get( 'term' )->get_index_name( $site_id );
151 }
152 $index = implode( ',', $index );
153 }
154
155 $ep_query = $indexable->query_es( $formatted_args, $query->query_vars, $index );
156
157 if ( false === $ep_query ) {
158 $query->elasticsearch_success = false;
159 return $results;
160 }
161
162 /**
163 * Elasticsearch 5 will return found_documents as a number,
164 * ES 7+ will return it as an object with `value`. If any of that is found,
165 * fallback to a simple count of returned documents.
166 *
167 * @since 3.6.3
168 */
169 if ( is_integer( $ep_query['found_documents'] ) ) {
170 $query->found_terms = $ep_query['found_documents'];
171 } elseif ( is_array( $ep_query['found_documents'] ) && isset( $ep_query['found_documents']['value'] ) ) {
172 $query->found_terms = $ep_query['found_documents']['value'];
173 } else {
174 $query->found_terms = count( $ep_query['documents'] );
175 }
176
177 $query->elasticsearch_success = true;
178
179 // Determine how we should format the results from ES based on the fields parameter.
180 $fields = $query->query_vars['fields'];
181
182 $new_terms = [];
183
184 switch ( $fields ) {
185 case 'all_with_object_id':
186 $new_terms = $this->format_hits_as_terms( $ep_query['documents'], $new_terms, $query->query_vars );
187 break;
188
189 case 'count':
190 $new_terms = count( $ep_query['documents'] );
191 break;
192
193 case 'ids':
194 $new_terms = $this->format_hits_as_ids( $ep_query['documents'], $new_terms );
195 break;
196
197 case 'id=>name':
198 $new_terms = $this->format_hits_as_id_name( $ep_query['documents'], $new_terms );
199 break;
200
201 case 'id=>parent':
202 $new_terms = $this->format_hits_as_id_parent( $ep_query['documents'], $new_terms );
203 break;
204
205 case 'id=>slug':
206 $new_terms = $this->format_hits_as_id_slug( $ep_query['documents'], $new_terms );
207 break;
208
209 case 'names':
210 $new_terms = $this->format_hits_as_names( $ep_query['documents'], $new_terms );
211 break;
212
213 case 'tt_ids':
214 $new_terms = $this->format_hits_as_ids( $ep_query['documents'], $new_terms, 'term_taxonomy_id' );
215 break;
216
217 default:
218 $new_terms = $this->format_hits_as_terms( $ep_query['documents'], $new_terms, $query->query_vars );
219 break;
220 }
221
222 if ( ! empty( $query->query_vars['count'] ) ) {
223 $new_terms = count( $ep_query['documents'] );
224 }
225 }
226
227 return $new_terms;
228 }
229
230 /**
231 * Format the ES hits/results as term objects.
232 *
233 * @param array $terms The terms that should be formatted.
234 * @param array $new_terms Array of terms from cache.
235 * @param array $query_vars Query variables.
236 * @since 3.1
237 * @return array
238 */
239 protected function format_hits_as_terms( $terms, $new_terms, $query_vars ) {
240 foreach ( $terms as $term_array ) {
241 $term = new \stdClass();
242
243 $term->ID = $term_array['term_id'];
244 $term->site_id = get_current_blog_id();
245
246 if ( ! empty( $term_array['site_id'] ) ) {
247 $term->site_id = $term_array['site_id'];
248 }
249
250 $term_return_args = apply_filters(
251 'ep_search_term_return_args',
252 array(
253 'term_id',
254 'name',
255 'slug',
256 'term_group',
257 'term_taxonomy_id',
258 'taxonomy',
259 'description',
260 'parent',
261 'count',
262 'meta',
263 )
264 );
265
266 foreach ( $term_return_args as $key ) {
267 if ( isset( $term_array[ $key ] ) ) {
268 if ( 'count' === $key && ! empty( $query_vars['pad_counts'] ) ) {
269 $term_array[ $key ] += $term_array['hierarchy']['children']['count'];
270 }
271
272 $term->$key = $term_array[ $key ];
273 }
274 }
275
276 $term->elasticsearch = true; // Super useful for debugging.
277
278 $term = new \WP_Term( $term ); // Necessary for WordPress actions that expect WP_Term as the object type.
279
280 if ( $term ) {
281 $new_terms[] = $term;
282 }
283 }
284
285 return $new_terms;
286 }
287
288 /**
289 * Format the ES hits/results as an array of ids.
290 *
291 * @param array $terms The terms that should be formatted.
292 * @param array $new_terms Array of terms from cache.
293 * @param string $type Type of ID to return. Default 'term_id'.
294 * @since 3.1
295 * @return array
296 */
297 protected function format_hits_as_ids( $terms, $new_terms, $type = 'term_id' ) {
298 foreach ( $terms as $term_array ) {
299 $new_terms[] = 'term_id' === $type ? $term_array['term_id'] : $term_array['term_taxonomy_id'];
300 }
301
302 return $new_terms;
303 }
304
305 /**
306 * Format the ES hits/results as an array of term ID and term name.
307 *
308 * @param array $terms The terms that should be formatted.
309 * @param array $new_terms Array of terms from cache.
310 * @since 3.1
311 * @return array Returns an associative array with ids as keys, term names as values
312 */
313 protected function format_hits_as_id_name( $terms, $new_terms ) {
314 foreach ( $terms as $term_array ) {
315 $new_terms[ $term_array['term_id'] ] = $term_array['name'];
316 }
317
318 return $new_terms;
319 }
320
321 /**
322 * Format the ES hits/results as an array of term ID and parent ID.
323 *
324 * @param array $terms The terms that should be formatted.
325 * @param array $new_terms Array of terms from cache.
326 * @since 3.1
327 * @return array Returns an associative array with ids as keys, parent term IDs as values
328 */
329 protected function format_hits_as_id_parent( $terms, $new_terms ) {
330 foreach ( $terms as $term_array ) {
331 $new_terms[ $term_array['term_id'] ] = $term_array['parent'];
332 }
333
334 return $new_terms;
335 }
336
337 /**
338 * Format the ES hits/results as an array of term ID and term slug.
339 *
340 * @param array $terms The terms that should be formatted.
341 * @param array $new_terms Array of terms from cache.
342 * @since 3.1
343 * @return array Returns an associative array with ids as keys, term slugs as values
344 */
345 protected function format_hits_as_id_slug( $terms, $new_terms ) {
346 foreach ( $terms as $term_array ) {
347 $new_terms[ $term_array['term_id'] ] = $term_array['slug'];
348 }
349
350 return $new_terms;
351 }
352
353 /**
354 * Format the ES hits/results as an array of term names.
355 *
356 * @param array $terms The terms that should be formatted.
357 * @param array $new_terms Array of terms from cache.
358 * @since 3.1
359 * @return array Returns an array of term names.
360 */
361 protected function format_hits_as_names( $terms, $new_terms ) {
362 foreach ( $terms as $term_array ) {
363 $new_terms[] = $term_array['name'];
364 }
365
366 return $new_terms;
367 }
368
369 /**
370 * Determine whether ES should be used for the query if all taxonomies are indexable.
371 *
372 * @param \WP_Term_Query $query The WP_Term_Query object.
373 * @return boolean
374 */
375 protected function is_searchable( $query ) {
376
377 $taxonomies = $query->query_vars['taxonomy'];
378 if ( ! $taxonomies ) {
379 return true;
380 }
381
382 $indexable_taxonomies = Indexables::factory()->get( 'term' )->get_indexable_taxonomies();
383 return empty( array_diff( $taxonomies, $indexable_taxonomies ) );
384 }
385
386 }
387