PluginProbe
ElasticPress / 4.7.2
ElasticPress v4.7.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.7.2, at includes/classes/StatusReport/FailedQueries.php

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