PluginProbe
ElasticPress / 4.6.0
ElasticPress v4.6.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 / StatusReport / FailedQueries.php

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

227 lines 6.8 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( sanitize_key( $_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 = Utils\get_elasticsearch_error_reason( $log );
150
151 $solution = ( ! empty( $error ) ) ?
152 $this->maybe_suggest_solution_for_es( $error ) :
153 '';
154
155 return [ $error, $solution ];
156 }
157
158 /**
159 * Given an Elasticsearch error, try to suggest a solution
160 *
161 * @param string $error The error
162 * @return string
163 */
164 protected function maybe_suggest_solution_for_es( $error ) {
165 $sync_url = Utils\get_sync_url();
166
167 if ( preg_match( '/no such index \[(.*?)\]/', $error, $matches ) ) {
168 return sprintf(
169 /* translators: 1. Index name; 2. Sync Page URL */
170 __( 'It seems the %1$s index is missing. <a href="%2$s">Delete all data and sync</a> to fix the issue.', 'elasticpress' ),
171 '<code>' . $matches[1] . '</code>',
172 $sync_url
173 );
174 }
175
176 if ( preg_match( '/No mapping found for \[(.*?)\] in order to sort on/', $error, $matches ) ) {
177 return sprintf(
178 /* translators: 1. Index name; 2. Sync Page URL */
179 __( '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' ),
180 '<code>' . $matches[1] . '</code>',
181 $sync_url
182 );
183 }
184
185 /* translators: 1. Field name; 2. Sync Page URL */
186 $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' );
187
188 if ( preg_match( '/Fielddata is disabled on text fields by default. Set fielddata=true on \[(.*?)\]/', $error, $matches ) ) {
189 return sprintf( $field_type_solution, $matches[1], $sync_url );
190 }
191
192 if ( preg_match( '/field \[(.*?)\] is of type \[(.*?)\], but only numeric types are supported./', $error, $matches ) ) {
193 return sprintf( $field_type_solution, $matches[1], $sync_url );
194 }
195
196 if ( preg_match( '/Alternatively, set fielddata=true on \[(.*?)\] in order to load field data by uninverting the inverted index./', $error, $matches ) ) {
197 return sprintf( $field_type_solution, $matches[1], $sync_url );
198 }
199
200 if ( preg_match( '/Limit of total fields \[(.*?)\] in index \[(.*?)\] has been exceeded/', $error, $matches ) ) {
201 return sprintf(
202 /* translators: Elasticsearch or ElasticPress.io; 2. Link to article; 3. Link to article */
203 __( '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' ),
204 Utils\is_epio() ? __( 'ElasticPress.io', 'elasticpress' ) : __( 'Elasticsearch', 'elasticpress' ),
205 'https://elasticpress.zendesk.com/hc/en-us/articles/360051401212-I-get-the-error-Limit-of-total-fields-in-index-has-been-exceeded-',
206 'https://elasticpress.zendesk.com/hc/en-us/articles/360052019111'
207 );
208 }
209
210 // field limit
211
212 if ( Utils\is_epio() ) {
213 return sprintf(
214 /* translators: ElasticPress.io My Account URL */
215 __( 'We did not recognize this error. Please open an ElasticPress.io <a href="%s">support ticket</a> so we can troubleshoot further.', 'elasticpress' ),
216 'https://www.elasticpress.io/my-account/'
217 );
218 }
219
220 return sprintf(
221 /* translators: New GitHub issue URL */
222 __( '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' ),
223 'https://github.com/10up/ElasticPress/issues/new/choose'
224 );
225 }
226 }
227