PluginProbe
ElasticPress / 4.2.2
ElasticPress v4.2.2
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.2, at includes/classes/Elasticsearch.php

1,618 lines 43.9 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 if ( $close_first ) {
941 $this->close_index( $index );
942 }
943
944 $settings = trailingslashit( $index ) . '_settings';
945 $request = $this->remote_request( $settings, $request_args, [], 'update_index_settings' );
946
947 $updated = ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) );
948
949 if ( $close_first ) {
950 $opened = $this->open_index( $index );
951 return ( $updated && $opened );
952 }
953
954 return $updated;
955 }
956
957 /**
958 * Delete an Elasticsearch index
959 *
960 * @param string $index Index name.
961 * @since 3.0
962 * @return boolean
963 */
964 public function delete_index( $index ) {
965
966 $request_args = [
967 'method' => 'DELETE',
968 'timeout' => 30,
969 ];
970
971 $request = $this->remote_request( $index, $request_args, [], 'delete_index' );
972
973 // 200 means the delete was successful
974 // 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
975 if ( ! is_wp_error( $request ) && ( 200 === wp_remote_retrieve_response_code( $request ) || 404 === wp_remote_retrieve_response_code( $request ) ) ) {
976 $response_body = wp_remote_retrieve_body( $request );
977
978 return json_decode( $response_body );
979 }
980
981 return false;
982 }
983
984 /**
985 * Delete all indices
986 *
987 * @since 3.0
988 * @return boolean
989 */
990 public function delete_all_indices() {
991 return $this->delete_index( '*' );
992 }
993
994 /**
995 * Check if an ES index exists
996 *
997 * @param string $index Index name.
998 * @since 3.0
999 * @return boolean
1000 */
1001 public function index_exists( $index ) {
1002
1003 $request_args = [
1004 'method' => 'HEAD',
1005 ];
1006
1007 $request = $this->remote_request( $index, $request_args, [], 'index_exists' );
1008
1009 // 200 means the index exists.
1010 // 404 means the index was non-existent.
1011 if ( ! is_wp_error( $request ) && ( 200 === wp_remote_retrieve_response_code( $request ) || 404 === wp_remote_retrieve_response_code( $request ) ) ) {
1012
1013 if ( 404 === wp_remote_retrieve_response_code( $request ) ) {
1014 return false;
1015 }
1016
1017 if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
1018 return true;
1019 }
1020 }
1021
1022 return false;
1023 }
1024
1025 /**
1026 * Bulk index Elasticsearch documents
1027 *
1028 * @param string $index Index name.
1029 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
1030 * @param string $body Encoded JSON.
1031 * @since 3.0
1032 * @return WP_Error|array
1033 */
1034 public function bulk_index( $index, $type, $body ) {
1035 /**
1036 * Filter Elasticsearch bulk index request path
1037 *
1038 * @hook ep_bulk_index_request_path
1039 * @param {string} Request path
1040 * @param {string} $body Bulk index request body
1041 * @param {string} $type Index type
1042 * @return {string} New path
1043 */
1044 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
1045 $path = apply_filters( 'ep_bulk_index_request_path', $index . '/' . $type . '/_bulk', $body, $type );
1046 } else {
1047 $path = apply_filters( 'ep_bulk_index_request_path', $index . '/_bulk', $body, $type );
1048 }
1049
1050 $request_args = array(
1051 'method' => 'POST',
1052 'body' => $body,
1053 'timeout' => apply_filters( 'ep_bulk_index_timeout', 30 ),
1054 );
1055
1056 $request = $this->remote_request( $path, $request_args, [], 'bulk_index' );
1057
1058 if ( is_wp_error( $request ) ) {
1059 return $request;
1060 }
1061
1062 $response = wp_remote_retrieve_response_code( $request );
1063
1064 if ( 200 !== $response ) {
1065 return new WP_Error( $response, wp_remote_retrieve_response_message( $request ), $request );
1066 }
1067
1068 return json_decode( wp_remote_retrieve_body( $request ), true );
1069 }
1070
1071 /**
1072 * Return queries for debugging
1073 *
1074 * @since 1.8
1075 * @return array
1076 */
1077 public function get_query_log() {
1078 return $this->queries;
1079 }
1080
1081 /**
1082 * Wrapper for wp_remote_request
1083 *
1084 * This is a wrapper function for wp_remote_request to account for request failures.
1085 *
1086 * @since 1.6
1087 *
1088 * @param string $path Site URL to retrieve.
1089 * @param array $args Optional. Request arguments. Default empty array.
1090 * @param array $query_args Optional. The query args originally passed to WP_Query.
1091 * @param string $type Type of request, used for debugging.
1092 *
1093 * @return WP_Error|array The response or WP_Error on failure.
1094 */
1095 public function remote_request( $path, $args = [], $query_args = [], $type = null ) {
1096
1097 if ( empty( $args['method'] ) ) {
1098 $args['method'] = 'GET';
1099 }
1100
1101 // Checks for any previously set headers
1102 $existing_headers = isset( $args['headers'] ) ? (array) $args['headers'] : [];
1103
1104 // Add the API Header.
1105 // Note that the "User Agent" header will be changed via WordPress's `http_headers_useragent` filter later.
1106 $new_headers = $this->format_request_headers();
1107
1108 $args['headers'] = array_merge( $existing_headers, $new_headers );
1109
1110 /**
1111 * Filter Elasticsearch args prior to remote request
1112 *
1113 * @hook ep_pre_request_args
1114 * @since 3.6.4
1115 * @param {array} $args Request args
1116 * @param {string} $path Site URL to retrieve
1117 * @param {array} $query_args The query args originally passed to WP_Query.
1118 * @param {string|null} $type Type of request, used for debugging.
1119 * @return {array} New request args
1120 */
1121 $args = apply_filters( 'ep_pre_request_args', $args, $path, $query_args, $type );
1122
1123 $query = array(
1124 'time_start' => microtime( true ),
1125 'time_finish' => false,
1126 'args' => $args,
1127 'blocking' => true,
1128 'failed_hosts' => [],
1129 'request' => false,
1130 'host' => Utils\get_host(),
1131 'query_args' => $query_args,
1132 );
1133
1134 $request = false;
1135 $failures = 0;
1136
1137 add_filter( 'http_headers_useragent', [ $this, 'add_elasticpress_version_to_user_agent' ] );
1138
1139 // Optionally let us try back up hosts and account for failures.
1140 while ( true ) {
1141 /**
1142 * Filter Elasticsearch host prior to remote request
1143 *
1144 * @hook ep_pre_request_host
1145 * @param {string} Request host
1146 * @param {int} $failures Number of current failures
1147 * @param {string} $path Request path
1148 * @param {array} $args Request arguments
1149 * @return {string} New host
1150 */
1151 $query['host'] = apply_filters( 'ep_pre_request_host', $query['host'], $failures, $path, $args );
1152
1153 /**
1154 * Filter Elasticsearch url prior to remote request
1155 *
1156 * @hook ep_pre_request_url
1157 * @param {string} Request url
1158 * @param {int} $failures Number of current failures
1159 * @param {string} $host Request host
1160 * @param {string} $path Request path
1161 * @param {array} $args Request arguments
1162 * @return {string} New url
1163 */
1164 $query['url'] = apply_filters( 'ep_pre_request_url', esc_url( trailingslashit( $query['host'] ) . $path ), $failures, $query['host'], $path, $args );
1165
1166 /**
1167 * Filter whether remote request should be intercepted
1168 *
1169 * @hook ep_intercept_remote_request
1170 * @param {boolean} $intercept True to intercept
1171 * @return {boolean} New value
1172 */
1173 if ( true === apply_filters( 'ep_intercept_remote_request', false ) ) {
1174 /**
1175 * Filter intercepted request
1176 *
1177 * @hook ep_do_intercept_request
1178 * @since 3.2.2
1179 * @since 3.6.5 added $type
1180 * @param {array} $request New remote request response
1181 * @param {array} $query Remote request arguments
1182 * @param {args} $args Request arguments
1183 * @param {int} $failures Number of failures
1184 * @param {string} $type Type of request
1185 * @return {array} New request
1186 */
1187 $request = apply_filters( 'ep_do_intercept_request', new WP_Error( 400, 'No Request defined' ), $query, $args, $failures, $type );
1188 } else {
1189 $request = wp_remote_request( $query['url'], $args ); // try the existing host to avoid unnecessary calls.
1190 }
1191
1192 $request_response_code = (int) wp_remote_retrieve_response_code( $request );
1193
1194 $is_valid_res = ( $request_response_code >= 200 && $request_response_code <= 299 );
1195 $is_non_blocking_request = ( 0 === $request_response_code );
1196
1197 if ( false === $request || is_wp_error( $request ) || ( ! $is_valid_res && ! $is_non_blocking_request ) ) {
1198 $failures++;
1199
1200 /**
1201 * Filter max number of times to attempt remote requests
1202 *
1203 * @hook ep_max_remote_request_tries
1204 * @param {int} $tries Number of times to try
1205 * @param {path} $path Request path
1206 * @param {args} $args Request arguments
1207 * @return {int} New number of tries
1208 */
1209 if ( $failures >= apply_filters( 'ep_max_remote_request_tries', 1, $path, $args ) ) {
1210 break;
1211 }
1212 } else {
1213 break;
1214 }
1215 }
1216
1217 remove_filter( 'http_headers_useragent', [ $this, 'add_elasticpress_version_to_user_agent' ] );
1218
1219 // Return now if we're not blocking, since we won't have a response yet.
1220 if ( isset( $args['blocking'] ) && false === $args['blocking'] ) {
1221 $query['blocking'] = true;
1222 $query['request'] = $request;
1223 $this->add_query_log( $query );
1224
1225 return $request;
1226 }
1227
1228 $query['time_finish'] = microtime( true );
1229 $query['request'] = $request;
1230 $this->add_query_log( $query );
1231
1232 /**
1233 * Fires after Elasticsearch remote request
1234 *
1235 * @hook ep_remote_request
1236 * @param {array} $query Remote request arguments
1237 * @param {string} $type Request type
1238 */
1239 do_action( 'ep_remote_request', $query, $type );
1240
1241 return $request;
1242
1243 }
1244
1245 /**
1246 * Parse response from Elasticsearch
1247 *
1248 * Determines if there is an issue or if the response is valid.
1249 *
1250 * @since 1.9
1251 * @param object $response JSON decoded response from Elasticsearch.
1252 * @return array Contains the status message or the returned statistics.
1253 */
1254 public function parse_api_response( $response ) {
1255
1256 if ( null === $response ) {
1257
1258 return array(
1259 'status' => false,
1260 'msg' => esc_html__( 'Invalid response from ElasticPress server. Please contact your administrator.' ),
1261 );
1262
1263 } elseif (
1264 isset( $response->error ) &&
1265 (
1266 ( is_string( $response->error ) && stristr( $response->error, 'IndexMissingException' ) ) ||
1267 ( isset( $response->error->reason ) && stristr( $response->error->reason, 'no such index' ) )
1268 )
1269 ) {
1270
1271 if ( is_multisite() ) {
1272
1273 $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' );
1274
1275 } else {
1276
1277 $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' );
1278
1279 }
1280
1281 return array(
1282 'status' => false,
1283 'msg' => $error,
1284 );
1285
1286 }
1287
1288 return array(
1289 'status' => true,
1290 'data' => $response->_all->primaries->indexing,
1291 );
1292
1293 }
1294
1295 /**
1296 * Set ES plugins and version, detect server type, and cache everything
1297 *
1298 * @since 4.2.1
1299 * @param bool $force Bust cache or not.
1300 * @return array
1301 */
1302 public function set_elasticsearch_info( $force = false ) {
1303 if ( empty( Utils\get_host() ) ) {
1304 return;
1305 }
1306
1307 if ( ! $force && null !== $this->elasticsearch_version && null !== $this->elasticsearch_plugins ) {
1308 return;
1309 }
1310
1311 // Get ES info from cache if available. If we are forcing, then skip cache check.
1312 if ( ! $force ) {
1313 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
1314 $es_info = get_site_transient( 'ep_es_info' );
1315 } else {
1316 $es_info = get_transient( 'ep_es_info' );
1317 }
1318 if ( ! empty( $es_info ) ) {
1319 $this->elasticsearch_version = $es_info['version'];
1320 $this->elasticsearch_plugins = $es_info['plugins'];
1321 $this->server_type = $es_info['server_type'];
1322 return;
1323 }
1324 }
1325
1326 $path = '_nodes/plugins';
1327
1328 $request = $this->remote_request( $path, array( 'method' => 'GET' ) );
1329
1330 if ( is_wp_error( $request ) || 200 !== wp_remote_retrieve_response_code( $request ) ) {
1331 $this->elasticsearch_version = false;
1332 $this->elasticsearch_plugins = false;
1333
1334 /**
1335 * Try a different endpoint in case the plugins url is restricted
1336 *
1337 * @since 2.2.1
1338 */
1339
1340 $request = $this->remote_request( '', array( 'method' => 'GET' ) );
1341
1342 if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) {
1343 $response_body = wp_remote_retrieve_body( $request );
1344 $response = json_decode( $response_body, true );
1345
1346 try {
1347 $this->elasticsearch_version = $response['version']['number'];
1348 if ( ! empty( $response['version']['distribution'] ) ) {
1349 $this->server_type = $response['version']['distribution'];
1350 }
1351 } catch ( \Exception $e ) {
1352 // Do nothing.
1353 }
1354 }
1355 return;
1356 }
1357
1358 $response = json_decode( wp_remote_retrieve_body( $request ), true );
1359
1360 $this->elasticsearch_plugins = [];
1361 $this->elasticsearch_version = false;
1362
1363 if ( isset( $response['nodes'] ) ) {
1364 $node = end( $response['nodes'] );
1365 // Save version of last node. We assume all nodes are same version.
1366 $this->elasticsearch_version = $node['version'];
1367
1368 if ( isset( $node['plugins'] ) && is_array( $node['plugins'] ) ) {
1369 foreach ( $node['plugins'] as $plugin ) {
1370 $this->elasticsearch_plugins[ $plugin['name'] ] = $plugin['version'];
1371 }
1372 }
1373 if ( isset( $node['modules'] )
1374 && is_array( $node['modules'] )
1375 && ! empty( $node['modules'] )
1376 && ! empty( $node['modules'][0]['opensearch_version'] )
1377 ) {
1378 $this->server_type = 'opensearch';
1379 }
1380 }
1381
1382 /**
1383 * Cache ES info
1384 *
1385 * @since 2.3.1
1386 */
1387 $this->cache_elasticsearch_info();
1388 }
1389
1390 /**
1391 * Return ES plugins, version and type.
1392 *
1393 * This function also sets those values in the object instance, getting it from cache
1394 * or not, according to `$force` value.
1395 *
1396 * @param bool $force Bust cache or not.
1397 * @since 2.2
1398 * @return array
1399 */
1400 public function get_elasticsearch_info( $force = false ) {
1401 $this->set_elasticsearch_info( $force );
1402 return [
1403 'plugins' => $this->elasticsearch_plugins,
1404 'version' => $this->elasticsearch_version,
1405 'server_type' => $this->server_type,
1406 ];
1407 }
1408
1409 /**
1410 * Cache the ES info.
1411 *
1412 * @since 4.2.1
1413 */
1414 protected function cache_elasticsearch_info() {
1415 /**
1416 * Filter elasticsearch info cache expiration
1417 *
1418 * @hook ep_es_info_cache_expiration
1419 * @param {int} $time Cache time in seconds
1420 * @return {int} New cache time
1421 */
1422 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
1423 set_site_transient(
1424 'ep_es_info',
1425 array(
1426 'version' => $this->elasticsearch_version,
1427 'plugins' => $this->elasticsearch_plugins,
1428 'server_type' => $this->server_type,
1429 ),
1430 apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) )
1431 );
1432 } else {
1433 set_transient(
1434 'ep_es_info',
1435 array(
1436 'version' => $this->elasticsearch_version,
1437 'plugins' => $this->elasticsearch_plugins,
1438 'server_type' => $this->server_type,
1439 ),
1440 apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) )
1441 );
1442 }
1443 }
1444
1445 /**
1446 * Get cluster status
1447 *
1448 * Retrieves cluster stats from Elasticsearch.
1449 *
1450 * @since 1.9
1451 * @return array Contains the status message or the returned statistics.
1452 */
1453 public function get_cluster_status() {
1454
1455 if ( is_wp_error( Utils\get_host() ) ) {
1456
1457 return array(
1458 'status' => false,
1459 'msg' => esc_html__( 'Elasticsearch Host is not available.', 'elasticpress' ),
1460 );
1461
1462 } else {
1463
1464 $request = $this->remote_request( '_cluster/stats', array( 'method' => 'GET' ) );
1465
1466 if ( ! is_wp_error( $request ) ) {
1467
1468 $response = json_decode( wp_remote_retrieve_body( $request ) );
1469
1470 return $response;
1471
1472 }
1473
1474 return array(
1475 'status' => false,
1476 'msg' => $request->get_error_message(),
1477 );
1478
1479 }
1480 }
1481
1482 /**
1483 * Get an Elasticsearch pipeline
1484 *
1485 * @param string $id Id of pipeline.
1486 * @since 2.3
1487 * @return WP_Error|bool|array
1488 */
1489 public function get_pipeline( $id ) {
1490 $path = '_ingest/pipeline/' . $id;
1491
1492 $request_args = array(
1493 'method' => 'GET',
1494 );
1495
1496 /**
1497 * Filter get pipeline request arguments
1498 *
1499 * @hook ep_get_pipeline_args
1500 * @param {array} $request_args Request arguments
1501 * @return {array} New arguments
1502 */
1503 $request = $this->remote_request( $path, apply_filters( 'ep_get_pipeline_args', $request_args ), [], 'get_pipeline' );
1504
1505 if ( is_wp_error( $request ) ) {
1506 return $request;
1507 }
1508
1509 $response = wp_remote_retrieve_response_code( $request );
1510
1511 if ( 200 !== $response ) {
1512 return new WP_Error( $response, wp_remote_retrieve_response_message( $request ), $request );
1513 }
1514
1515 $body = json_decode( wp_remote_retrieve_body( $request ), true );
1516
1517 if ( empty( $body ) ) {
1518 return false;
1519 }
1520
1521 return $body;
1522 }
1523
1524 /**
1525 * Put an Elasticsearch pipeline
1526 *
1527 * @param string $id Pipeline id.
1528 * @param array $args Args to send to ES.
1529 * @since 2.3
1530 * @return WP_Error|bool
1531 */
1532 public function create_pipeline( $id, $args ) {
1533 $path = '_ingest/pipeline/' . $id;
1534
1535 $request_args = array(
1536 'body' => wp_json_encode( $args ),
1537 'method' => 'PUT',
1538 );
1539
1540 /**
1541 * Filter create pipeline request arguments
1542 *
1543 * @hook ep_create_pipeline_args
1544 * @param {array} $request_args Request arguments
1545 * @return {array} New arguments
1546 */
1547 $request = $this->remote_request( $path, apply_filters( 'ep_create_pipeline_args', $request_args ), [], 'create_pipeline' );
1548
1549 if ( is_wp_error( $request ) ) {
1550 return $request;
1551 }
1552
1553 $response = wp_remote_retrieve_response_code( $request );
1554
1555 if ( 200 > $response || 300 <= $response ) {
1556 return new WP_Error( $response, wp_remote_retrieve_response_message( $request ), $request );
1557 }
1558
1559 $body = json_decode( wp_remote_retrieve_body( $request ), true );
1560
1561 if ( empty( $body ) ) {
1562 return false;
1563 }
1564
1565 return true;
1566 }
1567
1568 /**
1569 * Conditionally add the ElasticPress version to the User Agent string.
1570 *
1571 * @since 3.6.1
1572 * @param string $user_agent Original User Agent.
1573 * @return string
1574 */
1575 public function add_elasticpress_version_to_user_agent( $user_agent ) {
1576 /**
1577 * Filter the User Agent header when submitting requests to Elasticsearch.
1578 *
1579 * @hook ep_remote_request_add_ep_user_agent
1580 * @param {bool} $should_add_ep_verion Whether the ElasticPress version should be added to the User Agent string.
1581 * @return {bool} New value
1582 * @since 3.6.1
1583 */
1584 if ( apply_filters( 'ep_remote_request_add_ep_user_agent', Utils\is_epio() ) ) {
1585 $end_part = '; ' . get_bloginfo( 'url' );
1586 $user_agent = str_replace(
1587 $end_part,
1588 ' (ElasticPress/' . EP_VERSION . ')' . $end_part,
1589 $user_agent
1590 );
1591 }
1592 return $user_agent;
1593 }
1594
1595 /**
1596 * Query logging. Don't log anything to the queries property when
1597 * WP_DEBUG is not enabled. Calls action 'ep_add_query_log' if you
1598 * want to access the query outside of the ElasticPress plugin. This
1599 * runs regardless of debufg settings.
1600 *
1601 * @param array $query Query to log.
1602 */
1603 protected function add_query_log( $query ) {
1604 if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || ( defined( 'WP_EP_DEBUG' ) && WP_EP_DEBUG ) ) {
1605 $this->queries[] = $query;
1606 }
1607
1608 /**
1609 * Fires after item is added to the query log
1610 *
1611 * @hook ep_add_query_log
1612 * @param {array} $query Query to log
1613 */
1614 do_action( 'ep_add_query_log', $query );
1615 }
1616
1617 }
1618