PluginProbe
ElasticPress / 5.3.5
ElasticPress v5.3.5
5.3.5 5.3.4 3.6.5 3.6.6 4.0.0 4.0.1 4.1.0 4.2.0 4.2.1 4.2.2 4.3.0 4.3.1 4.4.0 4.4.1 4.5.0 4.5.1 4.5.2 4.6.0 4.6.1 4.7.0 4.7.1 4.7.2 5.0.0 5.0.1 5.0.2 All 108 releases
← All changes | includes/classes/Elasticsearch.php +408 -135 4.2.05.3.5 View file →
@@ -7,10 +7,11 @@
7 7 */
8 8
9 9 namespace ElasticPress;
10 10
11 -use ElasticPress\Utils as Utils;
12 -use \WP_Error as WP_Error;
11 +use WP_Error;
12 +use ElasticPress\Indexables;
13 +use ElasticPress\Utils;
13 14
14 15 if ( ! defined( 'ABSPATH' ) ) {
15 16 exit; // Exit if accessed directly.
16 17 }
@@ -44,8 +45,15 @@
44 45 */
45 46 public $elasticsearch_version = null;
46 47
47 48 /**
49 + * Server type (elasticsearch, opensearch, etc.)
50 + *
51 + * @var string
52 + */
53 + public $server_type = 'elasticsearch';
54 +
55 + /**
48 56 * Return singleton instance of class
49 57 *
50 58 * @return object
51 59 * @since 0.1.0
@@ -69,9 +77,9 @@
69 77 * @param string $type Index type. Previously this was used for index type. Now it's just passed to hooks for legacy reasons.
70 78 * @param array $document Formatted Elasticsearch document.
71 79 * @param boolean $blocking Blocking HTTP request or not.
72 80 * @since 3.0
73 - * @return boolean|array
81 + * @return boolean|object
74 82 */
75 83 public function index_document( $index, $type, $document, $blocking = true ) {
76 84 /**
77 85 * Filter Elasticsearch index document request path
@@ -83,9 +91,9 @@
83 91 * @param {string} $type Type of document
84 92 * @return {string} New path
85 93 * @since 3.0
86 94 */
87 - if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
95 + if ( version_compare( (string) $this->get_elasticsearch_version(), '7.0', '<' ) ) {
88 96 $path = apply_filters( 'ep_index_' . $type . '_request_path', $index . '/' . $type . '/' . $document['ID'], $document, $type );
89 97 } else {
90 98 $path = apply_filters( 'ep_index_' . $type . '_request_path', $index . '/_doc/' . $document['ID'], $document, $type );
91 99 }
@@ -220,8 +228,30 @@
220 228 return apply_filters( 'ep_elasticsearch_version', $info['version'] );
221 229 }
222 230
223 231 /**
232 + * Get server type. We cache this so we don't have to do it every time.
233 + *
234 + * @param bool $force Bust cache or not.
235 + * @since 4.2.1
236 + * @return string|bool
237 + */
238 + public function get_server_type( $force = false ) {
239 +
240 + $info = $this->get_elasticsearch_info( $force );
241 +
242 + /**
243 + * Filter server type
244 + *
245 + * @hook ep_server_type
246 + * @param {string} $type Type (elasticsearch, opensearch, others)
247 + * @return {string} New type
248 + * @since 4.2.1
249 + */
250 + return apply_filters( 'ep_server_type', $info['server_type'] );
251 + }
252 +
253 + /**
224 254 * Get Elasticsearch plugins. We cache this so we don't have to do it every time.
225 255 *
226 256 * @param bool $force Force cache refresh or not.
227 257 * @since 2.2
@@ -253,9 +283,9 @@
253 283 * @since 3.0
254 284 * @return bool|array
255 285 */
256 286 public function query( $index, $type, $query, $query_args, $query_object = null ) {
257 - if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
287 + if ( version_compare( (string) $this->get_elasticsearch_version(), '7.0', '<' ) ) {
258 288 $path = $index . '/' . $type . '/_search';
259 289 } else {
260 290 $path = $index . '/_search';
261 291 }
@@ -373,8 +403,16 @@
373 403 * @param {string} $scope Backwards compat for scope parameter.
374 404 * @param {array} $query_args Current WP Query arguments
375 405 */
376 406 do_action( 'ep_retrieve_aggregations', $response['aggregations'], $query, '', $query_args );
407 +
408 + if ( is_object( $query_object ) ) {
409 + if ( method_exists( $query_object, 'set' ) ) {
410 + $query_object->set( 'ep_aggregations', $response['aggregations'] );
411 + } else {
412 + $query_object->query_vars['ep_aggregations'] = $response['aggregations'];
413 + }
414 + }
377 415 }
378 416
379 417 /**
380 418 * Fires after valid Elasticsearch query
@@ -401,9 +439,9 @@
401 439
402 440 $documents = [];
403 441
404 442 foreach ( $hits as $hit ) {
405 - $document = $hit['_source'];
443 + $document = isset( $hit['_source'] ) ? $hit['_source'] : array();
406 444 $document['site_id'] = $this->parse_site_id( $hit['_index'] );
407 445
408 446 if ( ! empty( $hit['highlight'] ) ) {
409 447 $document['highlight'] = $hit['highlight'];
@@ -425,13 +463,17 @@
425 463 * Filter Elasticsearch query results
426 464 *
427 465 * @hook ep_es_query_results
428 466 * @param {array} $results Results from Elasticsearch
429 - * @param {response} $response Raw response from Elasticsearch
430 - * @param {array} $query Raw Elasticsearch query
431 - * @param {array} $query_args Query arguments
432 - * @param {mixed} $query_object Could be WP_Query, WP_User_Query, etc.
433 - * @return {array} New results
467 + * @param {int} $results.found_documents Total number of documents.
468 + * @param {array} $results.documents Array of documents.
469 + * @param {array} $results.aggregations Array of aggregations.
470 + * @param {array} $results.suggest Array of suggestions.
471 + * @param {response} $response Raw response from Elasticsearch
472 + * @param {array} $query Raw Elasticsearch query
473 + * @param {array} $query_args Query arguments
474 + * @param {mixed} $query_object Could be WP_Query, WP_User_Query, etc.
475 + * @return {array} New results
434 476 */
435 477 return apply_filters(
436 478 'ep_es_query_results',
437 479 [
@@ -436,8 +478,10 @@
436 478 'ep_es_query_results',
437 479 [
438 480 'found_documents' => $total_hits,
439 481 'documents' => $documents,
482 + 'aggregations' => $response['aggregations'] ?? [],
483 + 'suggest' => $response['suggest'] ?? [],
440 484 ],
441 485 $response,
442 486 $query,
443 487 $query_args,
@@ -539,9 +583,9 @@
539 583 * @since 3.0
540 584 * @return boolean
541 585 */
542 586 public function delete_document( $index, $type, $document_id, $blocking = true ) {
543 - if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
587 + if ( version_compare( (string) $this->get_elasticsearch_version(), '7.0', '<' ) ) {
544 588 $path = $index . '/' . $type . '/' . $document_id;
545 589 } else {
546 590 $path = $index . '/_doc/' . $document_id;
547 591 }
@@ -595,8 +639,13 @@
595 639 $headers['Authorization'] = 'Basic ' . base64_encode( $shield );
596 640 // phpcs:enable
597 641 }
598 642
643 + $request_id = Utils\generate_request_id();
644 + if ( ! empty( $request_id ) ) {
645 + $headers['X-ElasticPress-Request-ID'] = $request_id;
646 + }
647 +
599 648 /**
600 649 * Filter Elasticsearch request headers
601 650 *
602 651 * @hook ep_format_request_headers
@@ -617,9 +666,9 @@
617 666 * @since 3.0
618 667 * @return boolean|array
619 668 */
620 669 public function get_document( $index, $type, $document_id ) {
621 - if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
670 + if ( version_compare( (string) $this->get_elasticsearch_version(), '7.0', '<' ) ) {
622 671 $path = $index . '/' . $type . '/' . $document_id;
623 672 } else {
624 673 $path = $index . '/_doc/' . $document_id;
625 674 }
@@ -675,9 +724,9 @@
675 724 * @since 3.6.0
676 725 * @return boolean|array
677 726 */
678 727 public function get_documents( $index, $type, $document_ids ) {
679 - if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
728 + if ( version_compare( (string) $this->get_elasticsearch_version(), '7.0', '<' ) ) {
680 729 $path = apply_filters( 'ep_index_' . $type . '_request_path', $index . '/' . $type . '/_mget', $document_ids, $type );
681 730 } else {
682 731 $path = apply_filters( 'ep_index_' . $type . '_request_path', $index . '/_mget', $document_ids, $type );
683 732 }
@@ -777,12 +826,13 @@
777 826 * Put a mapping into Elasticsearch
778 827 *
779 828 * @param string $index Index name.
780 829 * @param array $mapping Mapping array.
830 + * @param string $return_type Desired return type. Can be either 'bool' or 'raw'
781 831 * @since 3.0
782 - * @return boolean
832 + * @return boolean|WP_Error
783 833 */
784 - public function put_mapping( $index, $mapping ) {
834 + public function put_mapping( $index, $mapping, $return_type = 'bool' ) {
785 835 /**
786 836 * Filter Elasticsearch mapping before put mapping
787 837 *
788 838 * @hook ep_config_mapping
@@ -810,17 +860,43 @@
810 860 * @return {array} New response
811 861 */
812 862 $request = apply_filters( 'ep_config_mapping_request', $request, $index, $mapping );
813 863
814 - $response_body = wp_remote_retrieve_body( $request );
864 + $response_code = wp_remote_retrieve_response_code( $request );
815 865
816 - if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) {
817 - $response_body = wp_remote_retrieve_body( $request );
866 + /**
867 + * Fires after sending a put mapping request
868 + *
869 + * @hook ep_after_put_mapping
870 + * @since 4.7.0
871 + * @param {string} $index Index name
872 + * @param {WP_Error|array} $request The response or WP_Error on failure.
873 + */
874 + do_action( 'ep_after_put_mapping', $index, $request );
818 875
819 - return true;
876 + // If WP_Error or not 200, return false or error message depends on attribute.
877 + if ( is_wp_error( $request ) || 200 !== $response_code ) {
878 + if ( 'bool' === $return_type ) {
879 + return false;
880 + }
881 +
882 + if ( is_wp_error( $request ) ) {
883 + return $request;
884 + }
885 +
886 + $response_body = wp_remote_retrieve_body( $request );
887 + $parsed_response = json_decode( $response_body, true );
888 + if ( is_array( $parsed_response ) ) {
889 + $status = $parsed_response['status'] ?? 'status-not-set';
890 + $error = $parsed_response['error'] ?? 'error-not-set';
891 + } else {
892 + $status = $response_code;
893 + $error = $response_body;
894 + }
895 + return new \WP_Error( $status, $error );
820 896 }
821 897
822 - return false;
898 + return true;
823 899 }
824 900
825 901 /**
826 902 * Get current index mapping from Elasticsearch.
@@ -890,8 +966,81 @@
890 966 return ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) );
891 967 }
892 968
893 969 /**
970 + * Get index settings
971 + *
972 + * @param string $index Index name
973 + * @param bool $force_refresh Whether to use or not a cached value. Default false, use cached.
974 + * @since 4.4.0, 4.7.0 added the $force_refresh parameter
975 + * @return array|WP_Error Raw ES response from the $index/_settings?flat_settings=true endpoint
976 + */
977 + public function get_index_settings( string $index, bool $force_refresh = false ) {
978 + $transient_key = "ep_index_settings_{$index}";
979 +
980 + if ( ! $force_refresh ) {
981 + $cache = Utils\get_transient( $transient_key );
982 + if ( false !== $cache ) {
983 + return $cache;
984 + }
985 + }
986 +
987 + $endpoint = trailingslashit( $index ) . '_settings?flat_settings=true';
988 + $request = $this->remote_request( $endpoint, [], [], 'get_index_settings' );
989 +
990 + if ( is_wp_error( $request ) ) {
991 + Utils\set_transient( $transient_key, $request, MINUTE_IN_SECONDS );
992 + return $request;
993 + }
994 +
995 + if ( wp_remote_retrieve_response_code( $request ) !== 200 ) {
996 + Utils\set_transient( $transient_key, $request, MINUTE_IN_SECONDS );
997 + return new \WP_Error(
998 + 'ep_get_index_settings_failed',
999 + esc_html__( 'Error while getting the index settings.', 'elasticpress' ),
1000 + $request
1001 + );
1002 + }
1003 +
1004 + $response_body = wp_remote_retrieve_body( $request );
1005 +
1006 + $settings = json_decode( $response_body, true );
1007 +
1008 + Utils\set_transient( $transient_key, $settings, DAY_IN_SECONDS );
1009 +
1010 + return $settings;
1011 + }
1012 +
1013 + /**
1014 + * Get a particular index setting
1015 + *
1016 + * @param string $index Index name
1017 + * @param string $setting Setting name
1018 + * @param bool $force_refresh Whether to use or not a cached value. Default false, use cached.
1019 + * @return mixed
1020 + */
1021 + public function get_index_setting( string $index, string $setting, bool $force_refresh = false ) {
1022 + $settings = $this->get_index_settings( $index, $force_refresh );
1023 +
1024 + if ( is_wp_error( $settings ) || empty( $settings[ $index ]['settings'][ $setting ] ) ) {
1025 + return null;
1026 + }
1027 +
1028 + return $settings[ $index ]['settings'][ $setting ];
1029 + }
1030 +
1031 + /**
1032 + * Given an index return its total fields limit
1033 + *
1034 + * @since 4.4.0, 4.7.0 wrapper of get_index_setting()
1035 + * @param string $index_name The index name
1036 + * @return int|null
1037 + */
1038 + public function get_index_total_fields_limit( $index_name ) {
1039 + return $this->get_index_setting( $index_name, 'index.mapping.total_fields.limit' );
1040 + }
1041 +
1042 + /**
894 1043 * Update index settings.
895 1044 *
896 1045 * @param string $index Index name.
897 1046 * @param array $settings Setting update array.
@@ -907,23 +1056,28 @@
907 1056 'method' => 'PUT',
908 1057 'timeout' => 30,
909 1058 ];
910 1059
911 - $closed = false;
912 1060 if ( $close_first ) {
913 - $closed = $this->close_index( $index );
1061 + $this->close_index( $index );
914 1062 }
915 1063
916 - if ( ! $close_first || $closed ) {
917 - $settings = trailingslashit( $index ) . '_settings';
918 - $request = $this->remote_request( $settings, $request_args, [], 'update_index_settings' );
919 - } else {
920 - return false;
921 - }
1064 + $settings_url = trailingslashit( $index ) . '_settings';
1065 + $request = $this->remote_request( $settings_url, $request_args, [], 'update_index_settings' );
922 1066
923 1067 $updated = ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) );
924 1068
925 - if ( $closed ) {
1069 + /**
1070 + * Fires after updating an index settings
1071 + *
1072 + * @hook ep_update_index_settings
1073 + * @since 4.4.0
1074 + * @param {string} $index Index name
1075 + * @param {array} $settings Setting update array
1076 + */
1077 + do_action( 'ep_update_index_settings', $index, $settings );
1078 +
1079 + if ( $close_first ) {
926 1080 $opened = $this->open_index( $index );
927 1081 return ( $updated && $opened );
928 1082 }
929 1083
@@ -1016,9 +1170,9 @@
1016 1170 * @param {string} $body Bulk index request body
1017 1171 * @param {string} $type Index type
1018 1172 * @return {string} New path
1019 1173 */
1020 - if ( version_compare( $this->get_elasticsearch_version(), '7.0', '<' ) ) {
1174 + if ( version_compare( (string) $this->get_elasticsearch_version(), '7.0', '<' ) ) {
1021 1175 $path = apply_filters( 'ep_bulk_index_request_path', $index . '/' . $type . '/_bulk', $body, $type );
1022 1176 } else {
1023 1177 $path = apply_filters( 'ep_bulk_index_request_path', $index . '/_bulk', $body, $type );
1024 1178 }
@@ -1050,9 +1204,17 @@
1050 1204 * @since 1.8
1051 1205 * @return array
1052 1206 */
1053 1207 public function get_query_log() {
1054 - return $this->queries;
1208 + /**
1209 + * Filter the query log
1210 + *
1211 + * @hook ep_get_query_log
1212 + * @since 5.3.0
1213 + * @param {array} $queries The query log
1214 + * @return {array} The query log
1215 + */
1216 + return apply_filters( 'ep_get_query_log', $this->queries );
1055 1217 }
1056 1218
1057 1219 /**
1058 1220 * Wrapper for wp_remote_request
@@ -1067,9 +1229,9 @@
1067 1229 * @param string $type Type of request, used for debugging.
1068 1230 *
1069 1231 * @return WP_Error|array The response or WP_Error on failure.
1070 1232 */
1071 - public function remote_request( $path, $args = [], $query_args = [], $type = null ) {
1233 + public function remote_request( $path, $args = [], $query_args = [], $type = '' ) {
1072 1234
1073 1235 if ( empty( $args['method'] ) ) {
1074 1236 $args['method'] = 'GET';
1075 1237 }
@@ -1145,9 +1307,9 @@
1145 1307 * @hook ep_intercept_remote_request
1146 1308 * @param {boolean} $intercept True to intercept
1147 1309 * @return {boolean} New value
1148 1310 */
1149 - if ( true === apply_filters( 'ep_intercept_remote_request', false ) ) {
1311 + if ( true === apply_filters( 'ep_intercept_remote_request', false ) || ! empty( $query_args['ep_intercept_request'] ) ) {
1150 1312 /**
1151 1313 * Filter intercepted request
1152 1314 *
1153 1315 * @hook ep_do_intercept_request
@@ -1170,9 +1332,9 @@
1170 1332 $is_valid_res = ( $request_response_code >= 200 && $request_response_code <= 299 );
1171 1333 $is_non_blocking_request = ( 0 === $request_response_code );
1172 1334
1173 1335 if ( false === $request || is_wp_error( $request ) || ( ! $is_valid_res && ! $is_non_blocking_request ) ) {
1174 - $failures++;
1336 + ++$failures;
1175 1337
1176 1338 /**
1177 1339 * Filter max number of times to attempt remote requests
1178 1340 *
@@ -1197,8 +1359,17 @@
1197 1359 $query['blocking'] = true;
1198 1360 $query['request'] = $request;
1199 1361 $this->add_query_log( $query );
1200 1362
1363 + /**
1364 + * Fires after Elasticsearch remote request
1365 + *
1366 + * @hook ep_remote_request
1367 + * @param {array} $query Remote request arguments
1368 + * @param {string} $type Request type
1369 + */
1370 + do_action( 'ep_remote_request', $query, $type );
1371 +
1201 1372 return $request;
1202 1373 }
1203 1374
1204 1375 $query['time_finish'] = microtime( true );
@@ -1204,19 +1375,12 @@
1204 1375 $query['time_finish'] = microtime( true );
1205 1376 $query['request'] = $request;
1206 1377 $this->add_query_log( $query );
1207 1378
1208 - /**
1209 - * Fires after Elasticsearch remote request
1210 - *
1211 - * @hook ep_remote_request
1212 - * @param {array} $query Remote request arguments
1213 - * @param {string} $type Request type
1214 - */
1379 + // This action is documented above
1215 1380 do_action( 'ep_remote_request', $query, $type );
1216 1381
1217 1382 return $request;
1218 -
1219 1383 }
1220 1384
1221 1385 /**
1222 1386 * Parse response from Elasticsearch
@@ -1264,130 +1428,165 @@
1264 1428 return array(
1265 1429 'status' => true,
1266 1430 'data' => $response->_all->primaries->indexing,
1267 1431 );
1268 -
1269 1432 }
1270 1433
1271 1434 /**
1272 - * Get ES plugins and version, cache everything
1435 + * Set ES plugins and version, detect server type, and cache everything
1273 1436 *
1274 - * @param bool $force Bust cache or not.
1275 - * @since 2.2
1437 + * @since 4.2.1
1438 + * @param bool $force Bust cache or not.
1276 1439 * @return array
1277 1440 */
1278 - public function get_elasticsearch_info( $force = false ) {
1279 - if ( ! empty( Utils\get_host() ) && ( $force || null === $this->elasticsearch_version || null === $this->elasticsearch_plugins ) ) {
1441 + public function set_elasticsearch_info( $force = false ) {
1442 + if ( empty( Utils\get_host() ) ) {
1443 + return;
1444 + }
1280 1445
1281 - // Get ES info from cache if available. If we are forcing, then skip cache check.
1282 - if ( $force ) {
1283 - $es_info = false;
1446 + if ( ! $force && null !== $this->elasticsearch_version && null !== $this->elasticsearch_plugins ) {
1447 + return;
1448 + }
1449 +
1450 + // Get ES info from cache if available. If we are forcing, then skip cache check.
1451 + if ( ! $force ) {
1452 + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
1453 + $es_info = get_site_transient( 'ep_es_info' );
1284 1454 } else {
1285 - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
1286 - $es_info = get_site_transient( 'ep_es_info' );
1287 - } else {
1288 - $es_info = get_transient( 'ep_es_info' );
1289 - }
1455 + $es_info = get_transient( 'ep_es_info' );
1290 1456 }
1291 -
1292 1457 if ( ! empty( $es_info ) ) {
1293 - // Set ES info from cache.
1294 1458 $this->elasticsearch_version = $es_info['version'];
1295 1459 $this->elasticsearch_plugins = $es_info['plugins'];
1296 - } else {
1297 - $path = '_nodes/plugins';
1460 + $this->server_type = $es_info['server_type'];
1461 + return;
1462 + }
1463 + }
1298 1464
1299 - $request = $this->remote_request( $path, array( 'method' => 'GET' ) );
1465 + $path = '_nodes/plugins';
1300 1466
1301 - if ( is_wp_error( $request ) || 200 !== wp_remote_retrieve_response_code( $request ) ) {
1302 - $this->elasticsearch_version = false;
1303 - $this->elasticsearch_plugins = false;
1467 + $request = $this->remote_request( $path, array( 'method' => 'GET' ) );
1304 1468
1305 - /**
1306 - * Try a different endpoint in case the plugins url is restricted
1307 - *
1308 - * @since 2.2.1
1309 - */
1469 + if ( is_wp_error( $request ) || 200 !== wp_remote_retrieve_response_code( $request ) ) {
1470 + $this->elasticsearch_version = false;
1471 + $this->elasticsearch_plugins = false;
1310 1472
1311 - $request = $this->remote_request( '', array( 'method' => 'GET' ) );
1473 + /**
1474 + * Try a different endpoint in case the plugins url is restricted
1475 + *
1476 + * @since 2.2.1
1477 + */
1312 1478
1313 - if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) {
1314 - $response_body = wp_remote_retrieve_body( $request );
1315 - $response = json_decode( $response_body, true );
1479 + $request = $this->remote_request( '', array( 'method' => 'GET' ) );
1316 1480
1317 - try {
1318 - $this->elasticsearch_version = $response['version']['number'];
1319 - } catch ( Exception $e ) {
1320 - // Do nothing.
1321 - }
1481 + if ( ! is_wp_error( $request ) && 200 === wp_remote_retrieve_response_code( $request ) ) {
1482 + $response_body = wp_remote_retrieve_body( $request );
1483 + $response = json_decode( $response_body, true );
1484 +
1485 + try {
1486 + $this->elasticsearch_version = $response['version']['number'];
1487 + if ( ! empty( $response['version']['distribution'] ) ) {
1488 + $this->server_type = $response['version']['distribution'];
1322 1489 }
1323 - } else {
1324 - $response = json_decode( wp_remote_retrieve_body( $request ), true );
1490 + } catch ( \Exception $e ) {
1491 + // Do nothing.
1492 + }
1493 + }
1494 + return;
1495 + }
1325 1496
1326 - $this->elasticsearch_plugins = [];
1327 - $this->elasticsearch_version = false;
1497 + $response = json_decode( wp_remote_retrieve_body( $request ), true );
1328 1498
1329 - if ( isset( $response['nodes'] ) ) {
1499 + $this->elasticsearch_plugins = [];
1500 + $this->elasticsearch_version = false;
1330 1501
1331 - foreach ( $response['nodes'] as $node ) {
1332 - // Save version of last node. We assume all nodes are same version.
1333 - $this->elasticsearch_version = $node['version'];
1502 + if ( isset( $response['nodes'] ) ) {
1503 + $node = end( $response['nodes'] );
1504 + // Save version of last node. We assume all nodes are same version.
1505 + $this->elasticsearch_version = $node['version'];
1334 1506
1335 - if ( isset( $node['plugins'] ) && is_array( $node['plugins'] ) ) {
1507 + // Elasticsearch calls "modules" all default plugins that can't be uninstalled
1508 + if ( isset( $node['modules'] ) && is_array( $node['modules'] ) ) {
1509 + foreach ( $node['modules'] as $plugin ) {
1510 + $this->elasticsearch_plugins[ $plugin['name'] ] = $plugin['version'];
1511 + }
1336 1512
1337 - foreach ( $node['plugins'] as $plugin ) {
1338 -
1339 - $this->elasticsearch_plugins[ $plugin['name'] ] = $plugin['version'];
1340 - }
1341 -
1342 - break;
1343 - }
1344 - }
1345 - }
1513 + if ( ! empty( $node['modules'] ) && ! empty( $node['modules'][0]['opensearch_version'] ) ) {
1514 + $this->server_type = 'opensearch';
1346 1515 }
1516 + }
1347 1517
1348 - /**
1349 - * Cache ES info
1350 - *
1351 - * @since 2.3.1
1352 - */
1353 -
1354 - /**
1355 - * Filter elasticsearch info cache expiration
1356 - *
1357 - * @hook ep_es_info_cache_expiration
1358 - * @param {int} $time Cache time in seconds
1359 - * @return {int} New cache time
1360 - */
1361 - if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
1362 - set_site_transient(
1363 - 'ep_es_info',
1364 - array(
1365 - 'version' => $this->elasticsearch_version,
1366 - 'plugins' => $this->elasticsearch_plugins,
1367 - ),
1368 - apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) )
1369 - );
1370 - } else {
1371 - set_transient(
1372 - 'ep_es_info',
1373 - array(
1374 - 'version' => $this->elasticsearch_version,
1375 - 'plugins' => $this->elasticsearch_plugins,
1376 - ),
1377 - apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) )
1378 - );
1518 + if ( isset( $node['plugins'] ) && is_array( $node['plugins'] ) ) {
1519 + foreach ( $node['plugins'] as $plugin ) {
1520 + $this->elasticsearch_plugins[ $plugin['name'] ] = $plugin['version'];
1379 1521 }
1380 1522 }
1381 1523 }
1382 1524
1383 - return array(
1384 - 'plugins' => $this->elasticsearch_plugins,
1385 - 'version' => $this->elasticsearch_version,
1386 - );
1525 + /**
1526 + * Cache ES info
1527 + *
1528 + * @since 2.3.1
1529 + */
1530 + $this->cache_elasticsearch_info();
1387 1531 }
1388 1532
1389 1533 /**
1534 + * Return ES plugins, version and type.
1535 + *
1536 + * This function also sets those values in the object instance, getting it from cache
1537 + * or not, according to `$force` value.
1538 + *
1539 + * @param bool $force Bust cache or not.
1540 + * @since 2.2
1541 + * @return array
1542 + */
1543 + public function get_elasticsearch_info( $force = false ) {
1544 + $this->set_elasticsearch_info( $force );
1545 + return [
1546 + 'plugins' => $this->elasticsearch_plugins,
1547 + 'version' => $this->elasticsearch_version,
1548 + 'server_type' => $this->server_type,
1549 + ];
1550 + }
1551 +
1552 + /**
1553 + * Cache the ES info.
1554 + *
1555 + * @since 4.2.1
1556 + */
1557 + protected function cache_elasticsearch_info() {
1558 + /**
1559 + * Filter elasticsearch info cache expiration
1560 + *
1561 + * @hook ep_es_info_cache_expiration
1562 + * @param {int} $time Cache time in seconds
1563 + * @return {int} New cache time
1564 + */
1565 + if ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
1566 + set_site_transient(
1567 + 'ep_es_info',
1568 + array(
1569 + 'version' => $this->elasticsearch_version,
1570 + 'plugins' => $this->elasticsearch_plugins,
1571 + 'server_type' => $this->server_type,
1572 + ),
1573 + apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) )
1574 + );
1575 + } else {
1576 + set_transient(
1577 + 'ep_es_info',
1578 + array(
1579 + 'version' => $this->elasticsearch_version,
1580 + 'plugins' => $this->elasticsearch_plugins,
1581 + 'server_type' => $this->server_type,
1582 + ),
1583 + apply_filters( 'ep_es_info_cache_expiration', ( 5 * MINUTE_IN_SECONDS ) )
1584 + );
1585 + }
1586 + }
1587 +
1588 + /**
1390 1589 * Get cluster status
1391 1590 *
1392 1591 * Retrieves cluster stats from Elasticsearch.
1393 1592 *
@@ -1520,9 +1719,9 @@
1520 1719 /**
1521 1720 * Filter the User Agent header when submitting requests to Elasticsearch.
1522 1721 *
1523 1722 * @hook ep_remote_request_add_ep_user_agent
1524 - * @param {bool} $should_add_ep_verion Whether the ElasticPress version should be added to the User Agent string.
1723 + * @param {bool} $should_add_ep_version Whether the ElasticPress version should be added to the User Agent string.
1525 1724 * @return {bool} New value
1526 1725 * @since 3.6.1
1527 1726 */
1528 1727 if ( apply_filters( 'ep_remote_request_add_ep_user_agent', Utils\is_epio() ) ) {
@@ -1539,14 +1738,27 @@
1539 1738 /**
1540 1739 * Query logging. Don't log anything to the queries property when
1541 1740 * WP_DEBUG is not enabled. Calls action 'ep_add_query_log' if you
1542 1741 * want to access the query outside of the ElasticPress plugin. This
1543 - * runs regardless of debufg settings.
1742 + * runs regardless of debug settings.
1544 1743 *
1545 1744 * @param array $query Query to log.
1546 1745 */
1547 1746 protected function add_query_log( $query ) {
1548 - if ( ( defined( 'WP_DEBUG' ) && WP_DEBUG ) || ( defined( 'WP_EP_DEBUG' ) && WP_EP_DEBUG ) ) {
1747 + $wp_debug = defined( 'WP_DEBUG' ) && WP_DEBUG;
1748 + $wp_ep_debug = defined( 'WP_EP_DEBUG' ) && WP_EP_DEBUG;
1749 +
1750 + /**
1751 + * Filter query logging. Don't log anything to the queries property when true.
1752 + *
1753 + * @hook ep_disable_query_logging
1754 + * @param {bool} Whether to log to the queries property. Defaults to false.
1755 + * @return {bool} New value
1756 + * @since 5.1.4
1757 + */
1758 + $disable_query_logging = apply_filters( 'ep_disable_query_logging', false );
1759 +
1760 + if ( ! $disable_query_logging && ( $wp_debug || $wp_ep_debug ) ) {
1549 1761 $this->queries[] = $query;
1550 1762 }
1551 1763
1552 1764 /**
@@ -1557,5 +1769,66 @@
1557 1769 */
1558 1770 do_action( 'ep_add_query_log', $query );
1559 1771 }
1560 1772
1773 + /**
1774 + * Get all index names.
1775 + *
1776 + * @param string $status Whether to return active indexables or all registered.
1777 + * @since 4.4.0, 4.5.0 Added $status
1778 + * @return array
1779 + */
1780 + public function get_index_names( $status = 'active' ) {
1781 + $sites = ( defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) ?
1782 + Utils\get_sites( 0, true ) :
1783 + array( array( 'blog_id' => get_current_blog_id() ) );
1784 +
1785 + $all_indexables = Indexables::factory()->get_all( null, false, $status );
1786 +
1787 + $global_indexes = [];
1788 + $non_global_indexes = [];
1789 + foreach ( $all_indexables as $indexable ) {
1790 + if ( $indexable->global ) {
1791 + $global_indexes[] = $indexable->get_index_name();
1792 + continue;
1793 + }
1794 +
1795 + foreach ( $sites as $site ) {
1796 + $non_global_indexes[] = $indexable->get_index_name( $site['blog_id'] );
1797 + }
1798 + }
1799 +
1800 + return array_merge( $non_global_indexes, $global_indexes );
1801 + }
1802 +
1803 + /**
1804 + * Return all indices from the cluster.
1805 + *
1806 + * @since 4.4.0
1807 + * @return array Array of indices in Elasticsearch
1808 + */
1809 + public function get_cluster_indices(): array {
1810 + $path = '_cat/indices?format=json';
1811 +
1812 + $response = $this->remote_request( $path );
1813 +
1814 + return (array) json_decode( wp_remote_retrieve_body( $response ), true );
1815 + }
1816 +
1817 + /**
1818 + * Return a comparison between which indices should be and are present in the ES server.
1819 + *
1820 + * @since 4.6.0
1821 + * @return array Array with `missing_indices` and `present_indices` keys.
1822 + */
1823 + public function get_indices_comparison() {
1824 + $all_index_names = $this->get_index_names();
1825 + $cluster_indices = $this->get_cluster_indices();
1826 +
1827 + $cluster_index_names = wp_list_pluck( $cluster_indices, 'index' );
1828 +
1829 + return [
1830 + 'missing_indices' => array_diff( $all_index_names, $cluster_index_names ),
1831 + 'present_indices' => array_intersect( $all_index_names, $cluster_index_names ),
1832 + ];
1833 + }
1561 1834 }