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

FailedQueries.php in ElasticPress 4.4.1, at includes/classes/StatusReport/FailedQueries.php

234 lines 7.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Failed Queries report class
4 *
5 * @since 4.4.0
6 * @package elasticpress
7 */
8
9 namespace ElasticPress\StatusReport;
10
11 use \ElasticPress\QueryLogger;
12 use \ElasticPress\Utils;
13
14 defined( 'ABSPATH' ) || exit;
15
16 /**
17 * FailedQueries report class
18 *
19 * @package ElasticPress
20 */
21 class FailedQueries extends Report {
22
23 /**
24 * The logger instance
25 *
26 * @var QueryLogger
27 */
28 protected $query_logger;
29
30 /**
31 * Class constructor
32 *
33 * @param QueryLogger $query_logger The logger instance
34 */
35 public function __construct( QueryLogger $query_logger ) {
36 $this->query_logger = $query_logger;
37 }
38
39 /**
40 * Return the report title
41 *
42 * @return string
43 */
44 public function get_title() : string {
45 return __( 'Failed Queries', 'elasticpress' );
46 }
47
48 /**
49 * Return the report fields
50 *
51 * @return array
52 */
53 public function get_groups() : array {
54 $this->maybe_clear_logs();
55
56 $logs = $this->query_logger->get_logs( false );
57
58 $labels = [
59 'wp_url' => esc_html__( 'Page URL', 'elasticpress' ),
60 'es_req' => esc_html__( 'Elasticsearch Request', 'elasticpress' ),
61 'timestamp' => esc_html__( 'Time', 'elasticpress' ),
62 'query_time' => esc_html__( 'Time Spent (ms)', 'elasticpress' ),
63 'wp_args' => esc_html__( 'WP Query Args', 'elasticpress' ),
64 'status_code' => esc_html__( 'HTTP Status Code', 'elasticpress' ),
65 'body' => esc_html__( 'Query Body', 'elasticpress' ),
66 'result' => esc_html__( 'Query Result', 'elasticpress' ),
67 ];
68
69 $groups = [];
70 foreach ( $logs as $log ) {
71 list( $error, $solution ) = $this->analyze_log( $log );
72
73 $fields = [
74 'error' => [
75 'label' => __( 'Error', 'elasticpress' ),
76 'value' => $error,
77 ],
78 'recommended_solution' => [
79 'label' => __( 'Recommended Solution', 'elasticpress' ),
80 'value' => $solution,
81 ],
82 ];
83
84 foreach ( $log as $field => $value ) {
85 // Already outputted in the title
86 if ( in_array( $field, [ 'wp_url', 'timestamp' ], true ) ) {
87 continue;
88 }
89
90 $fields[ $field ] = [
91 'label' => $labels[ $field ] ?? $field,
92 'value' => $value,
93 ];
94 }
95
96 $groups[] = [
97 'title' => sprintf( '%s (%s)', $log['wp_url'], date_i18n( 'Y-m-d H:i:s', $log['timestamp'] ) ),
98 'fields' => $fields,
99 ];
100 }
101
102 return $groups;
103 }
104
105 /**
106 * Return the output of a button to clear the logged queries
107 *
108 * @return string
109 */
110 public function get_actions() : array {
111 global $wp;
112
113 $logs = $this->query_logger->get_logs( false );
114
115 if ( empty( $logs ) ) {
116 return [];
117 }
118
119 $label = __( 'Clear query log', 'elasticpress' );
120 $href = wp_nonce_url( add_query_arg( [ $_GET ], $wp->request ), 'ep-clear-logged-queries', '_wpnonce' ); // phpcs:ignore WordPress.Security.NonceVerification
121
122 return [
123 [
124 'href' => $href,
125 'label' => $label,
126 ],
127 ];
128 }
129
130 /**
131 * If a nonce is present, clear the logs
132 */
133 protected function maybe_clear_logs() {
134 if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'ep-clear-logged-queries' ) ) { // phpcs:ignore WordPress.Security.NonceVerification
135 return;
136 }
137
138 $this->query_logger->clear_logs();
139 }
140
141 /**
142 * Given a log, try to find the error and its solution
143 *
144 * @param array $log The log
145 * @return array The error in index 0, solution in index 1
146 */
147 public function analyze_log( $log ) {
148 $error = '';
149
150 if ( ! empty( $log['result']['error'] ) && ! empty( $log['result']['error']['root_cause'][0]['reason'] ) ) {
151 $error = $log['result']['error']['root_cause'][0]['reason'];
152 }
153
154 if ( ! empty( $log['result']['errors'] ) && ! empty( $log['result']['items'] ) && ! empty( $log['result']['items'][0]['index']['error']['reason'] ) ) {
155 $error = $log['result']['items'][0]['index']['error']['reason'];
156 }
157
158 $solution = ( ! empty( $error ) ) ?
159 $this->maybe_suggest_solution_for_es( $error ) :
160 '';
161
162 return [ $error, $solution ];
163 }
164
165 /**
166 * Given an Elasticsearch error, try to suggest a solution
167 *
168 * @param string $error The error
169 * @return string
170 */
171 protected function maybe_suggest_solution_for_es( $error ) {
172 $sync_url = Utils\get_sync_url();
173
174 if ( preg_match( '/no such index \[(.*?)\]/', $error, $matches ) ) {
175 return sprintf(
176 /* translators: 1. Index name; 2. Sync Page URL */
177 __( 'It seems the %1$s index is missing. <a href="%2$s">Delete all data and sync</a> to fix the issue.', 'elasticpress' ),
178 '<code>' . $matches[1] . '</code>',
179 $sync_url
180 );
181 }
182
183 if ( preg_match( '/No mapping found for \[(.*?)\] in order to sort on/', $error, $matches ) ) {
184 return sprintf(
185 /* translators: 1. Index name; 2. Sync Page URL */
186 __( 'The field %1$s was not found. Make sure it is added to the list of indexed fields and run <a href="%2$s">a new sync</a> to fix the issue.', 'elasticpress' ),
187 '<code>' . $matches[1] . '</code>',
188 $sync_url
189 );
190 }
191
192 /* translators: 1. Field name; 2. Sync Page URL */
193 $field_type_solution = __( 'It seems you saved a post without doing a full sync first because <code>%1$s</code> is missing the correct mapping type. <a href="%2$s">Delete all data and sync</a> to fix the issue.', 'elasticpress' );
194
195 if ( preg_match( '/Fielddata is disabled on text fields by default. Set fielddata=true on \[(.*?)\]/', $error, $matches ) ) {
196 return sprintf( $field_type_solution, $matches[1], $sync_url );
197 }
198
199 if ( preg_match( '/field \[(.*?)\] is of type \[(.*?)\], but only numeric types are supported./', $error, $matches ) ) {
200 return sprintf( $field_type_solution, $matches[1], $sync_url );
201 }
202
203 if ( preg_match( '/Alternatively, set fielddata=true on \[(.*?)\] in order to load field data by uninverting the inverted index./', $error, $matches ) ) {
204 return sprintf( $field_type_solution, $matches[1], $sync_url );
205 }
206
207 if ( preg_match( '/Limit of total fields \[(.*?)\] in index \[(.*?)\] has been exceeded/', $error, $matches ) ) {
208 return sprintf(
209 /* translators: Elasticsearch or ElasticPress.io; 2. Link to article; 3. Link to article */
210 __( 'Your website content has more public custom fields than %1$s is able to store. Check our articles about <a href="%2$s">Elasticsearch field limitations</a> and <a href="%3$s">how to index just the custom fields you need</a> and sync again.', 'elasticpress' ),
211 Utils\is_epio() ? __( 'ElasticPress.io', 'elasticpress' ) : __( 'Elasticsearch', 'elasticpress' ),
212 'https://elasticpress.zendesk.com/hc/en-us/articles/360051401212-I-get-the-error-Limit-of-total-fields-in-index-has-been-exceeded-',
213 'https://elasticpress.zendesk.com/hc/en-us/articles/360052019111'
214 );
215 }
216
217 // field limit
218
219 if ( Utils\is_epio() ) {
220 return sprintf(
221 /* translators: ElasticPress.io My Account URL */
222 __( 'We did not recognize this error. Please open an ElasticPress.io <a href="%s">support ticket</a> so we can troubleshoot further.', 'elasticpress' ),
223 'https://www.elasticpress.io/my-account/'
224 );
225 }
226
227 return sprintf(
228 /* translators: New GitHub issue URL */
229 __( 'We did not recognize this error. Please consider opening a <a href="%s">GitHub Issue</a> so we can add it to our list of supported errors. ', 'elasticpress' ),
230 'https://github.com/10up/ElasticPress/issues/new/choose'
231 );
232 }
233 }
234