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

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

235 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 'request_id' => esc_html__( 'Request ID', 'elasticpress' ),
62 'timestamp' => esc_html__( 'Time', 'elasticpress' ),
63 'query_time' => esc_html__( 'Time Spent (ms)', 'elasticpress' ),
64 'wp_args' => esc_html__( 'WP Query Args', 'elasticpress' ),
65 'status_code' => esc_html__( 'HTTP Status Code', 'elasticpress' ),
66 'body' => esc_html__( 'Query Body', 'elasticpress' ),
67 'result' => esc_html__( 'Query Result', 'elasticpress' ),
68 ];
69
70 $groups = [];
71 foreach ( $logs as $log ) {
72 list( $error, $solution ) = $this->analyze_log( $log );
73
74 $fields = [
75 'error' => [
76 'label' => __( 'Error', 'elasticpress' ),
77 'value' => $error,
78 ],
79 'recommended_solution' => [
80 'label' => __( 'Recommended Solution', 'elasticpress' ),
81 'value' => $solution,
82 ],
83 ];
84
85 foreach ( $log as $field => $value ) {
86 // Already outputted in the title
87 if ( in_array( $field, [ 'wp_url', 'timestamp' ], true ) ) {
88 continue;
89 }
90
91 $fields[ $field ] = [
92 'label' => $labels[ $field ] ?? $field,
93 'value' => $value,
94 ];
95 }
96
97 $groups[] = [
98 'title' => sprintf( '%s (%s)', $log['wp_url'], date_i18n( 'Y-m-d H:i:s', $log['timestamp'] ) ),
99 'fields' => $fields,
100 ];
101 }
102
103 return $groups;
104 }
105
106 /**
107 * Return the output of a button to clear the logged queries
108 *
109 * @return string
110 */
111 public function get_actions() : array {
112 global $wp;
113
114 $logs = $this->query_logger->get_logs( false );
115
116 if ( empty( $logs ) ) {
117 return [];
118 }
119
120 $label = __( 'Clear query log', 'elasticpress' );
121 $href = wp_nonce_url( add_query_arg( [ $_GET ], $wp->request ), 'ep-clear-logged-queries', '_wpnonce' ); // phpcs:ignore WordPress.Security.NonceVerification
122
123 return [
124 [
125 'href' => $href,
126 'label' => $label,
127 ],
128 ];
129 }
130
131 /**
132 * If a nonce is present, clear the logs
133 */
134 protected function maybe_clear_logs() {
135 if ( empty( $_GET['_wpnonce'] ) || ! wp_verify_nonce( $_GET['_wpnonce'], 'ep-clear-logged-queries' ) ) { // phpcs:ignore WordPress.Security.NonceVerification
136 return;
137 }
138
139 $this->query_logger->clear_logs();
140 }
141
142 /**
143 * Given a log, try to find the error and its solution
144 *
145 * @param array $log The log
146 * @return array The error in index 0, solution in index 1
147 */
148 public function analyze_log( $log ) {
149 $error = '';
150
151 if ( ! empty( $log['result']['error'] ) && ! empty( $log['result']['error']['root_cause'][0]['reason'] ) ) {
152 $error = $log['result']['error']['root_cause'][0]['reason'];
153 }
154
155 if ( ! empty( $log['result']['errors'] ) && ! empty( $log['result']['items'] ) && ! empty( $log['result']['items'][0]['index']['error']['reason'] ) ) {
156 $error = $log['result']['items'][0]['index']['error']['reason'];
157 }
158
159 $solution = ( ! empty( $error ) ) ?
160 $this->maybe_suggest_solution_for_es( $error ) :
161 '';
162
163 return [ $error, $solution ];
164 }
165
166 /**
167 * Given an Elasticsearch error, try to suggest a solution
168 *
169 * @param string $error The error
170 * @return string
171 */
172 protected function maybe_suggest_solution_for_es( $error ) {
173 $sync_url = Utils\get_sync_url();
174
175 if ( preg_match( '/no such index \[(.*?)\]/', $error, $matches ) ) {
176 return sprintf(
177 /* translators: 1. Index name; 2. Sync Page URL */
178 __( 'It seems the %1$s index is missing. <a href="%2$s">Delete all data and sync</a> to fix the issue.', 'elasticpress' ),
179 '<code>' . $matches[1] . '</code>',
180 $sync_url
181 );
182 }
183
184 if ( preg_match( '/No mapping found for \[(.*?)\] in order to sort on/', $error, $matches ) ) {
185 return sprintf(
186 /* translators: 1. Index name; 2. Sync Page URL */
187 __( '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' ),
188 '<code>' . $matches[1] . '</code>',
189 $sync_url
190 );
191 }
192
193 /* translators: 1. Field name; 2. Sync Page URL */
194 $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' );
195
196 if ( preg_match( '/Fielddata is disabled on text fields by default. Set fielddata=true on \[(.*?)\]/', $error, $matches ) ) {
197 return sprintf( $field_type_solution, $matches[1], $sync_url );
198 }
199
200 if ( preg_match( '/field \[(.*?)\] is of type \[(.*?)\], but only numeric types are supported./', $error, $matches ) ) {
201 return sprintf( $field_type_solution, $matches[1], $sync_url );
202 }
203
204 if ( preg_match( '/Alternatively, set fielddata=true on \[(.*?)\] in order to load field data by uninverting the inverted index./', $error, $matches ) ) {
205 return sprintf( $field_type_solution, $matches[1], $sync_url );
206 }
207
208 if ( preg_match( '/Limit of total fields \[(.*?)\] in index \[(.*?)\] has been exceeded/', $error, $matches ) ) {
209 return sprintf(
210 /* translators: Elasticsearch or ElasticPress.io; 2. Link to article; 3. Link to article */
211 __( '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' ),
212 Utils\is_epio() ? __( 'ElasticPress.io', 'elasticpress' ) : __( 'Elasticsearch', 'elasticpress' ),
213 'https://elasticpress.zendesk.com/hc/en-us/articles/360051401212-I-get-the-error-Limit-of-total-fields-in-index-has-been-exceeded-',
214 'https://elasticpress.zendesk.com/hc/en-us/articles/360052019111'
215 );
216 }
217
218 // field limit
219
220 if ( Utils\is_epio() ) {
221 return sprintf(
222 /* translators: ElasticPress.io My Account URL */
223 __( 'We did not recognize this error. Please open an ElasticPress.io <a href="%s">support ticket</a> so we can troubleshoot further.', 'elasticpress' ),
224 'https://www.elasticpress.io/my-account/'
225 );
226 }
227
228 return sprintf(
229 /* translators: New GitHub issue URL */
230 __( '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' ),
231 'https://github.com/10up/ElasticPress/issues/new/choose'
232 );
233 }
234 }
235