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 / Comment / QueryIntegration.php

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

376 lines 10.1 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_Comment_Query
4 *
5 * @since 3.6.0
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\Indexable\Comment;
10
11 use WP_Comment_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 * Comment indexable
26 *
27 * @var Comment
28 */
29 public $indexable = '';
30
31 /**
32 * Index name
33 *
34 * @var string
35 */
36 public $index = '';
37
38 /**
39 * Sets up the appropriate actions and filters.
40 *
41 * @param string $indexable_slug Indexable slug. Optional.
42 *
43 * @since 3.6.0
44 */
45 public function __construct( $indexable_slug = 'comment' ) {
46 /**
47 * Filter whether to enable query integration during indexing
48 *
49 * @since 4.5.2
50 * @hook ep_enable_query_integration_during_indexing
51 *
52 * @param {bool} $enable To allow query integration during indexing
53 * @param {string} $indexable_slug Indexable slug
54 * @return {bool} New value
55 */
56 $allow_query_integration_during_indexing = apply_filters( 'ep_enable_query_integration_during_indexing', false, $indexable_slug );
57
58 // Check if we are currently indexing
59 if ( Utils\is_indexing() && ! $allow_query_integration_during_indexing ) {
60 return;
61 }
62
63 // Add header
64 add_action( 'pre_get_comments', array( $this, 'action_pre_get_comments' ), 5 );
65
66 // Filter comment query
67 add_filter( 'comments_pre_query', [ $this, 'maybe_filter_query' ], 10, 2 );
68 }
69
70 /**
71 * Add EP header
72 *
73 * @param WP_Comment_Query $query Query object
74 * @since 3.6.0
75 * @return void
76 */
77 public function action_pre_get_comments( WP_Comment_Query $query ) {
78 /**
79 * Filter to skip WP_Comment_Query integration
80 *
81 * @hook ep_skip_comment_query_integration
82 * @since 3.6.0
83 * @param {bool} $skip True to skip
84 * @param {WP_Comment_Query} $query WP_Comment_Query to evaluate
85 * @return {bool} New skip value
86 */
87 if ( ! Indexables::factory()->get( 'comment' )->elasticpress_enabled( $query ) || apply_filters( 'ep_skip_comment_query_integration', false, $query ) ) {
88 return;
89 }
90
91 if ( ! headers_sent() ) {
92 /**
93 * Manually setting a header as $wp_query isn't yet initialized
94 * when we call: add_filter('wp_headers', 'filter_wp_headers');
95 */
96 header( 'X-ElasticPress-Search: true' );
97 }
98 }
99
100 /**
101 * If WP_Comment_Query meets certain conditions, query results from ES
102 *
103 * @param array $results Query results.
104 * @param WP_Comment_Query $query Current query.
105 * @since 3.6.0
106 * @return array
107 */
108 public function maybe_filter_query( $results, WP_Comment_Query $query ) {
109 $this->indexable = Indexables::factory()->get( 'comment' );
110
111 if ( ! $this->indexable->elasticpress_enabled( $query ) || apply_filters( 'ep_skip_comment_query_integration', false, $query ) ) {
112 return $results;
113 }
114
115 /**
116 * Filter cached comments pre-post query
117 *
118 * @hook ep_wp_query_cached_comments
119 * @since 3.6.0
120 * @param {mixed} $comments Comments or null
121 * @param {WP_Comment_Query} $query WP_Comment_Query object
122 * @return {array} New cached comments
123 */
124 $new_comments = apply_filters( 'ep_wp_query_cached_comments', null, $query );
125
126 if ( null !== $new_comments ) {
127 return $new_comments;
128 }
129
130 $new_comments = [];
131
132 $formatted_args = $this->indexable->format_args( $query->query_vars );
133
134 $scope = 'current';
135
136 $site__in = [];
137 $site__not_in = [];
138
139 if ( ! empty( $query->query_vars['sites'] ) ) {
140 _deprecated_argument( __FUNCTION__, '4.4.0', esc_html__( 'sites is deprecated. Use site__in instead.', 'elasticpress' ) );
141 }
142
143 if ( ! empty( $query->query_vars['site__in'] ) || ! empty( $query->query_vars['sites'] ) ) {
144 $site__in = ! empty( $query->query_vars['site__in'] ) ? (array) $query->query_vars['site__in'] : (array) $query->query_vars['sites'];
145
146 if ( in_array( 'all', $site__in, true ) ) {
147 $scope = 'all';
148 } elseif ( in_array( 'current', $site__in, true ) ) {
149 $site__in = (array) get_current_blog_id();
150 }
151 }
152
153 if ( ! empty( $query->query_vars['site__not_in'] ) ) {
154 $site__not_in = (array) $query->query_vars['site__not_in'];
155 }
156
157 /**
158 * Filter search scope
159 *
160 * @since 3.6.0
161 *
162 * @param mixed $scope The search scope. Accepts `all` (string), a single
163 * site id (int or string), or an array of site ids (array).
164 */
165 $scope = apply_filters( 'ep_comment_search_scope', $scope );
166
167 if ( ! defined( 'EP_IS_NETWORK' ) || ! EP_IS_NETWORK ) {
168 $scope = 'current';
169 }
170
171 $this->index = null;
172
173 if ( 'all' === $scope ) {
174 $this->index = $this->indexable->get_network_alias();
175 } elseif ( ! empty( $site__in ) ) {
176 $this->index = [];
177
178 foreach ( $site__in as $site_id ) {
179 $this->index[] = $this->indexable->get_index_name( $site_id );
180 }
181
182 $this->index = implode( ',', $this->index );
183 } elseif ( ! empty( $site__not_in ) ) {
184
185 $sites = \get_sites(
186 array(
187 'fields' => 'ids',
188 'site__not_in' => $site__not_in,
189 )
190 );
191 foreach ( $sites as $site_id ) {
192 if ( ! Utils\is_site_indexable( $site_id ) ) {
193 continue;
194 }
195 $index[] = Indexables::factory()->get( 'comment' )->get_index_name( $site_id );
196 }
197
198 $this->index = implode( ',', $index );
199
200 }
201
202 $ep_query = $this->indexable->query_es( $formatted_args, $query->query_vars, $this->index, $query );
203
204 if ( false === $ep_query ) {
205 $query->elasticsearch_success = false;
206 return $results;
207 }
208
209 if ( ! empty( $query->query_vars['count'] ) ) {
210 return count( $ep_query['documents'] );
211 }
212
213 $query->found_comments = $ep_query['found_documents']['value'];
214 $query->max_num_pages = $query->query_vars['number'] <= 0 ? 1 : max( 1, ceil( $query->found_comments / absint( $query->query_vars['number'] ) ) );
215 $query->elasticsearch_success = true;
216
217 // Determine how we should format the results from ES based on the fields parameter.
218 $fields = $query->query_vars['fields'];
219
220 switch ( $fields ) {
221 case 'count':
222 $new_comments = count( $ep_query['documents'] );
223 break;
224
225 case 'ids':
226 $new_comments = $this->format_hits_as_ids( $ep_query['documents'], $new_comments );
227 break;
228
229 default:
230 $new_comments = $this->format_hits_as_comments( $ep_query['documents'], $new_comments, $query->query_vars );
231 break;
232 }
233
234 return $new_comments;
235 }
236
237 /**
238 * Format the ES hits/results as comments objects.
239 *
240 * @param array $comments The comments that should be formatted.
241 * @param array $new_comments Array of comments from cache.
242 * @param array $query_vars Query variables.
243 * @since 3.6.0
244 * @return array
245 */
246 protected function format_hits_as_comments( $comments, $new_comments, $query_vars ) {
247 $hierarchical = isset( $query_vars['hierarchical'] ) ? $query_vars['hierarchical'] : false;
248
249 foreach ( $comments as $comment_array ) {
250 $comment = new \WP_Comment( (object) $comment_array );
251 if ( ! empty( $comment_array['site_id'] ) ) {
252 $comment->site_id = $comment_array['site_id'];
253 } else {
254 $comment->site_id = get_current_blog_id();
255 }
256
257 $comment->elasticsearch = true; // Super useful for debugging
258
259 if ( $comment ) {
260 $new_comments[] = $comment;
261 }
262 }
263
264 if ( $hierarchical ) {
265 $new_comments = $this->fill_descendants( $new_comments, $query_vars );
266 }
267
268 return $new_comments;
269 }
270
271 /**
272 * Format the ES hits/results as an array of ids.
273 *
274 * @param array $comments The comments that should be formatted.
275 * @param array $new_comments Array of comments from cache.
276 * @since 3.6.0
277 * @return array
278 */
279 protected function format_hits_as_ids( $comments, $new_comments ) {
280 foreach ( $comments as $comment_array ) {
281 $new_comments[] = $comment_array['comment_ID'];
282 }
283
284 return $new_comments;
285 }
286
287 /**
288 * Fetch descendants for located comments.
289 *
290 * @param array $comments Array of top-level comments whose descendants should be filled in.
291 * @param array $query_vars Current query vars.
292 * @since 3.6.0
293 * @return array
294 */
295 protected function fill_descendants( $comments, $query_vars ) {
296 $levels = [
297 0 => $comments,
298 ];
299
300 // Fetch an entire level of the descendant tree at a time.
301 $level = 0;
302 $exclude_keys = [ 'parent', 'parent__in', 'parent__not_in' ];
303
304 do {
305 $child_comments = [];
306 $_parent_ids = wp_list_pluck( $levels[ $level ], 'comment_ID' );
307
308 if ( $_parent_ids ) {
309 $parent_query_args = $query_vars;
310
311 foreach ( $exclude_keys as $exclude_key ) {
312 $parent_query_args[ $exclude_key ] = '';
313 }
314
315 $parent_query_args['parent__in'] = $_parent_ids;
316 $parent_query_args['hierarchical'] = false;
317 $parent_query_args['offset'] = 0;
318 $parent_query_args['number'] = 0;
319
320 $formatted_args = $this->indexable->format_args( $parent_query_args );
321 $ep_query = $this->indexable->query_es( $formatted_args, $query_vars, $this->index );
322
323 if ( false === $ep_query ) {
324 $level_comments = [];
325 } else {
326 $level_comments = $this->format_hits_as_comments( $ep_query['documents'], [], [] );
327 }
328
329 foreach ( $level_comments as $level_comment ) {
330 $child_comments[] = $level_comment;
331 }
332 }
333
334 ++$level;
335 $levels[ $level ] = $child_comments;
336 } while ( $child_comments );
337
338 // Pull out just the descendant comments
339 $descendants = [];
340 for ( $i = 1, $c = count( $levels ); $i < $c; $i++ ) {
341 $descendants = array_merge( $descendants, $levels[ $i ] );
342 }
343
344 // Assemble a flat array of all comments + descendants.
345 $all_comments = $comments;
346 foreach ( $descendants as $descendant ) {
347 $all_comments[] = $descendant;
348 }
349
350 // If a threaded representation was requested, build the tree.
351 if ( 'threaded' === $query_vars['hierarchical'] ) {
352 $threaded_comments = [];
353 $ref = [];
354
355 foreach ( $all_comments as $c ) {
356 // If the comment isn't in the reference array, it goes in the top level of the thread.
357 if ( ! isset( $ref[ $c->comment_parent ] ) ) {
358 $threaded_comments[ $c->comment_ID ] = $c;
359 $ref[ $c->comment_ID ] = $threaded_comments[ $c->comment_ID ];
360
361 // Otherwise, set it as a child of its parent.
362 } else {
363 $ref[ $c->comment_parent ]->add_child( $c );
364 $ref[ $c->comment_ID ] = $c;
365 }
366 }
367
368 $comments = $threaded_comments;
369 } else {
370 $comments = $all_comments;
371 }
372
373 return $comments;
374 }
375 }
376