PluginProbe
ElasticPress / 4.3.0
ElasticPress v4.3.0
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
elasticpress / includes / classes / Elasticsearch.php

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

1,619 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 'aggregations' => $response['aggregations'] ?? [],
470 ],
471 $response,
472 $query,
473 $query_args,
474 $query_object
475 );
476 }
477
478 /**
479 * Fires after invalid Elasticsearch query
480 *
481 * @hook ep_invalid_response
482 * @param {array} $request Remote request response
483 * @param {array} $query Prepared Elasticsearch query
484 * @param {array} $query_args Current WP Query arguments
485 * @param {mixed} $query_object Could be WP_Query, WP_User_Query, etc.
486 */
487 do_action( 'ep_invalid_response', $request, $query, $query_args, $query_object );
488
489 return false;
490 }
491
492 /**
493 * Returns the number of total results that ElasticSearch found for the given query
494 *
495 * @param array $response Response to get total hits from.
496 * @since 2.5
497 * @return int
498 */
499 public function get_total_hits_from_query( $response ) {
500
501 if ( $this->is_empty_query( $response ) ) {
502 return 0;
503 }
504
505 return $response['hits']['total'];
506 }
507
508 /**
509 * Returns array containing hits returned from query, if such exist
510 *
511 * @param array $response Response to get hits from.
512 * @since 2.5
513 * @return array
514 */
515 public function get_hits_from_query( $response ) {
516
517 if ( $this->is_empty_query( $response ) ) {
518 return [];
519 }
520
521 /**
522 * Filter Elasticsearch allows to flatten hits, if searched hits are come within aggregations.
523 *
524 * @see https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-top-hits-aggregation.html
525 *
526 * @hook ep_get_hits_from_query
527 * @param {array} $hits from Elasticsearch
528 * @param {response} $response Raw response from Elasticsearch
529 * @return {array} hits
530 */
531 return apply_filters( 'ep_get_hits_from_query', $response['hits']['hits'], $response );
532 }
533
534 /**
535 * Check if a response array contains results or not
536 *
537 * @param array $response Response to check.
538 * @since 0.1.2
539 * @return bool
540 */
541 public function is_empty_query( $response ) {
542
543 if ( ! is_array( $response ) ) {
544 return true;
545 }
546
547 if ( isset( $response['error'] ) ) {
548 return true;
549 }
550
551 if ( empty( $response['hits'] ) ) {
552 return true;
553 }
554
555 if ( isset( $response['hits']['total'] ) && 0 === (int) $response['hits']['total'] ) {
556 return true;
557 }
558
559 return false;
560 }
561
562 /**
563 * Delete an Elasticsearch document
564 *
565 * @param string $index Index name.
566 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
567 * @param int $document_id Document id to delete.
568 * @param boolean $blocking Blocking HTTP request or not.
569 * @since 3.0
570 * @return boolean
571 */
572 public function delete_document( $index, $type, $document_id, $blocking = true ) {
573 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
574 $path = $index . '/' . $type . '/' . $document_id;
575 } else {
576 $path = $index . '/_doc/' . $document_id;
577 }
578
579 $request_args = [
580 'method' => 'DELETE',
581 'timeout' => 15,
582 'blocking' => $blocking,
583 ];
584
585 $request = $this->remote_request( $path, $request_args, [], 'delete' );
586
587 if ( ! is_wp_error( $request ) ) {
588 $response_body = wp_remote_retrieve_body( $request );
589
590 $response = json_decode( $response_body, true );
591
592 if ( ! empty( $response['found'] ) ) {
593 return true;
594 }
595 }
596
597 return false;
598 }
599
600 /**
601 * Add appropriate headers to request
602 *
603 * @since 1.4
604 * @return array
605 */
606 public function format_request_headers() {
607 $headers = array(
608 'Content-Type' => 'application/json',
609 );
610
611 // Check for ElasticPress API key and add to header if needed.
612 if ( defined( 'EP_API_KEY' ) && EP_API_KEY ) {
613 $headers['X-ElasticPress-API-Key'] = EP_API_KEY;
614 }
615
616 /**
617 * ES Shield info
618 *
619 * @since 1.9
620 */
621 $shield = Utils\get_shield_credentials();
622
623 if ( ! empty( $shield ) ) {
624 // phpcs:disable
625 $headers['Authorization'] = 'Basic ' . base64_encode( $shield );
626 // phpcs:enable
627 }
628
629 /**
630 * Filter Elasticsearch request headers
631 *
632 * @hook ep_format_request_headers
633 * @param {array} $headers Current headers
634 * @return {array} New headers
635 */
636 $headers = apply_filters( 'ep_format_request_headers', $headers );
637
638 return $headers;
639 }
640
641 /**
642 * Get a document from Elasticsearch given an id
643 *
644 * @param string $index Index name.
645 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
646 * @param int $document_id Document id to get.
647 * @since 3.0
648 * @return boolean|array
649 */
650 public function get_document( $index, $type, $document_id ) {
651 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
652 $path = $index . '/' . $type . '/' . $document_id;
653 } else {
654 $path = $index . '/_doc/' . $document_id;
655 }
656
657 $request_args = [ 'method' => 'GET' ];
658
659 $request = $this->remote_request( $path, $request_args, [], 'get' );
660
661 if ( ! is_wp_error( $request ) ) {
662 $response_body = wp_remote_retrieve_body( $request );
663
664 $response = json_decode( $response_body, true );
665
666 if ( ! empty( $response['exists'] ) || ! empty( $response['found'] ) ) {
667 return $response['_source'];
668 }
669 }
670
671 return false;
672 }
673
674 /**
675 * Delete the network alias.
676 *
677 * Network aliases are used to query documents across blogs in a network.
678 *
679 * @param string $alias Alias to use.
680 * @since 3.0
681 * @return array|boolean
682 */
683 public function delete_network_alias( $alias ) {
684 $path = '*/_alias/' . $alias;
685
686 $request_args = [ 'method' => 'DELETE' ];
687
688 $request = $this->remote_request( $path, $request_args, [], 'delete_network_alias' );
689
690 if ( ! is_wp_error( $request ) && ( 200 >= wp_remote_retrieve_response_code( $request ) && 300 > wp_remote_retrieve_response_code( $request ) ) ) {
691 $response_body = wp_remote_retrieve_body( $request );
692
693 return json_decode( $response_body );
694 }
695
696 return false;
697 }
698
699 /**
700 * Get multiple documents from Elasticsearch given an array of ids
701 *
702 * @param string $index Index name.
703 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
704 * @param array $document_ids Array of document ids to get.
705 * @since 3.6.0
706 * @return boolean|array
707 */
708 public function get_documents( $index, $type, $document_ids ) {
709 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
710 $path = apply_filters( 'ep_index_' . $type . '_request_path', $index . '/' . $type . '/_mget', $document_ids, $type );
711 } else {
712 $path = apply_filters( 'ep_index_' . $type . '_request_path', $index . '/_mget', $document_ids, $type );
713 }
714
715 $request_args = [
716 'method' => 'POST',
717 'body' => wp_json_encode(
718 array(
719 'ids' => $document_ids,
720 )
721 ),
722 ];
723
724 $request = $this->remote_request( $path, $request_args, [], 'post' );
725
726 if ( is_wp_error( $request ) ) {
727 return false;
728 }
729
730 $response_body = wp_remote_retrieve_body( $request );
731
732 $response = json_decode( $response_body, true );
733
734 $docs = [];
735
736 if ( isset( $response['docs'] ) && is_array( $response['docs'] ) ) {
737 foreach ( $response['docs'] as $doc ) {
738 if ( ! empty( $doc['exists'] ) || ! empty( $doc['found'] ) ) {
739 $docs[ $doc['_id'] ] = $doc['_source'];
740 }
741 }
742 }
743
744 /**
745 * Filter documents found by Elasticsearch through the /_mget endpoint.
746 *
747 * @hook ep_get_documents
748 * @since 3.6.0
749 * @param {array} $docs Documents found indexed by ID
750 * @param {string} $index Index name
751 * @param {string} $type Index type
752 * @param {array} $document_ids Array of document ids
753 * @return {array} Documents to be returned
754 */
755 $docs = apply_filters( 'ep_get_documents', $docs, $index, $type, $document_ids );
756
757 return $docs;
758 }
759
760 /**
761 * Create the network alias.
762 *
763 * Network aliases are used to query documents across blogs in a network.
764 *
765 * @param array $indexes Indexes to group under alias.
766 * @param string $network_alias Name of network alias.
767 * @since 3.0
768 * @return boolean
769 */
770 public function create_network_alias( $indexes, $network_alias ) {
771
772 $path = '_aliases';
773
774 $args = array(
775 'actions' => [],
776 );
777
778 foreach ( $indexes as $index ) {
779 if ( empty( $index ) ) {
780 continue;
781 }
782
783 $args['actions'][] = array(
784 'add' => array(
785 'index' => $index,
786 'alias' => $network_alias,
787 ),
788 );
789 }
790
791 $request_args = array(
792 'body' => wp_json_encode( $args ),
793 'method' => 'POST',
794 'timeout' => 25,
795 );
796
797 $request = $this->remote_request( $path, $request_args, [], 'create_network_alias' );
798
799 if ( ! is_wp_error( $request ) && ( 200 >= wp_remote_retrieve_response_code( $request ) && 300 > wp_remote_retrieve_response_code( $request ) ) ) {
800 return true;
801 }
802
803 return false;
804 }
805
806 /**
807 * Put a mapping into Elasticsearch
808 *
809 * @param string $index Index name.
810 * @param array $mapping Mapping array.
811 * @since 3.0
812 * @return boolean
813 */
814 public function put_mapping( $index, $mapping ) {
815 /**
816 * Filter Elasticsearch mapping before put mapping
817 *
818 * @hook ep_config_mapping
819 * @param {array} $mapping Elasticsearch mapping
820 * @param {string} $index Index name
821 * @return {array} New mapping
822 */
823 $mapping = apply_filters( 'ep_config_mapping', $mapping, $index );
824
825 $request_args = [
826 'body' => wp_json_encode( $mapping ),
827 'method' => 'PUT',
828 'timeout' => 30,
829 ];
830
831 $request = $this->remote_request( $index, $request_args, [], 'put_mapping' );
832
833 /**
834 * Filter Elasticsearch put mapping response
835 *
836 * @hook ep_config_mapping_request
837 * @param {array} $request Elasticsearch response
838 * @param {string} $index Elasticsearch index name
839 * @param {array} $mapping Mapping sent to Elasticsearch
840 * @return {array} New response
841 */
842 $request = apply_filters( 'ep_config_mapping_request', $request, $index, $mapping );
843
844 $response_body = wp_remote_retrieve_body( $request );
845
846 if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) {
847 $response_body = wp_remote_retrieve_body( $request );
848
849 return true;
850 }
851
852 return false;
853 }
854
855 /**
856 * Get current index mapping from Elasticsearch.
857 *
858 * @param string $index The index name.
859 * @since 3.5
860 * @return array
861 */
862 public function get_mapping( $index ) {
863 $request_args = [
864 'method' => 'GET',
865 'timeout' => 30,
866 ];
867
868 $request = $this->remote_request( $index, $request_args, [], 'get_mapping' );
869
870 if ( is_wp_error( $request ) || 200 !== wp_remote_retrieve_response_code( $request ) ) {
871 return [];
872 }
873
874 $body = wp_remote_retrieve_body( $request );
875
876 if ( ! $body ) {
877 return [];
878 }
879
880 $mapping = json_decode( $body, true );
881
882 return is_array( $mapping ) ? $mapping : [];
883 }
884
885 /**
886 * Close an open index.
887 *
888 * @param string $index Index name.
889 * @since 3.5
890 * @return boolean
891 */
892 public function close_index( $index ) {
893 $request_args = [
894 'method' => 'POST',
895 'timeout' => 30,
896 ];
897
898 $close = trailingslashit( $index ) . '_close';
899 $request = $this->remote_request( $close, $request_args, [], 'close_index' );
900
901 return ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) );
902 }
903
904 /**
905 * Open a closed index.
906 *
907 * @param string $index Index name.
908 * @since 3.5
909 * @return boolean
910 */
911 public function open_index( $index ) {
912 $request_args = [
913 'method' => 'POST',
914 'timeout' => 30,
915 ];
916
917 $open = trailingslashit( $index ) . '_open';
918 $request = $this->remote_request( $open, $request_args, [], 'open_index' );
919
920 return ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) );
921 }
922
923 /**
924 * Update index settings.
925 *
926 * @param string $index Index name.
927 * @param array $settings Setting update array.
928 * @param boolean $close_first Optional. True if index must be closed prior to update.
929 * Dynamic settings can be updated on open indices. Static
930 * settings must be closed. Default false.
931 * @since 3.5
932 * @return boolean
933 */
934 public function update_index_settings( $index, $settings, $close_first = false ) {
935 $request_args = [
936 'body' => wp_json_encode( $settings ),
937 'method' => 'PUT',
938 'timeout' => 30,
939 ];
940
941 if ( $close_first ) {
942 $this->close_index( $index );
943 }
944
945 $settings = trailingslashit( $index ) . '_settings';
946 $request = $this->remote_request( $settings, $request_args, [], 'update_index_settings' );
947
948 $updated = ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) );
949
950 if ( $close_first ) {
951 $opened = $this->open_index( $index );
952 return ( $updated && $opened );
953 }
954
955 return $updated;
956 }
957
958 /**
959 * Delete an Elasticsearch index
960 *
961 * @param string $index Index name.
962 * @since 3.0
963 * @return boolean
964 */
965 public function delete_index( $index ) {
966
967 $request_args = [
968 'method' => 'DELETE',
969 'timeout' => 30,
970 ];
971
972 $request = $this->remote_request( $index, $request_args, [], 'delete_index' );
973
974 // 200 means the delete was successful
975 // 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
976 if ( ! is_wp_error( $request ) && ( 200 === wp_remote_retrieve_response_code( $request ) || 404 === wp_remote_retrieve_response_code( $request ) ) ) {
977 $response_body = wp_remote_retrieve_body( $request );
978
979 return json_decode( $response_body );
980 }
981
982 return false;
983 }
984
985 /**
986 * Delete all indices
987 *
988 * @since 3.0
989 * @return boolean
990 */
991 public function delete_all_indices() {
992 return $this->delete_index( '*' );
993 }
994
995 /**
996 * Check if an ES index exists
997 *
998 * @param string $index Index name.
999 * @since 3.0
1000 * @return boolean
1001 */
1002 public function index_exists( $index ) {
1003
1004 $request_args = [
1005 'method' => 'HEAD',
1006 ];
1007
1008 $request = $this->remote_request( $index, $request_args, [], 'index_exists' );
1009
1010 // 200 means the index exists.
1011 // 404 means the index was non-existent.
1012 if ( ! is_wp_error( $request ) && ( 200 === wp_remote_retrieve_response_code( $request ) || 404 === wp_remote_retrieve_response_code( $request ) ) ) {
1013
1014 if ( 404 === wp_remote_retrieve_response_code( $request ) ) {
1015 return false;
1016 }
1017
1018 if ( 200 === wp_remote_retrieve_response_code( $request ) ) {
1019 return true;
1020 }
1021 }
1022
1023 return false;
1024 }
1025
1026 /**
1027 * Bulk index Elasticsearch documents
1028 *
1029 * @param string $index Index name.
1030 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
1031 * @param string $body Encoded JSON.
1032 * @since 3.0
1033 * @return WP_Error|array
1034 */
1035 public function bulk_index( $index, $type, $body ) {
1036 /**
1037 * Filter Elasticsearch bulk index request path
1038 *
1039 * @hook ep_bulk_index_request_path
1040 * @param {string} Request path
1041 * @param {string} $body Bulk index request body
1042 * @param {string} $type Index type
1043 * @return {string} New path
1044 */
1045 if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
1046 $path = apply_filters( 'ep_bulk_index_request_path', $index . '/' . $type . '/_bulk', $body, $type );
1047 } else {
1048 $path = apply_filters( 'ep_bulk_index_request_path', $index . '/_bulk', $body, $type );
1049 }
1050
1051 $request_args = array(
1052 'method' => 'POST',
1053 'body' => $body,
1054 'timeout' => apply_filters( 'ep_bulk_index_timeout', 30 ),
1055 );
1056
1057 $request = $this->remote_request( $path, $request_args, [], 'bulk_index' );
1058
1059 if ( is_wp_error( $request ) ) {
1060 return $request;
1061 }
1062
1063 $response = wp_remote_retrieve_response_code( $request );
1064
1065 if ( 200 !== $response ) {
1066 return new WP_Error( $response, wp_remote_retrieve_response_message( $request ), $request );
1067 }
1068
1069 return json_decode( wp_remote_retrieve_body( $request ), true );
1070 }
1071
1072 /**
1073 * Return queries for debugging
1074 *
1075 * @since 1.8
1076 * @return array
1077 */
1078 public function get_query_log() {
1079 return $this->queries;
1080 }
1081
1082 /**
1083 * Wrapper for wp_remote_request
1084 *
1085 * This is a wrapper function for wp_remote_request to account for request failures.
1086 *
1087 * @since 1.6
1088 *
1089 * @param string $path Site URL to retrieve.
1090 * @param array $args Optional. Request arguments. Default empty array.
1091 * @param array $query_args Optional. The query args originally passed to WP_Query.
1092 * @param string $type Type of request, used for debugging.
1093 *
1094 * @return WP_Error|array The response or WP_Error on failure.
1095 */
1096 public function remote_request( $path, $args = [], $query_args = [], $type = null ) {
1097
1098 if ( empty( $args['method'] ) ) {
1099 $args['method'] = 'GET';
1100 }
1101
1102 // Checks for any previously set headers
1103 $existing_headers = isset( $args['headers'] ) ? (array) $args['headers'] : [];
1104
1105 // Add the API Header.
1106 // Note that the "User Agent" header will be changed via WordPress's `http_headers_useragent` filter later.
1107 $new_headers = $this->format_request_headers();
1108
1109 $args['headers'] = array_merge( $existing_headers, $new_headers );
1110
1111 /**
1112 * Filter Elasticsearch args prior to remote request
1113 *
1114 * @hook ep_pre_request_args
1115 * @since 3.6.4
1116 * @param {array} $args Request args
1117 * @param {string} $path Site URL to retrieve
1118 * @param {array} $query_args The query args originally passed to WP_Query.
1119 * @param {string|null} $type Type of request, used for debugging.
1120 * @return {array} New request args
1121 */
1122 $args = apply_filters( 'ep_pre_request_args', $args, $path, $query_args, $type );
1123
1124 $query = array(
1125 'time_start' => microtime( true ),
1126 'time_finish' => false,
1127 'args' => $args,
1128 'blocking' => true,
1129 'failed_hosts' => [],
1130 'request' => false,
1131 'host' => Utils\get_host(),
1132 'query_args' => $query_args,
1133 );
1134
1135 $request = false;
1136 $failures = 0;
1137
1138 add_filter( 'http_headers_useragent', [ $this, 'add_elasticpress_version_to_user_agent' ] );
1139
1140 // Optionally let us try back up hosts and account for failures.
1141 while ( true ) {
1142 /**
1143 * Filter Elasticsearch host prior to remote request
1144 *
1145 * @hook ep_pre_request_host
1146 * @param {string} Request host
1147 * @param {int} $failures Number of current failures
1148 * @param {string} $path Request path
1149 * @param {array} $args Request arguments
1150 * @return {string} New host
1151 */
1152 $query['host'] = apply_filters( 'ep_pre_request_host', $query['host'], $failures, $path, $args );
1153
1154 /**
1155 * Filter Elasticsearch url prior to remote request
1156 *
1157 * @hook ep_pre_request_url
1158 * @param {string} Request url
1159 * @param {int} $failures Number of current failures
1160 * @param {string} $host Request host
1161 * @param {string} $path Request path
1162 * @param {array} $args Request arguments
1163 * @return {string} New url
1164 */
1165 $query['url'] = apply_filters( 'ep_pre_request_url', esc_url( trailingslashit( $query['host'] ) . $path ), $failures, $query['host'], $path, $args );
1166
1167 /**
1168 * Filter whether remote request should be intercepted
1169 *
1170 * @hook ep_intercept_remote_request
1171 * @param {boolean} $intercept True to intercept
1172 * @return {boolean} New value
1173 */
1174 if ( true === apply_filters( 'ep_intercept_remote_request', false ) ) {
1175 /**
1176 * Filter intercepted request
1177 *
1178 * @hook ep_do_intercept_request
1179 * @since 3.2.2
1180 * @since 3.6.5 added $type
1181 * @param {array} $request New remote request response
1182 * @param {array} $query Remote request arguments
1183 * @param {args} $args Request arguments
1184 * @param {int} $failures Number of failures
1185 * @param {string} $type Type of request
1186 * @return {array} New request
1187 */
1188 $request = apply_filters( 'ep_do_intercept_request', new WP_Error( 400, 'No Request defined' ), $query, $args, $failures, $type );
1189 } else {
1190 $request = wp_remote_request( $query['url'], $args ); // try the existing host to avoid unnecessary calls.
1191 }
1192
1193 $request_response_code = (int) wp_remote_retrieve_response_code( $request );
1194
1195 $is_valid_res = ( $request_response_code >= 200 && $request_response_code <= 299 );
1196 $is_non_blocking_request = ( 0 === $request_response_code );
1197
1198 if ( false === $request || is_wp_error( $request ) || ( ! $is_valid_res && ! $is_non_blocking_request ) ) {
1199 $failures++;
1200
1201 /**
1202 * Filter max number of times to attempt remote requests
1203 *
1204 * @hook ep_max_remote_request_tries
1205 * @param {int} $tries Number of times to try
1206 * @param {path} $path Request path
1207 * @param {args} $args Request arguments
1208 * @return {int} New number of tries
1209 */
1210 if ( $failures >= apply_filters( 'ep_max_remote_request_tries', 1, $path, $args ) ) {
1211 break;
1212 }
1213 } else {
1214 break;
1215 }
1216 }
1217
1218 remove_filter( 'http_headers_useragent', [ $this, 'add_elasticpress_version_to_user_agent' ] );
1219
1220 // Return now if we're not blocking, since we won't have a response yet.
1221 if ( isset( $args['blocking'] ) && false === $args['blocking'] ) {
1222 $query['blocking'] = true;
1223 $query['request'] = $request;
1224 $this->add_query_log( $query );
1225
1226 return $request;
1227 }
1228
1229 $query['time_finish'] = microtime( true );
1230 $query['request'] = $request;
1231 $this->add_query_log( $query );
1232
1233 /**
1234 * Fires after Elasticsearch remote request
1235 *
1236 * @hook ep_remote_request
1237 * @param {array} $query Remote request arguments
1238 * @param {string} $type Request type
1239 */
1240 do_action( 'ep_remote_request', $query, $type );
1241
1242 return $request;
1243
1244 }
1245
1246 /**
1247 * Parse response from Elasticsearch
1248 *
1249 * Determines if there is an issue or if the response is valid.
1250 *
1251 * @since 1.9
1252 * @param object $response JSON decoded response from Elasticsearch.
1253 * @return array Contains the status message or the returned statistics.
1254 */
1255 public function parse_api_response( $response ) {
1256
1257 if ( null === $response ) {
1258
1259 return array(
1260 'status' => false,
1261 'msg' => esc_html__( 'Invalid response from ElasticPress server. Please contact your administrator.' ),
1262 );
1263
1264 } elseif (
1265 isset( $response->error ) &&
1266 (
1267 ( is_string( $response->error ) && stristr( $response->error, 'IndexMissingException' ) ) ||
1268 ( isset( $response->error->reason ) && stristr( $response->error->reason, 'no such index' ) )
1269 )
1270 ) {
1271
1272 if ( is_multisite() ) {
1273
1274 $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' );
1275
1276 } else {
1277
1278 $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' );
1279
1280 }
1281
1282 return array(
1283 'status' => false,
1284 'msg' => $error,
1285 );
1286
1287 }
1288
1289 return array(
1290 'status' => true,
1291 'data' => $response->_all->primaries->indexing,
1292 );
1293
1294 }
1295
1296 /**
1297 * Set ES plugins and version, detect server type, and cache everything
1298 *
1299 * @since 4.2.1
1300 * @param bool $force Bust cache or not.
1301 * @return array
1302 */
1303 public function set_elasticsearch_info( $force = false ) {
1304 if ( empty( Utils\get_host() ) ) {
1305 return;
1306 }
1307
1308 if ( ! $force && null !== $this->elasticsearch_version && null !== $this->elasticsearch_plugins ) {
1309 return;
1310 }
1311
1312 // Get ES info from cache if available. If we are forcing, then skip cache check.
1313 if ( ! $force ) {
1314 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
1315 $es_info = get_site_transient( 'ep_es_info' );
1316 } else {
1317 $es_info = get_transient( 'ep_es_info' );
1318 }
1319 if ( ! empty( $es_info ) ) {
1320 $this->elasticsearch_version = $es_info['version'];
1321 $this->elasticsearch_plugins = $es_info['plugins'];
1322 $this->server_type = $es_info['server_type'];
1323 return;
1324 }
1325 }
1326
1327 $path = '_nodes/plugins';
1328
1329 $request = $this->remote_request( $path, array( 'method' => 'GET' ) );
1330
1331 if ( is_wp_error( $request ) || 200 !== wp_remote_retrieve_response_code( $request ) ) {
1332 $this->elasticsearch_version = false;
1333 $this->elasticsearch_plugins = false;
1334
1335 /**
1336 * Try a different endpoint in case the plugins url is restricted
1337 *
1338 * @since 2.2.1
1339 */
1340
1341 $request = $this->remote_request( '', array( 'method' => 'GET' ) );
1342
1343 if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) {
1344 $response_body = wp_remote_retrieve_body( $request );
1345 $response = json_decode( $response_body, true );
1346
1347 try {
1348 $this->elasticsearch_version = $response['version']['number'];
1349 if ( ! empty( $response['version']['distribution'] ) ) {
1350 $this->server_type = $response['version']['distribution'];
1351 }
1352 } catch ( \Exception $e ) {
1353 // Do nothing.
1354 }
1355 }
1356 return;
1357 }
1358
1359 $response = json_decode( wp_remote_retrieve_body( $request ), true );
1360
1361 $this->elasticsearch_plugins = [];
1362 $this->elasticsearch_version = false;
1363
1364 if ( isset( $response['nodes'] ) ) {
1365 $node = end( $response['nodes'] );
1366 // Save version of last node. We assume all nodes are same version.
1367 $this->elasticsearch_version = $node['version'];
1368
1369 if ( isset( $node['plugins'] ) && is_array( $node['plugins'] ) ) {
1370 foreach ( $node['plugins'] as $plugin ) {
1371 $this->elasticsearch_plugins[ $plugin['name'] ] = $plugin['version'];
1372 }
1373 }
1374 if ( isset( $node['modules'] )
1375 && is_array( $node['modules'] )
1376 && ! empty( $node['modules'] )
1377 && ! empty( $node['modules'][0]['opensearch_version'] )
1378 ) {
1379 $this->server_type = 'opensearch';
1380 }
1381 }
1382
1383 /**
1384 * Cache ES info
1385 *
1386 * @since 2.3.1
1387 */
1388 $this->cache_elasticsearch_info();
1389 }
1390
1391 /**
1392 * Return ES plugins, version and type.
1393 *
1394 * This function also sets those values in the object instance, getting it from cache
1395 * or not, according to `$force` value.
1396 *
1397 * @param bool $force Bust cache or not.
1398 * @since 2.2
1399 * @return array
1400 */
1401 public function get_elasticsearch_info( $force = false ) {
1402 $this->set_elasticsearch_info( $force );
1403 return [
1404 'plugins' => $this->elasticsearch_plugins,
1405 'version' => $this->elasticsearch_version,
1406 'server_type' => $this->server_type,
1407 ];
1408 }
1409
1410 /**
1411 * Cache the ES info.
1412 *
1413 * @since 4.2.1
1414 */
1415 protected function cache_elasticsearch_info() {
1416 /**
1417 * Filter elasticsearch info cache expiration
1418 *
1419 * @hook ep_es_info_cache_expiration
1420 * @param {int} $time Cache time in seconds
1421 * @return {int} New cache time
1422 */
1423 if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
1424 set_site_transient(
1425 'ep_es_info',
1426 array(
1427 'version' => $this->elasticsearch_version,
1428 'plugins' => $this->elasticsearch_plugins,
1429 'server_type' => $this->server_type,
1430 ),
1431 apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) )
1432 );
1433 } else {
1434 set_transient(
1435 'ep_es_info',
1436 array(
1437 'version' => $this->elasticsearch_version,
1438 'plugins' => $this->elasticsearch_plugins,
1439 'server_type' => $this->server_type,
1440 ),
1441 apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) )
1442 );
1443 }
1444 }
1445
1446 /**
1447 * Get cluster status
1448 *
1449 * Retrieves cluster stats from Elasticsearch.
1450 *
1451 * @since 1.9
1452 * @return array Contains the status message or the returned statistics.
1453 */
1454 public function get_cluster_status() {
1455
1456 if ( is_wp_error( Utils\get_host() ) ) {
1457
1458 return array(
1459 'status' => false,
1460 'msg' => esc_html__( 'Elasticsearch Host is not available.', 'elasticpress' ),
1461 );
1462
1463 } else {
1464
1465 $request = $this->remote_request( '_cluster/stats', array( 'method' => 'GET' ) );
1466
1467 if ( ! is_wp_error( $request ) ) {
1468
1469 $response = json_decode( wp_remote_retrieve_body( $request ) );
1470
1471 return $response;
1472
1473 }
1474
1475 return array(
1476 'status' => false,
1477 'msg' => $request->get_error_message(),
1478 );
1479
1480 }
1481 }
1482
1483 /**
1484 * Get an Elasticsearch pipeline
1485 *
1486 * @param string $id Id of pipeline.
1487 * @since 2.3
1488 * @return WP_Error|bool|array
1489 */
1490 public function get_pipeline( $id ) {
1491 $path = '_ingest/pipeline/' . $id;
1492
1493 $request_args = array(
1494 'method' => 'GET',
1495 );
1496
1497 /**
1498 * Filter get pipeline request arguments
1499 *
1500 * @hook ep_get_pipeline_args
1501 * @param {array} $request_args Request arguments
1502 * @return {array} New arguments
1503 */
1504 $request = $this->remote_request( $path, apply_filters( 'ep_get_pipeline_args', $request_args ), [], 'get_pipeline' );
1505
1506 if ( is_wp_error( $request ) ) {
1507 return $request;
1508 }
1509
1510 $response = wp_remote_retrieve_response_code( $request );
1511
1512 if ( 200 !== $response ) {
1513 return new WP_Error( $response, wp_remote_retrieve_response_message( $request ), $request );
1514 }
1515
1516 $body = json_decode( wp_remote_retrieve_body( $request ), true );
1517
1518 if ( empty( $body ) ) {
1519 return false;
1520 }
1521
1522 return $body;
1523 }
1524
1525 /**
1526 * Put an Elasticsearch pipeline
1527 *
1528 * @param string $id Pipeline id.
1529 * @param array $args Args to send to ES.
1530 * @since 2.3
1531 * @return WP_Error|bool
1532 */
1533 public function create_pipeline( $id, $args ) {
1534 $path = '_ingest/pipeline/' . $id;
1535
1536 $request_args = array(
1537 'body' => wp_json_encode( $args ),
1538 'method' => 'PUT',
1539 );
1540
1541 /**
1542 * Filter create pipeline request arguments
1543 *
1544 * @hook ep_create_pipeline_args
1545 * @param {array} $request_args Request arguments
1546 * @return {array} New arguments
1547 */
1548 $request = $this->remote_request( $path, apply_filters( 'ep_create_pipeline_args', $request_args ), [], 'create_pipeline' );
1549
1550 if ( is_wp_error( $request ) ) {
1551 return $request;
1552 }
1553
1554 $response = wp_remote_retrieve_response_code( $request );
1555
1556 if ( 200 > $response || 300 <= $response ) {
1557 return new WP_Error( $response, wp_remote_retrieve_response_message( $request ), $request );
1558 }
1559
1560 $body = json_decode( wp_remote_retrieve_body( $request ), true );
1561
1562 if ( empty( $body ) ) {
1563 return false;
1564 }
1565
1566 return true;
1567 }
1568
1569 /**
1570 * Conditionally add the ElasticPress version to the User Agent string.
1571 *
1572 * @since 3.6.1
1573 * @param string $user_agent Original User Agent.
1574 * @return string
1575 */
1576 public function add_elasticpress_version_to_user_agent( $user_agent ) {
1577 /**
1578 * Filter the User Agent header when submitting requests to Elasticsearch.
1579 *
1580 * @hook ep_remote_request_add_ep_user_agent
1581 * @param {bool} $should_add_ep_verion Whether the ElasticPress version should be added to the User Agent string.
1582 * @return {bool} New value
1583 * @since 3.6.1
1584 */
1585 if ( apply_filters( 'ep_remote_request_add_ep_user_agent', Utils\is_epio() ) ) {
1586 $end_part = '; ' . get_bloginfo( 'url' );
1587 $user_agent = str_replace(
1588 $end_part,
1589 ' (ElasticPress/' . EP_VERSION . ')' . $end_part,
1590 $user_agent
1591 );
1592 }
1593 return $user_agent;
1594 }
1595
1596 /**
1597 * Query logging. Don't log anything to the queries property when
1598 * WP_DEBUG is not enabled. Calls action 'ep_add_query_log' if you
1599 * want to access the query outside of the ElasticPress plugin. This
1600 * runs regardless of debufg settings.
1601 *
1602 * @param array $query Query to log.
1603 */
1604 protected function add_query_log( $query ) {
1605 if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || ( defined( 'WP_EP_DEBUG' ) && WP_EP_DEBUG ) ) {
1606 $this->queries[] = $query;
1607 }
1608
1609 /**
1610 * Fires after item is added to the query log
1611 *
1612 * @hook ep_add_query_log
1613 * @param {array} $query Query to log
1614 */
1615 do_action( 'ep_add_query_log', $query );
1616 }
1617
1618 }
1619