PluginProbe
ElasticPress / 4.2.1
ElasticPress v4.2.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 / Elasticsearch.php

Elasticsearch.php in ElasticPress 4.2.1, at includes/classes/Elasticsearch.php

1,623 lines 44.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * ElasticPress-Elasticsearch API functions
4 *
5 * @since 3.0
6 * @package elasticpress
7 */
8
9 namespace ElasticPress;
10
11 use ElasticPress\Utils as Utils;
12 use \WP_Error as WP_Error;
13
14 if ( ! defined( 'ABSPATH' ) ) {
15 exit; // Exit if accessed directly.
16 }
17
18 /**
19 * Elasticsearch API class
20 */
21 class Elasticsearch {
22
23 /**
24 * Logged queries for debugging
25 *
26 * @since 1.8
27 * @var array
28 */
29 private $queries = [];
30
31 /**
32 * ES plugins
33 *
34 * @var array
35 * @since 2.2
36 */
37 public $elasticsearch_plugins = null;
38
39 /**
40 * ES version number
41 *
42 * @var string
43 * @since 2.2
44 */
45 public $elasticsearch_version = null;
46
47 /**
48 * Server type (elasticsearch, opensearch, etc.)
49 *
50 * @var string
51 */
52 public $server_type = 'elasticsearch';
53
54 /**
55 * Return singleton instance of class
56 *
57 * @return object
58 * @since 0.1.0
59 */
60 public static function factory() {
61 static $instance = false;
62
63 if ( ! $instance ) {
64 $instance = new self();
65 }
66
67 return $instance;
68 }
69
70 /**
71 * Index a document in Elasticsearch.
72 *
73 * We require $document to have ID set
74 *
75 * @param string $index Index name.
76 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
77 * @param array $document Formatted Elasticsearch document.
78 * @param boolean $blocking Blocking HTTP request or not.
79 * @since 3.0
80 * @return boolean|array
81 */
82 public function index_document( $index, $type, $document, $blocking = true ) {
83 /**
84 * Filter Elasticsearch index document request path
85 *
86 * @hook ep_index_{document_type}_request_path
87 * @param {string} $path Path to index document
88 * @param {int} $document_id Document ID
89 * @param {array} $document Document to index
90 * @param {string} $type Type of document
91 * @return {string} New path
92 * @since 3.0
93 */
94 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
95 $path = apply_filters( 'ep_index_' . $type . '_request_path', $index . '/' . $type . '/' . $document['ID'], $document, $type );
96 } else {
97 $path = apply_filters( 'ep_index_' . $type . '_request_path', $index . '/_doc/' . $document['ID'], $document, $type );
98 }
99
100 $path = apply_filters( 'ep_index_request_path', $path, $document, $type );
101
102 if ( function_exists( 'wp_json_encode' ) ) {
103 $encoded_document = wp_json_encode( $document );
104 } else {
105 // phpcs:disable
106 $encoded_document = json_encode( $document );
107 // phpcs:enable
108 }
109
110 $request_args = array(
111 'body' => $encoded_document,
112 'method' => 'POST',
113 'timeout' => apply_filters( 'ep_index_document_timeout', 15 ),
114 'blocking' => $blocking,
115 );
116
117 $request = $this->remote_request( $path, $request_args, [], 'index' );
118
119 /**
120 * Backwards compat for pre-3.0
121 */
122
123 /**
124 * Fires after indexing document
125 *
126 * @hook ep_index_post_retrieve_raw_response
127 * @param {array} $request Remote request response
128 * @param {array} $document Current document
129 * @param {string} $path Elasticsearch request path
130 */
131 do_action( 'ep_index_post_retrieve_raw_response', $request, $document, $path );
132
133 /**
134 * Fires after indexing document
135 *
136 * @hook ep_index_retrieve_raw_response
137 * @param {array} $request Remote request response
138 * @param {array} $document Current document
139 * @param {string} $path Elasticsearch request path
140 */
141 do_action( 'ep_index_retrieve_raw_response', $request, $document, $path );
142
143 if ( ! is_wp_error( $request ) ) {
144 $response_body = wp_remote_retrieve_body( $request );
145
146 $return = json_decode( $response_body );
147 } else {
148 $return = false;
149 }
150
151 /**
152 * Backwards compat for pre-3.0
153 */
154
155 /**
156 * Fires after indexing document and body decoding
157 *
158 * @hook ep_index_index_post
159 * @param {array} $document Current document
160 * @param {array|boolean} $return Elasticsearch response. False on error.
161 */
162 do_action( 'ep_after_index_post', $document, $return );
163
164 /**
165 * Fires after indexing document and body decoding
166 *
167 * @hook ep_index_index
168 * @param {array} $document Current document
169 * @param {array|boolean} $return Elasticsearch response. False on error.
170 */
171 do_action( 'ep_after_index', $document, $return );
172
173 return $return;
174 }
175
176 /**
177 * Pull the site id from the index name
178 *
179 * @param string $index_name Index name.
180 * @since 0.9.0
181 * @return int
182 */
183 public function parse_site_id( $index_name ) {
184 return (int) preg_replace( '#^.*\-([0-9]+)$#', '$1', $index_name );
185 }
186
187 /**
188 * Refresh all index. Sometimes useful if you need changes to show up instantly.
189 *
190 * @since 3.0
191 * @return bool
192 */
193 public function refresh_indices() {
194
195 $request_args = array( 'method' => 'POST' );
196
197 $request = $this->remote_request( '_refresh', $request_args, [], 'refresh_indices' );
198
199 if ( ! is_wp_error( $request ) ) {
200 if ( isset( $request['response']['code'] ) && 200 === $request['response']['code'] ) {
201 return true;
202 }
203 }
204
205 return false;
206 }
207
208 /**
209 * Get Elasticsearch version. We cache this so we don't have to do it every time.
210 *
211 * @param bool $force Bust cache or not.
212 * @since 2.1.2
213 * @return string|bool
214 */
215 public function get_elasticsearch_version( $force = false ) {
216
217 $info = $this->get_elasticsearch_info( $force );
218
219 /**
220 * Filter Elasticsearch version
221 *
222 * @hook ep_elasticsearch_version
223 * @param {string} $version Version
224 * @return {string} New version
225 * @since 2.1.2
226 */
227 return apply_filters( 'ep_elasticsearch_version', $info['version'] );
228 }
229
230 /**
231 * Get server type. We cache this so we don't have to do it every time.
232 *
233 * @param bool $force Bust cache or not.
234 * @since 4.2.1
235 * @return string|bool
236 */
237 public function get_server_type( $force = false ) {
238
239 $info = $this->get_elasticsearch_info( $force );
240
241 /**
242 * Filter server type
243 *
244 * @hook ep_server_type
245 * @param {string} $type Type (elasticsearch, opensearch, others)
246 * @return {string} New type
247 * @since 4.2.1
248 */
249 return apply_filters( 'ep_server_type', $info['server_type'] );
250 }
251
252 /**
253 * Get Elasticsearch plugins. We cache this so we don't have to do it every time.
254 *
255 * @param bool $force Force cache refresh or not.
256 * @since 2.2
257 * @return string|bool
258 */
259 public function get_elasticsearch_plugins( $force = false ) {
260
261 $info = $this->get_elasticsearch_info( $force );
262
263 /**
264 * Filter Elasticsearch plugins
265 *
266 * @hook ep_elasticsearch_plugins
267 * @param {array} $plugins Elasticsearch plugins
268 * @return {array} New plugins
269 * @since 2.2
270 */
271 return apply_filters( 'ep_elasticsearch_plugins', $info['plugins'] );
272 }
273
274 /**
275 * Run a query on Elasticsearch
276 *
277 * @param string $index Index name.
278 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
279 * @param array $query Prepared ES query.
280 * @param array $query_args WP query args.
281 * @param mixed $query_object Could be WP_Query, WP_User_Query, etc.
282 * @since 3.0
283 * @return bool|array
284 */
285 public function query( $index, $type, $query, $query_args, $query_object = null ) {
286 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
287 $path = $index . '/' . $type . '/_search';
288 } else {
289 $path = $index . '/_search';
290 }
291
292 // For backwards compat
293 /**
294 * Filter Elasticsearch query request path
295 *
296 * @hook ep_search_request_path
297 * @param {string} $path Request path
298 * @param {string} $index Index name
299 * @param {string} $type Index type
300 * @param {array} $query Prepared Elasticsearch query
301 * @param {array} $query_args Query arguments
302 * @param {mixed} $query_object Could be WP_Query, WP_User_Query, etc.
303 * @return {string} New path
304 */
305 $path = apply_filters( 'ep_search_request_path', $path, $index, $type, $query, $query_args, $query_object );
306
307 /**
308 * Filter Elasticsearch query request path
309 *
310 * @hook ep_query_request_path
311 * @param {string} $path Request path
312 * @param {string} $index Index name
313 * @param {string} $type Index type
314 * @param {array} $query Prepared Elasticsearch query
315 * @param {array} $query_args Query arguments
316 * @param {mixed} $query_object Could be WP_Query, WP_User_Query, etc.
317 * @return {string} New path
318 */
319 $path = apply_filters( 'ep_query_request_path', $path, $index, $type, $query, $query_args, $query_object );
320
321 $request_args = array(
322 'body' => wp_json_encode( $query ),
323 'method' => 'POST',
324 'headers' => array(
325 'Content-Type' => 'application/json',
326 ),
327 );
328
329 /**
330 * Filter whether to send the EP-Search-Term header or not.
331 *
332 * @todo Evaluate if we should remove tests for is_admin() and empty post types.
333 *
334 * @since 3.5.2
335 * @hook ep_query_send_ep_search_term_header
336 * @param {bool} $send_header True means send the EP-Search-Term header
337 * @param {array} $query_args WP query args
338 * @return {bool} New $send_header value
339 */
340 $send_ep_search_term_header = apply_filters(
341 'ep_query_send_ep_search_term_header',
342 (
343 Utils\is_epio() &&
344 ! empty( $query_args['s'] ) &&
345 Utils\is_integrated_request( 'search' ) &&
346 ! isset( $_GET['post_type'] ) // phpcs:ignore WordPress.Security.NonceVerification
347 ),
348 $query_args
349 );
350
351 // If needed, send the search term as a header to ES so the backend understands what a normal query looks like
352 if ( $send_ep_search_term_header ) {
353 $request_args['headers']['EP-Search-Term'] = rawurlencode( $query_args['s'] );
354 }
355
356 /**
357 * Filter Elasticsearch query request arguments
358 *
359 * @hook ep_query_request_args
360 * @since 3.6.4
361 * @param {array} $request_args Request arguments
362 * @param {string} $path Request path
363 * @param {string} $index Index name
364 * @param {string} $type Index type
365 * @param {array} $query Prepared Elasticsearch query
366 * @param {array} $query_args Query arguments
367 * @param {mixed} $query_object Could be WP_Query, WP_User_Query, etc.
368 * @return {array} New request arguments
369 */
370 $request_args = apply_filters( 'ep_query_request_args', $request_args, $path, $index, $type, $query, $query_args, $query_object );
371
372 $request = $this->remote_request( $path, $request_args, $query_args, 'query' );
373
374 $remote_req_res_code = absint( wp_remote_retrieve_response_code( $request ) );
375
376 $is_valid_res = ( $remote_req_res_code >= 200 && $remote_req_res_code <= 299 );
377
378 /**
379 * Filter whether Elasticsearch remote request response code is valid
380 *
381 * @hook ep_remote_request_is_valid_res
382 * @param {boolean} $is_valid_res Whether response code is valid or not
383 * @param {array} $request Remote request response
384 * @return {string} New value
385 */
386 if ( ! is_wp_error( $request ) && apply_filters( 'ep_remote_request_is_valid_res', $is_valid_res, $request ) ) {
387
388 $response_body = wp_remote_retrieve_body( $request );
389
390 $response = json_decode( $response_body, true );
391
392 $hits = $this->get_hits_from_query( $response );
393 $total_hits = $this->get_total_hits_from_query( $response );
394
395 if ( ! empty( $response['aggregations'] ) ) {
396 /**
397 * Deprecated way to retrieve aggregations.
398 *
399 * @hook ep_retrieve_aggregations
400 * @param {array} $aggregations Elasticsearch aggregations
401 * @param {array} $query Prepared Elasticsearch query
402 * @param {string} $scope Backwards compat for scope parameter.
403 * @param {array} $query_args Current WP Query arguments
404 */
405 do_action( 'ep_retrieve_aggregations', $response['aggregations'], $query, '', $query_args );
406 }
407
408 /**
409 * Fires after valid Elasticsearch query
410 *
411 * @hook ep_valid_response
412 * @param {array} $response Elasticsearch decoded response
413 * @param {array} $query Prepared Elasticsearch query
414 * @param {array} $query_args Current WP Query arguments
415 * @param {mixed} $query_object Could be WP_Query, WP_User_Query, etc.
416 */
417 do_action( 'ep_valid_response', $response, $query, $query_args, $query_object );
418
419 // Backwards compat
420 /**
421 * Fires after valid Elasticsearch query
422 *
423 * @hook ep_retrieve_raw_response
424 * @param {array} $response Elasticsearch request
425 * @param {array} $query Prepared Elasticsearch query
426 * @param {array} $query_args Current WP Query arguments
427 * @param {mixed} $query_object Could be WP_Query, WP_User_Query, etc.
428 */
429 do_action( 'ep_retrieve_raw_response', $request, $query, $query_args, $query_object );
430
431 $documents = [];
432
433 foreach ( $hits as $hit ) {
434 $document = $hit['_source'];
435 $document['site_id'] = $this->parse_site_id( $hit['_index'] );
436
437 if ( ! empty( $hit['highlight'] ) ) {
438 $document['highlight'] = $hit['highlight'];
439 }
440
441 /**
442 * Filter Elasticsearch retrieved document
443 *
444 * @hook ep_retrieve_the_{index_type}
445 * @param {array} $document Document retrieved from Elasticsearch
446 * @param {array} $hit Raw Elasticsearch hit
447 * @param {string} $index Index name
448 * @return {array} New document
449 */
450 $documents[] = apply_filters( 'ep_retrieve_the_' . $type, $document, $hit, $index );
451 }
452
453 /**
454 * Filter Elasticsearch query results
455 *
456 * @hook ep_es_query_results
457 * @param {array} $results Results from Elasticsearch
458 * @param {response} $response Raw response from Elasticsearch
459 * @param {array} $query Raw Elasticsearch query
460 * @param {array} $query_args Query arguments
461 * @param {mixed} $query_object Could be WP_Query, WP_User_Query, etc.
462 * @return {array} New results
463 */
464 return apply_filters(
465 'ep_es_query_results',
466 [
467 'found_documents' => $total_hits,
468 'documents' => $documents,
469 ],
470 $response,
471 $query,
472 $query_args,
473 $query_object
474 );
475 }
476
477 /**
478 * Fires after invalid Elasticsearch query
479 *
480 * @hook ep_invalid_response
481 * @param {array} $request Remote request response
482 * @param {array} $query Prepared Elasticsearch query
483 * @param {array} $query_args Current WP Query arguments
484 * @param {mixed} $query_object Could be WP_Query, WP_User_Query, etc.
485 */
486 do_action( 'ep_invalid_response', $request, $query, $query_args, $query_object );
487
488 return false;
489 }
490
491 /**
492 * Returns the number of total results that ElasticSearch found for the given query
493 *
494 * @param array $response Response to get total hits from.
495 * @since 2.5
496 * @return int
497 */
498 public function get_total_hits_from_query( $response ) {
499
500 if ( $this->is_empty_query( $response ) ) {
501 return 0;
502 }
503
504 return $response['hits']['total'];
505 }
506
507 /**
508 * Returns array containing hits returned from query, if such exist
509 *
510 * @param array $response Response to get hits from.
511 * @since 2.5
512 * @return array
513 */
514 public function get_hits_from_query( $response ) {
515
516 if ( $this->is_empty_query( $response ) ) {
517 return [];
518 }
519
520 /**
521 * Filter Elasticsearch allows to flatten hits, if searched hits are come within aggregations.
522 *
523 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-top-hits-aggregation.html
524 *
525 * @hook ep_get_hits_from_query
526 * @param {array} $hits from Elasticsearch
527 * @param {response} $response Raw response from Elasticsearch
528 * @return {array} hits
529 */
530 return apply_filters( 'ep_get_hits_from_query', $response['hits']['hits'], $response );
531 }
532
533 /**
534 * Check if a response array contains results or not
535 *
536 * @param array $response Response to check.
537 * @since 0.1.2
538 * @return bool
539 */
540 public function is_empty_query( $response ) {
541
542 if ( ! is_array( $response ) ) {
543 return true;
544 }
545
546 if ( isset( $response['error'] ) ) {
547 return true;
548 }
549
550 if ( empty( $response['hits'] ) ) {
551 return true;
552 }
553
554 if ( isset( $response['hits']['total'] ) && 0 === (int) $response['hits']['total'] ) {
555 return true;
556 }
557
558 return false;
559 }
560
561 /**
562 * Delete an Elasticsearch document
563 *
564 * @param string $index Index name.
565 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
566 * @param int $document_id Document id to delete.
567 * @param boolean $blocking Blocking HTTP request or not.
568 * @since 3.0
569 * @return boolean
570 */
571 public function delete_document( $index, $type, $document_id, $blocking = true ) {
572 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
573 $path = $index . '/' . $type . '/' . $document_id;
574 } else {
575 $path = $index . '/_doc/' . $document_id;
576 }
577
578 $request_args = [
579 'method' => 'DELETE',
580 'timeout' => 15,
581 'blocking' => $blocking,
582 ];
583
584 $request = $this->remote_request( $path, $request_args, [], 'delete' );
585
586 if ( ! is_wp_error( $request ) ) {
587 $response_body = wp_remote_retrieve_body( $request );
588
589 $response = json_decode( $response_body, true );
590
591 if ( ! empty( $response['found'] ) ) {
592 return true;
593 }
594 }
595
596 return false;
597 }
598
599 /**
600 * Add appropriate headers to request
601 *
602 * @since 1.4
603 * @return array
604 */
605 public function format_request_headers() {
606 $headers = array(
607 'Content-Type' => 'application/json',
608 );
609
610 // Check for ElasticPress API key and add to header if needed.
611 if ( defined( 'EP_API_KEY' ) && EP_API_KEY ) {
612 $headers['X-ElasticPress-API-Key'] = EP_API_KEY;
613 }
614
615 /**
616 * ES Shield info
617 *
618 * @since 1.9
619 */
620 $shield = Utils\get_shield_credentials();
621
622 if ( ! empty( $shield ) ) {
623 // phpcs:disable
624 $headers['Authorization'] = 'Basic ' . base64_encode( $shield );
625 // phpcs:enable
626 }
627
628 /**
629 * Filter Elasticsearch request headers
630 *
631 * @hook ep_format_request_headers
632 * @param {array} $headers Current headers
633 * @return {array} New headers
634 */
635 $headers = apply_filters( 'ep_format_request_headers', $headers );
636
637 return $headers;
638 }
639
640 /**
641 * Get a document from Elasticsearch given an id
642 *
643 * @param string $index Index name.
644 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
645 * @param int $document_id Document id to get.
646 * @since 3.0
647 * @return boolean|array
648 */
649 public function get_document( $index, $type, $document_id ) {
650 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
651 $path = $index . '/' . $type . '/' . $document_id;
652 } else {
653 $path = $index . '/_doc/' . $document_id;
654 }
655
656 $request_args = [ 'method' => 'GET' ];
657
658 $request = $this->remote_request( $path, $request_args, [], 'get' );
659
660 if ( ! is_wp_error( $request ) ) {
661 $response_body = wp_remote_retrieve_body( $request );
662
663 $response = json_decode( $response_body, true );
664
665 if ( ! empty( $response['exists'] ) || ! empty( $response['found'] ) ) {
666 return $response['_source'];
667 }
668 }
669
670 return false;
671 }
672
673 /**
674 * Delete the network alias.
675 *
676 * Network aliases are used to query documents across blogs in a network.
677 *
678 * @param string $alias Alias to use.
679 * @since 3.0
680 * @return array|boolean
681 */
682 public function delete_network_alias( $alias ) {
683 $path = '*/_alias/' . $alias;
684
685 $request_args = [ 'method' => 'DELETE' ];
686
687 $request = $this->remote_request( $path, $request_args, [], 'delete_network_alias' );
688
689 if ( ! is_wp_error( $request ) && ( 200 >= wp_remote_retrieve_response_code( $request ) && 300 > wp_remote_retrieve_response_code( $request ) ) ) {
690 $response_body = wp_remote_retrieve_body( $request );
691
692 return json_decode( $response_body );
693 }
694
695 return false;
696 }
697
698 /**
699 * Get multiple documents from Elasticsearch given an array of ids
700 *
701 * @param string $index Index name.
702 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
703 * @param array $document_ids Array of document ids to get.
704 * @since 3.6.0
705 * @return boolean|array
706 */
707 public function get_documents( $index, $type, $document_ids ) {
708 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
709 $path = apply_filters( 'ep_index_' . $type . '_request_path', $index . '/' . $type . '/_mget', $document_ids, $type );
710 } else {
711 $path = apply_filters( 'ep_index_' . $type . '_request_path', $index . '/_mget', $document_ids, $type );
712 }
713
714 $request_args = [
715 'method' => 'POST',
716 'body' => wp_json_encode(
717 array(
718 'ids' => $document_ids,
719 )
720 ),
721 ];
722
723 $request = $this->remote_request( $path, $request_args, [], 'post' );
724
725 if ( is_wp_error( $request ) ) {
726 return false;
727 }
728
729 $response_body = wp_remote_retrieve_body( $request );
730
731 $response = json_decode( $response_body, true );
732
733 $docs = [];
734
735 if ( isset( $response['docs'] ) && is_array( $response['docs'] ) ) {
736 foreach ( $response['docs'] as $doc ) {
737 if ( ! empty( $doc['exists'] ) || ! empty( $doc['found'] ) ) {
738 $docs[ $doc['_id'] ] = $doc['_source'];
739 }
740 }
741 }
742
743 /**
744 * Filter documents found by Elasticsearch through the /_mget endpoint.
745 *
746 * @hook ep_get_documents
747 * @since 3.6.0
748 * @param {array} $docs Documents found indexed by ID
749 * @param {string} $index Index name
750 * @param {string} $type Index type
751 * @param {array} $document_ids Array of document ids
752 * @return {array} Documents to be returned
753 */
754 $docs = apply_filters( 'ep_get_documents', $docs, $index, $type, $document_ids );
755
756 return $docs;
757 }
758
759 /**
760 * Create the network alias.
761 *
762 * Network aliases are used to query documents across blogs in a network.
763 *
764 * @param array $indexes Indexes to group under alias.
765 * @param string $network_alias Name of network alias.
766 * @since 3.0
767 * @return boolean
768 */
769 public function create_network_alias( $indexes, $network_alias ) {
770
771 $path = '_aliases';
772
773 $args = array(
774 'actions' => [],
775 );
776
777 foreach ( $indexes as $index ) {
778 if ( empty( $index ) ) {
779 continue;
780 }
781
782 $args['actions'][] = array(
783 'add' => array(
784 'index' => $index,
785 'alias' => $network_alias,
786 ),
787 );
788 }
789
790 $request_args = array(
791 'body' => wp_json_encode( $args ),
792 'method' => 'POST',
793 'timeout' => 25,
794 );
795
796 $request = $this->remote_request( $path, $request_args, [], 'create_network_alias' );
797
798 if ( ! is_wp_error( $request ) && ( 200 >= wp_remote_retrieve_response_code( $request ) && 300 > wp_remote_retrieve_response_code( $request ) ) ) {
799 return true;
800 }
801
802 return false;
803 }
804
805 /**
806 * Put a mapping into Elasticsearch
807 *
808 * @param string $index Index name.
809 * @param array $mapping Mapping array.
810 * @since 3.0
811 * @return boolean
812 */
813 public function put_mapping( $index, $mapping ) {
814 /**
815 * Filter Elasticsearch mapping before put mapping
816 *
817 * @hook ep_config_mapping
818 * @param {array} $mapping Elasticsearch mapping
819 * @param {string} $index Index name
820 * @return {array} New mapping
821 */
822 $mapping = apply_filters( 'ep_config_mapping', $mapping, $index );
823
824 $request_args = [
825 'body' => wp_json_encode( $mapping ),
826 'method' => 'PUT',
827 'timeout' => 30,
828 ];
829
830 $request = $this->remote_request( $index, $request_args, [], 'put_mapping' );
831
832 /**
833 * Filter Elasticsearch put mapping response
834 *
835 * @hook ep_config_mapping_request
836 * @param {array} $request Elasticsearch response
837 * @param {string} $index Elasticsearch index name
838 * @param {array} $mapping Mapping sent to Elasticsearch
839 * @return {array} New response
840 */
841 $request = apply_filters( 'ep_config_mapping_request', $request, $index, $mapping );
842
843 $response_body = wp_remote_retrieve_body( $request );
844
845 if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) {
846 $response_body = wp_remote_retrieve_body( $request );
847
848 return true;
849 }
850
851 return false;
852 }
853
854 /**
855 * Get current index mapping from Elasticsearch.
856 *
857 * @param string $index The index name.
858 * @since 3.5
859 * @return array
860 */
861 public function get_mapping( $index ) {
862 $request_args = [
863 'method' => 'GET',
864 'timeout' => 30,
865 ];
866
867 $request = $this->remote_request( $index, $request_args, [], 'get_mapping' );
868
869 if ( is_wp_error( $request ) || 200 !== wp_remote_retrieve_response_code( $request ) ) {
870 return [];
871 }
872
873 $body = wp_remote_retrieve_body( $request );
874
875 if ( ! $body ) {
876 return [];
877 }
878
879 $mapping = json_decode( $body, true );
880
881 return is_array( $mapping ) ? $mapping : [];
882 }
883
884 /**
885 * Close an open index.
886 *
887 * @param string $index Index name.
888 * @since 3.5
889 * @return boolean
890 */
891 public function close_index( $index ) {
892 $request_args = [
893 'method' => 'POST',
894 'timeout' => 30,
895 ];
896
897 $close = trailingslashit( $index ) . '_close';
898 $request = $this->remote_request( $close, $request_args, [], 'close_index' );
899
900 return ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) );
901 }
902
903 /**
904 * Open a closed index.
905 *
906 * @param string $index Index name.
907 * @since 3.5
908 * @return boolean
909 */
910 public function open_index( $index ) {
911 $request_args = [
912 'method' => 'POST',
913 'timeout' => 30,
914 ];
915
916 $open = trailingslashit( $index ) . '_open';
917 $request = $this->remote_request( $open, $request_args, [], 'open_index' );
918
919 return ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) );
920 }
921
922 /**
923 * Update index settings.
924 *
925 * @param string $index Index name.
926 * @param array $settings Setting update array.
927 * @param boolean $close_first Optional. True if index must be closed prior to update.
928 * Dynamic settings can be updated on open indices. Static
929 * settings must be closed. Default false.
930 * @since 3.5
931 * @return boolean
932 */
933 public function update_index_settings( $index, $settings, $close_first = false ) {
934 $request_args = [
935 'body' => wp_json_encode( $settings ),
936 'method' => 'PUT',
937 'timeout' => 30,
938 ];
939
940 $closed = false;
941 if ( $close_first ) {
942 $closed = $this->close_index( $index );
943 }
944
945 if ( ! $close_first || $closed ) {
946 $settings = trailingslashit( $index ) . '_settings';
947 $request = $this->remote_request( $settings, $request_args, [], 'update_index_settings' );
948 } else {
949 return false;
950 }
951
952 $updated = ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) );
953
954 if ( $closed ) {
955 $opened = $this->open_index( $index );
956 return ( $updated && $opened );
957 }
958
959 return $updated;
960 }
961
962 /**
963 * Delete an Elasticsearch index
964 *
965 * @param string $index Index name.
966 * @since 3.0
967 * @return boolean
968 */
969 public function delete_index( $index ) {
970
971 $request_args = [
972 'method' => 'DELETE',
973 'timeout' => 30,
974 ];
975
976 $request = $this->remote_request( $index, $request_args, [], 'delete_index' );
977
978 // 200 means the delete was successful
979 // 404 means the index was non-existent, but we should still pass this through as we will occasionally want to delete an already deleted index
980 if ( ! is_wp_error( $request ) && ( 200 === wp_remote_retrieve_response_code( $request ) || 404 === wp_remote_retrieve_response_code( $request ) ) ) {
981 $response_body = wp_remote_retrieve_body( $request );
982
983 return json_decode( $response_body );
984 }
985
986 return false;
987 }
988
989 /**
990 * Delete all indices
991 *
992 * @since 3.0
993 * @return boolean
994 */
995 public function delete_all_indices() {
996 return $this->delete_index( '*' );
997 }
998
999 /**
1000 * Check if an ES index exists
1001 *
1002 * @param string $index Index name.
1003 * @since 3.0
1004 * @return boolean
1005 */
1006 public function index_exists( $index ) {
1007
1008 $request_args = [
1009 'method' => 'HEAD',
1010 ];
1011
1012 $request = $this->remote_request( $index, $request_args, [], 'index_exists' );
1013
1014 // 200 means the index exists.
1015 // 404 means the index was non-existent.
1016 if ( ! is_wp_error( $request ) && ( 200 === wp_remote_retrieve_response_code( $request ) || 404 === wp_remote_retrieve_response_code( $request ) ) ) {
1017
1018 if ( 404 === wp_remote_retrieve_response_code( $request ) ) {
1019 return false;
1020 }
1021
1022 if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
1023 return true;
1024 }
1025 }
1026
1027 return false;
1028 }
1029
1030 /**
1031 * Bulk index Elasticsearch documents
1032 *
1033 * @param string $index Index name.
1034 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
1035 * @param string $body Encoded JSON.
1036 * @since 3.0
1037 * @return WP_Error|array
1038 */
1039 public function bulk_index( $index, $type, $body ) {
1040 /**
1041 * Filter Elasticsearch bulk index request path
1042 *
1043 * @hook ep_bulk_index_request_path
1044 * @param {string} Request path
1045 * @param {string} $body Bulk index request body
1046 * @param {string} $type Index type
1047 * @return {string} New path
1048 */
1049 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
1050 $path = apply_filters( 'ep_bulk_index_request_path', $index . '/' . $type . '/_bulk', $body, $type );
1051 } else {
1052 $path = apply_filters( 'ep_bulk_index_request_path', $index . '/_bulk', $body, $type );
1053 }
1054
1055 $request_args = array(
1056 'method' => 'POST',
1057 'body' => $body,
1058 'timeout' => apply_filters( 'ep_bulk_index_timeout', 30 ),
1059 );
1060
1061 $request = $this->remote_request( $path, $request_args, [], 'bulk_index' );
1062
1063 if ( is_wp_error( $request ) ) {
1064 return $request;
1065 }
1066
1067 $response = wp_remote_retrieve_response_code( $request );
1068
1069 if ( 200 !== $response ) {
1070 return new WP_Error( $response, wp_remote_retrieve_response_message( $request ), $request );
1071 }
1072
1073 return json_decode( wp_remote_retrieve_body( $request ), true );
1074 }
1075
1076 /**
1077 * Return queries for debugging
1078 *
1079 * @since 1.8
1080 * @return array
1081 */
1082 public function get_query_log() {
1083 return $this->queries;
1084 }
1085
1086 /**
1087 * Wrapper for wp_remote_request
1088 *
1089 * This is a wrapper function for wp_remote_request to account for request failures.
1090 *
1091 * @since 1.6
1092 *
1093 * @param string $path Site URL to retrieve.
1094 * @param array $args Optional. Request arguments. Default empty array.
1095 * @param array $query_args Optional. The query args originally passed to WP_Query.
1096 * @param string $type Type of request, used for debugging.
1097 *
1098 * @return WP_Error|array The response or WP_Error on failure.
1099 */
1100 public function remote_request( $path, $args = [], $query_args = [], $type = null ) {
1101
1102 if ( empty( $args['method'] ) ) {
1103 $args['method'] = 'GET';
1104 }
1105
1106 // Checks for any previously set headers
1107 $existing_headers = isset( $args['headers'] ) ? (array) $args['headers'] : [];
1108
1109 // Add the API Header.
1110 // Note that the "User Agent" header will be changed via WordPress's `http_headers_useragent` filter later.
1111 $new_headers = $this->format_request_headers();
1112
1113 $args['headers'] = array_merge( $existing_headers, $new_headers );
1114
1115 /**
1116 * Filter Elasticsearch args prior to remote request
1117 *
1118 * @hook ep_pre_request_args
1119 * @since 3.6.4
1120 * @param {array} $args Request args
1121 * @param {string} $path Site URL to retrieve
1122 * @param {array} $query_args The query args originally passed to WP_Query.
1123 * @param {string|null} $type Type of request, used for debugging.
1124 * @return {array} New request args
1125 */
1126 $args = apply_filters( 'ep_pre_request_args', $args, $path, $query_args, $type );
1127
1128 $query = array(
1129 'time_start' => microtime( true ),
1130 'time_finish' => false,
1131 'args' => $args,
1132 'blocking' => true,
1133 'failed_hosts' => [],
1134 'request' => false,
1135 'host' => Utils\get_host(),
1136 'query_args' => $query_args,
1137 );
1138
1139 $request = false;
1140 $failures = 0;
1141
1142 add_filter( 'http_headers_useragent', [ $this, 'add_elasticpress_version_to_user_agent' ] );
1143
1144 // Optionally let us try back up hosts and account for failures.
1145 while ( true ) {
1146 /**
1147 * Filter Elasticsearch host prior to remote request
1148 *
1149 * @hook ep_pre_request_host
1150 * @param {string} Request host
1151 * @param {int} $failures Number of current failures
1152 * @param {string} $path Request path
1153 * @param {array} $args Request arguments
1154 * @return {string} New host
1155 */
1156 $query['host'] = apply_filters( 'ep_pre_request_host', $query['host'], $failures, $path, $args );
1157
1158 /**
1159 * Filter Elasticsearch url prior to remote request
1160 *
1161 * @hook ep_pre_request_url
1162 * @param {string} Request url
1163 * @param {int} $failures Number of current failures
1164 * @param {string} $host Request host
1165 * @param {string} $path Request path
1166 * @param {array} $args Request arguments
1167 * @return {string} New url
1168 */
1169 $query['url'] = apply_filters( 'ep_pre_request_url', esc_url( trailingslashit( $query['host'] ) . $path ), $failures, $query['host'], $path, $args );
1170
1171 /**
1172 * Filter whether remote request should be intercepted
1173 *
1174 * @hook ep_intercept_remote_request
1175 * @param {boolean} $intercept True to intercept
1176 * @return {boolean} New value
1177 */
1178 if ( true === apply_filters( 'ep_intercept_remote_request', false ) ) {
1179 /**
1180 * Filter intercepted request
1181 *
1182 * @hook ep_do_intercept_request
1183 * @since 3.2.2
1184 * @since 3.6.5 added $type
1185 * @param {array} $request New remote request response
1186 * @param {array} $query Remote request arguments
1187 * @param {args} $args Request arguments
1188 * @param {int} $failures Number of failures
1189 * @param {string} $type Type of request
1190 * @return {array} New request
1191 */
1192 $request = apply_filters( 'ep_do_intercept_request', new WP_Error( 400, 'No Request defined' ), $query, $args, $failures, $type );
1193 } else {
1194 $request = wp_remote_request( $query['url'], $args ); // try the existing host to avoid unnecessary calls.
1195 }
1196
1197 $request_response_code = (int) wp_remote_retrieve_response_code( $request );
1198
1199 $is_valid_res = ( $request_response_code >= 200 && $request_response_code <= 299 );
1200 $is_non_blocking_request = ( 0 === $request_response_code );
1201
1202 if ( false === $request || is_wp_error( $request ) || ( ! $is_valid_res && ! $is_non_blocking_request ) ) {
1203 $failures++;
1204
1205 /**
1206 * Filter max number of times to attempt remote requests
1207 *
1208 * @hook ep_max_remote_request_tries
1209 * @param {int} $tries Number of times to try
1210 * @param {path} $path Request path
1211 * @param {args} $args Request arguments
1212 * @return {int} New number of tries
1213 */
1214 if ( $failures >= apply_filters( 'ep_max_remote_request_tries', 1, $path, $args ) ) {
1215 break;
1216 }
1217 } else {
1218 break;
1219 }
1220 }
1221
1222 remove_filter( 'http_headers_useragent', [ $this, 'add_elasticpress_version_to_user_agent' ] );
1223
1224 // Return now if we're not blocking, since we won't have a response yet.
1225 if ( isset( $args['blocking'] ) && false === $args['blocking'] ) {
1226 $query['blocking'] = true;
1227 $query['request'] = $request;
1228 $this->add_query_log( $query );
1229
1230 return $request;
1231 }
1232
1233 $query['time_finish'] = microtime( true );
1234 $query['request'] = $request;
1235 $this->add_query_log( $query );
1236
1237 /**
1238 * Fires after Elasticsearch remote request
1239 *
1240 * @hook ep_remote_request
1241 * @param {array} $query Remote request arguments
1242 * @param {string} $type Request type
1243 */
1244 do_action( 'ep_remote_request', $query, $type );
1245
1246 return $request;
1247
1248 }
1249
1250 /**
1251 * Parse response from Elasticsearch
1252 *
1253 * Determines if there is an issue or if the response is valid.
1254 *
1255 * @since 1.9
1256 * @param object $response JSON decoded response from Elasticsearch.
1257 * @return array Contains the status message or the returned statistics.
1258 */
1259 public function parse_api_response( $response ) {
1260
1261 if ( null === $response ) {
1262
1263 return array(
1264 'status' => false,
1265 'msg' => esc_html__( 'Invalid response from ElasticPress server. Please contact your administrator.' ),
1266 );
1267
1268 } elseif (
1269 isset( $response->error ) &&
1270 (
1271 ( is_string( $response->error ) && stristr( $response->error, 'IndexMissingException' ) ) ||
1272 ( isset( $response->error->reason ) && stristr( $response->error->reason, 'no such index' ) )
1273 )
1274 ) {
1275
1276 if ( is_multisite() ) {
1277
1278 $error = __( 'Site not indexed. <p>Please run: <code>wp elasticpress index --setup --network-wide</code> using WP-CLI. Or use the index button on the left of this screen.</p>', 'elasticpress' );
1279
1280 } else {
1281
1282 $error = __( 'Site not indexed. <p>Please run: <code>wp elasticpress index --setup</code> using WP-CLI. Or use the index button on the left of this screen.</p>', 'elasticpress' );
1283
1284 }
1285
1286 return array(
1287 'status' => false,
1288 'msg' => $error,
1289 );
1290
1291 }
1292
1293 return array(
1294 'status' => true,
1295 'data' => $response->_all->primaries->indexing,
1296 );
1297
1298 }
1299
1300 /**
1301 * Set ES plugins and version, detect server type, and cache everything
1302 *
1303 * @since 4.2.1
1304 * @param bool $force Bust cache or not.
1305 * @return array
1306 */
1307 public function set_elasticsearch_info( $force = false ) {
1308 if ( empty( Utils\get_host() ) ) {
1309 return;
1310 }
1311
1312 if ( ! $force && null !== $this->elasticsearch_version && null !== $this->elasticsearch_plugins ) {
1313 return;
1314 }
1315
1316 // Get ES info from cache if available. If we are forcing, then skip cache check.
1317 if ( ! $force ) {
1318 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
1319 $es_info = get_site_transient( 'ep_es_info' );
1320 } else {
1321 $es_info = get_transient( 'ep_es_info' );
1322 }
1323 if ( ! empty( $es_info ) ) {
1324 $this->elasticsearch_version = $es_info['version'];
1325 $this->elasticsearch_plugins = $es_info['plugins'];
1326 $this->server_type = $es_info['server_type'];
1327 return;
1328 }
1329 }
1330
1331 $path = '_nodes/plugins';
1332
1333 $request = $this->remote_request( $path, array( 'method' => 'GET' ) );
1334
1335 if ( is_wp_error( $request ) || 200 !== wp_remote_retrieve_response_code( $request ) ) {
1336 $this->elasticsearch_version = false;
1337 $this->elasticsearch_plugins = false;
1338
1339 /**
1340 * Try a different endpoint in case the plugins url is restricted
1341 *
1342 * @since 2.2.1
1343 */
1344
1345 $request = $this->remote_request( '', array( 'method' => 'GET' ) );
1346
1347 if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) {
1348 $response_body = wp_remote_retrieve_body( $request );
1349 $response = json_decode( $response_body, true );
1350
1351 try {
1352 $this->elasticsearch_version = $response['version']['number'];
1353 if ( ! empty( $response['version']['distribution'] ) ) {
1354 $this->server_type = $response['version']['distribution'];
1355 }
1356 } catch ( \Exception $e ) {
1357 // Do nothing.
1358 }
1359 }
1360 return;
1361 }
1362
1363 $response = json_decode( wp_remote_retrieve_body( $request ), true );
1364
1365 $this->elasticsearch_plugins = [];
1366 $this->elasticsearch_version = false;
1367
1368 if ( isset( $response['nodes'] ) ) {
1369 $node = end( $response['nodes'] );
1370 // Save version of last node. We assume all nodes are same version.
1371 $this->elasticsearch_version = $node['version'];
1372
1373 if ( isset( $node['plugins'] ) && is_array( $node['plugins'] ) ) {
1374 foreach ( $node['plugins'] as $plugin ) {
1375 $this->elasticsearch_plugins[ $plugin['name'] ] = $plugin['version'];
1376 }
1377 }
1378 if ( isset( $node['modules'] )
1379 && is_array( $node['modules'] )
1380 && ! empty( $node['modules'] )
1381 && ! empty( $node['modules'][0]['opensearch_version'] )
1382 ) {
1383 $this->server_type = 'opensearch';
1384 }
1385 }
1386
1387 /**
1388 * Cache ES info
1389 *
1390 * @since 2.3.1
1391 */
1392 $this->cache_elasticsearch_info();
1393 }
1394
1395 /**
1396 * Return ES plugins, version and type.
1397 *
1398 * This function also sets those values in the object instance, getting it from cache
1399 * or not, according to `$force` value.
1400 *
1401 * @param bool $force Bust cache or not.
1402 * @since 2.2
1403 * @return array
1404 */
1405 public function get_elasticsearch_info( $force = false ) {
1406 $this->set_elasticsearch_info( $force );
1407 return [
1408 'plugins' => $this->elasticsearch_plugins,
1409 'version' => $this->elasticsearch_version,
1410 'server_type' => $this->server_type,
1411 ];
1412 }
1413
1414 /**
1415 * Cache the ES info.
1416 *
1417 * @since 4.2.1
1418 */
1419 protected function cache_elasticsearch_info() {
1420 /**
1421 * Filter elasticsearch info cache expiration
1422 *
1423 * @hook ep_es_info_cache_expiration
1424 * @param {int} $time Cache time in seconds
1425 * @return {int} New cache time
1426 */
1427 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
1428 set_site_transient(
1429 'ep_es_info',
1430 array(
1431 'version' => $this->elasticsearch_version,
1432 'plugins' => $this->elasticsearch_plugins,
1433 'server_type' => $this->server_type,
1434 ),
1435 apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) )
1436 );
1437 } else {
1438 set_transient(
1439 'ep_es_info',
1440 array(
1441 'version' => $this->elasticsearch_version,
1442 'plugins' => $this->elasticsearch_plugins,
1443 'server_type' => $this->server_type,
1444 ),
1445 apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) )
1446 );
1447 }
1448 }
1449
1450 /**
1451 * Get cluster status
1452 *
1453 * Retrieves cluster stats from Elasticsearch.
1454 *
1455 * @since 1.9
1456 * @return array Contains the status message or the returned statistics.
1457 */
1458 public function get_cluster_status() {
1459
1460 if ( is_wp_error( Utils\get_host() ) ) {
1461
1462 return array(
1463 'status' => false,
1464 'msg' => esc_html__( 'Elasticsearch Host is not available.', 'elasticpress' ),
1465 );
1466
1467 } else {
1468
1469 $request = $this->remote_request( '_cluster/stats', array( 'method' => 'GET' ) );
1470
1471 if ( ! is_wp_error( $request ) ) {
1472
1473 $response = json_decode( wp_remote_retrieve_body( $request ) );
1474
1475 return $response;
1476
1477 }
1478
1479 return array(
1480 'status' => false,
1481 'msg' => $request->get_error_message(),
1482 );
1483
1484 }
1485 }
1486
1487 /**
1488 * Get an Elasticsearch pipeline
1489 *
1490 * @param string $id Id of pipeline.
1491 * @since 2.3
1492 * @return WP_Error|bool|array
1493 */
1494 public function get_pipeline( $id ) {
1495 $path = '_ingest/pipeline/' . $id;
1496
1497 $request_args = array(
1498 'method' => 'GET',
1499 );
1500
1501 /**
1502 * Filter get pipeline request arguments
1503 *
1504 * @hook ep_get_pipeline_args
1505 * @param {array} $request_args Request arguments
1506 * @return {array} New arguments
1507 */
1508 $request = $this->remote_request( $path, apply_filters( 'ep_get_pipeline_args', $request_args ), [], 'get_pipeline' );
1509
1510 if ( is_wp_error( $request ) ) {
1511 return $request;
1512 }
1513
1514 $response = wp_remote_retrieve_response_code( $request );
1515
1516 if ( 200 !== $response ) {
1517 return new WP_Error( $response, wp_remote_retrieve_response_message( $request ), $request );
1518 }
1519
1520 $body = json_decode( wp_remote_retrieve_body( $request ), true );
1521
1522 if ( empty( $body ) ) {
1523 return false;
1524 }
1525
1526 return $body;
1527 }
1528
1529 /**
1530 * Put an Elasticsearch pipeline
1531 *
1532 * @param string $id Pipeline id.
1533 * @param array $args Args to send to ES.
1534 * @since 2.3
1535 * @return WP_Error|bool
1536 */
1537 public function create_pipeline( $id, $args ) {
1538 $path = '_ingest/pipeline/' . $id;
1539
1540 $request_args = array(
1541 'body' => wp_json_encode( $args ),
1542 'method' => 'PUT',
1543 );
1544
1545 /**
1546 * Filter create pipeline request arguments
1547 *
1548 * @hook ep_create_pipeline_args
1549 * @param {array} $request_args Request arguments
1550 * @return {array} New arguments
1551 */
1552 $request = $this->remote_request( $path, apply_filters( 'ep_create_pipeline_args', $request_args ), [], 'create_pipeline' );
1553
1554 if ( is_wp_error( $request ) ) {
1555 return $request;
1556 }
1557
1558 $response = wp_remote_retrieve_response_code( $request );
1559
1560 if ( 200 > $response || 300 <= $response ) {
1561 return new WP_Error( $response, wp_remote_retrieve_response_message( $request ), $request );
1562 }
1563
1564 $body = json_decode( wp_remote_retrieve_body( $request ), true );
1565
1566 if ( empty( $body ) ) {
1567 return false;
1568 }
1569
1570 return true;
1571 }
1572
1573 /**
1574 * Conditionally add the ElasticPress version to the User Agent string.
1575 *
1576 * @since 3.6.1
1577 * @param string $user_agent Original User Agent.
1578 * @return string
1579 */
1580 public function add_elasticpress_version_to_user_agent( $user_agent ) {
1581 /**
1582 * Filter the User Agent header when submitting requests to Elasticsearch.
1583 *
1584 * @hook ep_remote_request_add_ep_user_agent
1585 * @param {bool} $should_add_ep_verion Whether the ElasticPress version should be added to the User Agent string.
1586 * @return {bool} New value
1587 * @since 3.6.1
1588 */
1589 if ( apply_filters( 'ep_remote_request_add_ep_user_agent', Utils\is_epio() ) ) {
1590 $end_part = '; ' . get_bloginfo( 'url' );
1591 $user_agent = str_replace(
1592 $end_part,
1593 ' (ElasticPress/' . EP_VERSION . ')' . $end_part,
1594 $user_agent
1595 );
1596 }
1597 return $user_agent;
1598 }
1599
1600 /**
1601 * Query logging. Don't log anything to the queries property when
1602 * WP_DEBUG is not enabled. Calls action 'ep_add_query_log' if you
1603 * want to access the query outside of the ElasticPress plugin. This
1604 * runs regardless of debufg settings.
1605 *
1606 * @param array $query Query to log.
1607 */
1608 protected function add_query_log( $query ) {
1609 if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || ( defined( 'WP_EP_DEBUG' ) && WP_EP_DEBUG ) ) {
1610 $this->queries[] = $query;
1611 }
1612
1613 /**
1614 * Fires after item is added to the query log
1615 *
1616 * @hook ep_add_query_log
1617 * @param {array} $query Query to log
1618 */
1619 do_action( 'ep_add_query_log', $query );
1620 }
1621
1622 }
1623