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/IndexHelper.php +186 -44 4.7.05.3.5 View file →
@@ -6,15 +6,15 @@
6 6 * call, to respond to the AJAX request. That means this script will be called several times
7 7 * while syncing via dashboard, relying on the index_meta to pick it up where it stopped.
8 8 *
9 9 * @since 4.0.0
10 - * @see https://elasticpress.zendesk.com/hc/en-us/articles/16672117103501-Sync-Process
10 + * @see https://www.elasticpress.io/resources/articles/sync-process/
11 11 * @package elasticpress
12 12 */
13 13
14 14 namespace ElasticPress;
15 15
16 -use ElasticPress\Utils as Utils;
16 +use ElasticPress\Utils;
17 17
18 18 /**
19 19 * Index Helper Class.
20 20 *
@@ -84,8 +84,9 @@
84 84 */
85 85 $this->args = apply_filters( 'ep_sync_args', $args, $this->index_meta );
86 86
87 87 if ( false === $this->index_meta ) {
88 + $this->maybe_apply_feature_settings();
88 89 $this->build_index_meta();
89 90 }
90 91
91 92 // For the dashboard, this will be called and exit the script until the queue is empty again.
@@ -143,8 +144,9 @@
143 144 'start_time' => microtime( true ),
144 145 'start_date_time' => $start_date_time ? $start_date_time->format( DATE_ATOM ) : false,
145 146 'starting_indices' => $starting_indices,
146 147 'messages_queue' => [],
148 + 'trigger' => ! empty( $this->args['trigger'] ) ? sanitize_text_field( $this->args['trigger'] ) : null,
147 149 'totals' => [
148 150 'total' => 0,
149 151 'synced' => 0,
150 152 'skipped' => 0,
@@ -247,9 +249,9 @@
247 249 */
248 250 protected function filter_indexables( $indexables ) {
249 251 return array_filter(
250 252 $indexables,
251 - function( $indexable ) {
253 + function ( $indexable ) {
252 254 return empty( $this->args['indexables'] ) || in_array( $indexable, $this->args['indexables'], true );
253 255 }
254 256 );
255 257 }
@@ -569,9 +571,9 @@
569 571 $queued_items = [];
570 572
571 573 foreach ( $this->current_query['objects'] as $object ) {
572 574 if ( $this->should_skip_object_index( $object, $indexable ) ) {
573 - $this->index_meta['current_sync_item']['skipped']++;
575 + ++$this->index_meta['current_sync_item']['skipped'];
574 576 } else {
575 577 $queued_items[ $object->ID ] = true;
576 578 }
577 579 }
@@ -672,9 +674,9 @@
672 674 $failed_objects = array_merge(
673 675 $failed_objects,
674 676 array_filter(
675 677 $return['items'],
676 - function( $item ) {
678 + function ( $item ) {
677 679 return ! empty( $item['index']['error'] );
678 680 }
679 681 )
680 682 );
@@ -819,8 +821,9 @@
819 821
820 822 $current_sync_item = $this->index_meta['current_sync_item'];
821 823
822 824 $this->index_meta['current_sync_item'] = null;
825 + $this->index_meta['offset'] = 0;
823 826
824 827 if ( $current_sync_item['failed'] ) {
825 828 if ( ! empty( $current_sync_item['blog_id'] ) && defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
826 829 $message = sprintf(
@@ -841,10 +844,8 @@
841 844
842 845 $this->output( $message, 'warning' );
843 846 }
844 847
845 - $this->index_meta['offset'] = 0;
846 -
847 848 if ( ! empty( $current_sync_item['blog_id'] ) && defined( 'EP_IS_NETWORK' ) && EP_IS_NETWORK ) {
848 849 $message = sprintf(
849 850 /* translators: 1: indexable (plural), 2: Blog ID, 3: number of synced objects */
850 851 esc_html__( 'Number of %1$s indexed on site %2$d: %3$d', 'elasticpress' ),
@@ -867,14 +868,16 @@
867 868 /**
868 869 * Update last sync info.
869 870 *
870 871 * @since 4.2.0
872 + * @param string $final_status Optional final status
871 873 */
872 - protected function update_last_index() {
874 + protected function update_last_index( string $final_status = '' ) {
875 + $is_full_sync = $this->index_meta['put_mapping'];
876 + $method = $this->index_meta['method'];
873 877 $start_time = $this->index_meta['start_time'];
874 878 $totals = $this->index_meta['totals'];
875 - $method = $this->index_meta['method'];
876 - $is_full_sync = $this->index_meta['put_mapping'];
879 + $trigger = $this->index_meta['trigger'];
877 880
878 881 $this->index_meta = null;
879 882
880 883 $end_date_time = date_create( 'now', wp_timezone() );
@@ -879,19 +882,67 @@
879 882
880 883 $end_date_time = date_create( 'now', wp_timezone() );
881 884 $start_time_sec = (int) $start_time;
882 885
886 + // Time related info
883 887 $totals['end_date_time'] = $end_date_time ? $end_date_time->format( DATE_ATOM ) : false;
884 888 $totals['start_date_time'] = $start_time ? wp_date( DATE_ATOM, $start_time_sec ) : false;
885 889 $totals['end_time_gmt'] = time();
886 890 $totals['total_time'] = microtime( true ) - $start_time;
887 - $totals['method'] = $method;
888 - $totals['is_full_sync'] = $is_full_sync;
891 +
892 + // Additional info
893 + $totals['is_full_sync'] = $is_full_sync;
894 + $totals['method'] = $method;
895 + $totals['trigger'] = $trigger;
896 +
897 + // Final status
898 + if ( '' !== $final_status ) {
899 + $totals['final_status'] = $final_status;
900 + } elseif ( ! empty( $totals['failed'] ) ) {
901 + $totals['final_status'] = 'with_errors';
902 + } else {
903 + $totals['final_status'] = 'success';
904 + }
905 +
889 906 Utils\update_option( 'ep_last_cli_index', $totals, false );
890 - Utils\update_option( 'ep_last_index', $totals, false );
907 +
908 + $this->add_last_sync( $totals );
891 909 }
892 910
893 911 /**
912 + * Add a sync to the list of all past syncs
913 + *
914 + * @since 5.0.0
915 + * @param array $last_sync_info The latest sync info to be added to the log
916 + * @return void
917 + */
918 + protected function add_last_sync( array $last_sync_info ) {
919 + // Remove error messages from previous syncs - we only store msgs for the newest one.
920 + $last_syncs = array_map(
921 + function ( $sync ) {
922 + unset( $sync['errors'] );
923 + return $sync;
924 + },
925 + $this->get_sync_history()
926 + );
927 +
928 + /**
929 + * Filter the number of past syncs to keep info
930 + *
931 + * @since 5.0.0
932 + * @hook ep_syncs_to_keep_info
933 + * @param {int} $number Number of past syncs to keep info
934 + * @return {int} New number
935 + */
936 + $syncs_to_keep = (int) apply_filters( 'ep_syncs_to_keep_info', 5 );
937 +
938 + $last_syncs = array_slice( $last_syncs, 0, $syncs_to_keep - 1 );
939 + array_unshift( $last_syncs, $last_sync_info );
940 +
941 + Utils\update_option( 'ep_sync_history', $last_syncs, false );
942 + }
943 +
944 + /**
894 945 * Make the necessary clean up after everything was sync'd.
895 946 *
896 947 * @since 4.0.0
897 948 */
@@ -901,11 +952,12 @@
901 952 /**
902 953 * Fires after executing a reindex
903 954 *
904 955 * @since 4.0.0
956 + * @param array $args Sync arguments.
905 957 * @hook ep_after_sync_index
906 958 */
907 - do_action( 'ep_after_sync_index' );
959 + do_action( 'ep_after_sync_index', $this->args );
908 960
909 961 /**
910 962 * Fires after executing a reindex
911 963 *
@@ -979,9 +1031,9 @@
979 1031 if ( $this->index_meta ) {
980 1032 Utils\update_option( 'ep_index_meta', $this->index_meta );
981 1033 } else {
982 1034 Utils\delete_option( 'ep_index_meta' );
983 - $totals = $this->get_last_index();
1035 + $totals = $this->get_last_sync();
984 1036 }
985 1037
986 1038 $message = [
987 1039 'message' => ( is_array( $message_text ) ) ? implode( "\n", $message_text ) : $message_text,
@@ -989,8 +1041,12 @@
989 1041 'totals' => $totals ?? [],
990 1042 'status' => $type,
991 1043 ];
992 1044
1045 + if ( in_array( $type, [ 'warning', 'error' ], true ) ) {
1046 + $message['errors'] = $this->build_message_errors_data( $message_text );
1047 + }
1048 +
993 1049 if ( is_callable( $this->args['output_method'] ) ) {
994 1050 call_user_func( $this->args['output_method'], $message, $this->args, $this->index_meta, $context );
995 1051 }
996 1052 }
@@ -1035,9 +1091,9 @@
1035 1091 return $error_text;
1036 1092 }
1037 1093
1038 1094 /**
1039 - * Utilitary function to check if the indexable is being fully reindexed, i.e.,
1095 + * Utility function to check if the indexable is being fully reindexed, i.e.,
1040 1096 * the index was deleted, a new mapping was sent and content is being reindexed.
1041 1097 *
1042 1098 * @param string $indexable_slug Indexable slug.
1043 1099 * @param int|null $blog_id Blog ID
@@ -1083,50 +1139,71 @@
1083 1139 return apply_filters( "ep_is_full_reindexing_{$indexable_slug}", $is_full_reindexing );
1084 1140 }
1085 1141
1086 1142 /**
1087 - * Get the last index/sync meta information.
1143 + * Get the previous syncs meta information.
1088 1144 *
1089 - * @since 4.2.0
1145 + * @since 5.0.0
1090 1146 * @return array
1091 1147 */
1092 - public function get_last_index() {
1093 - return Utils\get_option( 'ep_last_index', [] );
1148 + public function get_sync_history(): array {
1149 + return Utils\get_option( 'ep_sync_history', [] );
1094 1150 }
1095 1151
1096 1152 /**
1153 + * Get the last sync meta information.
1154 + *
1155 + * @since 5.0.0
1156 + * @return array
1157 + */
1158 + public function get_last_sync(): array {
1159 + $syncs = $this->get_sync_history();
1160 + if ( empty( $syncs ) ) {
1161 + return [];
1162 + }
1163 + return array_shift( $syncs );
1164 + }
1165 +
1166 + /**
1097 1167 * Check if an object should be indexed or skipped.
1098 1168 *
1099 1169 * We used to have two different filters for this (one for the dashboard, another for CLI),
1100 1170 * this method combines both.
1101 1171 *
1102 - * @param {stdClass} $object Object to be checked
1172 + * @param {stdClass} $indexable_object Object to be checked
1103 1173 * @param {Indexable} $indexable Indexable
1104 1174 * @return boolean
1105 1175 */
1106 - protected function should_skip_object_index( $object, $indexable ) {
1176 + protected function should_skip_object_index( $indexable_object, $indexable ) {
1107 1177 /**
1108 1178 * Filter whether to not sync specific item in dashboard or not
1109 1179 *
1110 1180 * @since 2.1
1181 + * @deprecated 5.3.3 Use ep_{indexable_slug}_sync_kill instead
1111 1182 * @hook ep_item_sync_kill
1112 1183 * @param {boolean} $kill False means dont sync
1113 - * @param {array} $object Object to sync
1184 + * @param {array} $indexable_object Object to sync
1114 1185 * @return {Indexable} Indexable that object belongs to
1115 1186 */
1116 - $ep_item_sync_kill = apply_filters( 'ep_item_sync_kill', false, $object, $indexable );
1187 + $ep_item_sync_kill = apply_filters_deprecated(
1188 + 'ep_item_sync_kill',
1189 + [ false, $indexable_object, $indexable ],
1190 + 'ElasticPress 5.3.3',
1191 + 'ep_' . $indexable->slug . '_sync_kill'
1192 + );
1117 1193
1118 - /**
1119 - * Conditionally kill indexing for a post
1120 - *
1121 - * @hook ep_{indexable_slug}_index_kill
1122 - * @param {bool} $index True means dont index
1123 - * @param {int} $object_id Object ID
1124 - * @return {bool} New value
1125 - */
1126 - $ep_indexable_sync_kill = apply_filters( 'ep_' . $indexable->slug . '_index_kill', false, $object->ID );
1194 + /** This filter is documented in includes/classes/Indexable.php */
1195 + $ep_indexable_index_kill = apply_filters_deprecated(
1196 + 'ep_' . $indexable->slug . '_index_kill',
1197 + [ false, $indexable_object->ID ],
1198 + 'ElasticPress 5.3.3',
1199 + 'ep_' . $indexable->slug . '_sync_kill'
1200 + );
1127 1201
1128 - return $ep_item_sync_kill || $ep_indexable_sync_kill;
1202 + /** This filter is documented in includes/classes/Indexable.php */
1203 + $ep_indexable_sync_kill = apply_filters( 'ep_' . $indexable->slug . '_sync_kill', false, $indexable_object->ID );
1204 +
1205 + return $ep_item_sync_kill || $ep_indexable_sync_kill || $ep_indexable_index_kill;
1129 1206 }
1130 1207
1131 1208 /**
1132 1209 * Given an array, create a new sync item and add it to the stack.
@@ -1207,16 +1284,14 @@
1207 1284 * in-memory cache for persistent object caches
1208 1285 */
1209 1286 if ( function_exists( 'wp_cache_flush_runtime' ) ) {
1210 1287 wp_cache_flush_runtime();
1211 - } else {
1288 + } elseif ( ! wp_using_ext_object_cache() ) {
1212 1289 /*
1213 1290 * In the case where we're not using an external object cache, we need to call flush on the default
1214 1291 * WordPress object cache class to clear the values from the cache property
1215 1292 */
1216 - if ( ! wp_using_ext_object_cache() ) {
1217 - wp_cache_flush();
1218 - }
1293 + wp_cache_flush();
1219 1294 }
1220 1295
1221 1296 if ( is_object( $wp_object_cache ) ) {
1222 1297 $wp_object_cache->group_ops = [];
@@ -1257,19 +1332,22 @@
1257 1332 do_action( 'ep_stop_the_insanity' );
1258 1333 }
1259 1334
1260 1335 /**
1261 - * Utilitary function to delete the index meta option.
1336 + * Utility function to delete the index meta option.
1262 1337 *
1263 1338 * @since 4.0.0
1264 1339 */
1265 1340 public function clear_index_meta() {
1341 + if ( ! empty( $this->index_meta ) ) {
1342 + $this->update_last_index( 'aborted' );
1343 + }
1266 1344 $this->index_meta = false;
1267 1345 Utils\delete_option( 'ep_index_meta', false );
1268 1346 }
1269 1347
1270 1348 /**
1271 - * Utilitary function to get the index meta option.
1349 + * Utility function to get the index meta option.
1272 1350 *
1273 1351 * @return array
1274 1352 * @since 4.0.0
1275 1353 */
@@ -1321,9 +1399,9 @@
1321 1399 $totals = $this->index_meta['totals'];
1322 1400
1323 1401 $this->index_meta['totals']['errors'][] = $error['message'];
1324 1402 $this->index_meta['totals']['failed'] = $totals['total'] - ( $totals['synced'] + $totals['skipped'] );
1325 - $this->update_last_index();
1403 + $this->update_last_index( 'failed' );
1326 1404
1327 1405 /**
1328 1406 * Fires after a sync failed due to a PHP fatal error.
1329 1407 *
@@ -1339,10 +1417,12 @@
1339 1417 /* translators: Error message */
1340 1418 esc_html__( 'Mapping failed: %s', 'elasticpress' ),
1341 1419 Utils\get_elasticsearch_error_reason( $error['message'] )
1342 1420 );
1343 - $message .= "\n";
1344 - $message .= esc_html__( 'Mapping has failed, which will cause ElasticPress search results to be incorrect. Please click `Delete all Data and Start a Fresh Sync` to retry mapping.', 'elasticpress' );
1421 + if ( $this->should_suggest_retry( $message ) ) {
1422 + $message .= "\n";
1423 + $message .= esc_html__( 'Mapping has failed, which will cause ElasticPress search results to be incorrect. Please click `Delete all Data and Start a Fresh Sync` to retry mapping.', 'elasticpress' );
1424 + }
1345 1425 break;
1346 1426 default:
1347 1427 /* translators: Error message */
1348 1428 $message = sprintf( esc_html__( 'Index failed: %s', 'elasticpress' ), $error['message'] );
@@ -1357,9 +1437,9 @@
1357 1437 *
1358 1438 * @since 4.4.0
1359 1439 * @return integer
1360 1440 */
1361 - public function get_index_default_per_page() : int {
1441 + public function get_index_default_per_page(): int {
1362 1442 /**
1363 1443 * Filter number of items to index per cycle in the dashboard
1364 1444 *
1365 1445 * @since 2.1
@@ -1411,8 +1491,58 @@
1411 1491 }
1412 1492 }
1413 1493
1414 1494 /**
1495 + * Get data for a given error message(s)
1496 + *
1497 + * @since 5.0.0
1498 + * @param string|array $messages Messages
1499 + * @return array
1500 + */
1501 + protected function build_message_errors_data( $messages ): array {
1502 + $messages = (array) $messages;
1503 + $error_interpreter = new \ElasticPress\ElasticsearchErrorInterpreter();
1504 +
1505 + $errors_list = [];
1506 + foreach ( $messages as $message ) {
1507 + $error = $error_interpreter->maybe_suggest_solution_for_es( $message );
1508 +
1509 + if ( ! isset( $errors_list[ $error['error'] ] ) ) {
1510 + $errors_list[ $error['error'] ] = [
1511 + 'solution' => $error['solution'],
1512 + 'count' => 1,
1513 + ];
1514 + } else {
1515 + ++$errors_list[ $error['error'] ]['count'];
1516 + }
1517 + }
1518 + return $errors_list;
1519 + }
1520 +
1521 + /**
1522 + * If this is a full sync, apply the draft feature settings
1523 + *
1524 + * @since 5.0.0
1525 + */
1526 + protected function maybe_apply_feature_settings() {
1527 + if ( empty( $this->args['put_mapping'] ) ) {
1528 + return;
1529 + }
1530 +
1531 + Features::factory()->apply_draft_feature_settings();
1532 + }
1533 +
1534 + /**
1535 + * Whether to suggest retrying the sync or not.
1536 + *
1537 + * @param string $message The message returned by the hosting server
1538 + * @return boolean
1539 + */
1540 + protected function should_suggest_retry( $message ) {
1541 + return ! preg_match( '/you have reached the limit of indices your plan supports/', $message );
1542 + }
1543 +
1544 + /**
1415 1545 * Return singleton instance of class.
1416 1546 *
1417 1547 * @return self
1418 1548 * @since 4.0.0
@@ -1425,6 +1555,18 @@
1425 1555 $instance->setup();
1426 1556 }
1427 1557
1428 1558 return $instance;
1559 + }
1560 +
1561 + /**
1562 + * DEPRECATED. Get the last index/sync meta information.
1563 + *
1564 + * @since 4.2.0
1565 + * @deprecated 5.0.0
1566 + * @return array
1567 + */
1568 + public function get_last_index() {
1569 + _deprecated_function( __METHOD__, '5.0.0', '\ElasticPress\IndexHelper::get_last_sync' );
1570 + return $this->get_last_sync();
1429 1571 }
1430 1572 }