PluginProbe
WDesignKit – AI Templates, Widget Builder & MCP Workflow / 2.7.0
WDesignKit – AI Templates, Widget Builder & MCP Workflow v2.7.0
2.7.0 2.6.6 2.6.5 2.6.4 2.6.3 2.6.2 2.6.1 2.6.0 2.5.5 2.5.4 2.5.3 2.5.2 2.5.1 2.5.0 2.4.0 2.3.3 2.3.2 2.3.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 All 129 releases
wdesignkit / includes / admin / import / class-wdkit-import-errors.php

class-wdkit-import-errors.php in WDesignKit – AI Templates, Widget Builder & MCP Workflow 2.7.0, at includes/admin/import/class-wdkit-import-errors.php

283 lines 7.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Error classification for importer operations.
4 *
5 * The browser importer has its own ad-hoc retry handling and is deliberately left alone —
6 * nothing here changes how it behaves. These classes exist so the PHP runner
7 * (class-wdkit-import-runner.php) can decide what to do with a failure instead of
8 * treating every problem as fatal:
9 *
10 * Retryable - transient. Same call again later may well succeed (network, lock,
11 * rate limit, cloud 5xx).
12 * Skippable - this one unit failed but the import as a whole is still worth
13 * finishing (one image of forty, one optional page).
14 * NonRetryable - the run cannot succeed as configured (not logged in, no writable
15 * uploads dir, kit not found). Stop and report.
16 * Unknown - anything not classified. Treated as non-retryable by policy, because
17 * silently retrying an unknown failure is how you double-charge someone.
18 *
19 * @package Wdesignkit
20 * @since 2.6.5
21 */
22
23 if ( ! defined( 'ABSPATH' ) ) {
24 exit;
25 }
26
27 if ( ! class_exists( 'Wdkit_Import_Exception' ) ) {
28
29 /**
30 * Base class for every importer failure raised by the PHP services.
31 */
32 class Wdkit_Import_Exception extends Exception {
33
34 /**
35 * Machine-readable failure code, e.g. 'cloud_fetch_failed'.
36 *
37 * @var string
38 */
39 protected $error_code = 'wdkit_import_error';
40
41 /**
42 * The step that was running when this was raised.
43 *
44 * @var string
45 */
46 protected $step = '';
47
48 /**
49 * Free-form context for the log/session record. Never rendered to a visitor.
50 *
51 * @var array
52 */
53 protected $context = array();
54
55 /**
56 * @param string $message Human-readable message.
57 * @param string $error_code Machine code.
58 * @param string $step Step name.
59 * @param array $context Extra detail.
60 */
61 public function __construct( $message = '', $error_code = '', $step = '', $context = array() ) {
62 parent::__construct( $message );
63
64 if ( ! empty( $error_code ) ) {
65 $this->error_code = (string) $error_code;
66 }
67
68 $this->step = (string) $step;
69 $this->context = is_array( $context ) ? $context : array();
70 }
71
72 /** @return string */
73 public function get_error_code() {
74 return $this->error_code;
75 }
76
77 /** @return string */
78 public function get_step() {
79 return $this->step;
80 }
81
82 /** @return array */
83 public function get_context() {
84 return $this->context;
85 }
86
87 /**
88 * Should the runner try this step again?
89 *
90 * @return bool
91 */
92 public function is_retryable() {
93 return false;
94 }
95
96 /**
97 * Can the runner carry on to the next unit / step despite this?
98 *
99 * @return bool
100 */
101 public function is_skippable() {
102 return false;
103 }
104
105 /**
106 * Shape used by the session record and any future status endpoint.
107 *
108 * @return array
109 */
110 public function to_array() {
111 return array(
112 'code' => $this->get_error_code(),
113 'message' => $this->getMessage(),
114 'step' => $this->get_step(),
115 'class' => static::classification(),
116 'retryable' => $this->is_retryable(),
117 'skippable' => $this->is_skippable(),
118 'context' => $this->get_context(),
119 );
120 }
121
122 /**
123 * Short label for this class of failure.
124 *
125 * @return string
126 */
127 public static function classification() {
128 return 'unknown';
129 }
130 }
131 }
132
133 if ( ! class_exists( 'Wdkit_Import_Retryable_Exception' ) ) {
134
135 /**
136 * Transient failure — worth another attempt.
137 */
138 class Wdkit_Import_Retryable_Exception extends Wdkit_Import_Exception {
139
140 public function is_retryable() {
141 return true;
142 }
143
144 public static function classification() {
145 return 'retryable';
146 }
147 }
148 }
149
150 if ( ! class_exists( 'Wdkit_Import_Skippable_Exception' ) ) {
151
152 /**
153 * One unit failed; the run should continue without it.
154 */
155 class Wdkit_Import_Skippable_Exception extends Wdkit_Import_Exception {
156
157 public function is_skippable() {
158 return true;
159 }
160
161 public static function classification() {
162 return 'skippable';
163 }
164 }
165 }
166
167 if ( ! class_exists( 'Wdkit_Import_Non_Retryable_Exception' ) ) {
168
169 /**
170 * The run cannot succeed as configured. Stop.
171 */
172 class Wdkit_Import_Non_Retryable_Exception extends Wdkit_Import_Exception {
173
174 public static function classification() {
175 return 'non_retryable';
176 }
177 }
178 }
179
180 if ( ! class_exists( 'Wdkit_Import_Errors' ) ) {
181
182 /**
183 * Helpers for turning things that are not exceptions (WP_Error, cloud responses)
184 * into a classified importer failure.
185 */
186 class Wdkit_Import_Errors {
187
188 /**
189 * WP_Error codes that are worth retrying rather than surfacing as fatal.
190 *
191 * @var string[]
192 */
193 private static $retryable_codes = array(
194 'http_request_failed',
195 'connect_timeout',
196 'timeout',
197 'http_request_timeout',
198 'too_many_requests',
199 'wdkit_cloud_unavailable',
200 );
201
202 /**
203 * Classify a WP_Error into an importer exception without throwing it.
204 *
205 * @param WP_Error $error WordPress error.
206 * @param string $step Step name.
207 * @return Wdkit_Import_Exception
208 */
209 public static function from_wp_error( $error, $step = '' ) {
210 $code = is_wp_error( $error ) ? $error->get_error_code() : 'unknown_error';
211 $message = is_wp_error( $error ) ? $error->get_error_message() : __( 'Unknown error.', 'wdesignkit' );
212
213 if ( in_array( $code, self::$retryable_codes, true ) ) {
214 return new Wdkit_Import_Retryable_Exception( $message, $code, $step );
215 }
216
217 return new Wdkit_Import_Non_Retryable_Exception( $message, $code, $step );
218 }
219
220 /**
221 * Classify an HTTP status from the cloud.
222 *
223 * 5xx and 429 are transient; 4xx means the request itself is wrong.
224 *
225 * @param int $status HTTP status code.
226 * @param string $message Message to carry.
227 * @param string $step Step name.
228 * @return Wdkit_Import_Exception
229 */
230 public static function from_http_status( $status, $message = '', $step = '' ) {
231 $status = (int) $status;
232 $message = '' !== $message ? $message : sprintf( /* translators: %d: HTTP status code. */ __( 'Cloud returned status %d.', 'wdesignkit' ), $status );
233
234 if ( 429 === $status || $status >= 500 ) {
235 return new Wdkit_Import_Retryable_Exception( $message, 'http_' . $status, $step );
236 }
237
238 return new Wdkit_Import_Non_Retryable_Exception( $message, 'http_' . $status, $step );
239 }
240
241 /**
242 * Normalise anything thrown during a step into the session's error shape.
243 *
244 * A plain Exception or Error from deep inside WordPress is classified 'unknown'
245 * and treated as non-retryable — see the note at the top of this file.
246 *
247 * @param Throwable $e The thrown thing.
248 * @param string $step Step name.
249 * @return array
250 */
251 public static function to_record( $e, $step = '' ) {
252 if ( $e instanceof Wdkit_Import_Exception ) {
253 $record = $e->to_array();
254
255 if ( '' === $record['step'] ) {
256 $record['step'] = $step;
257 }
258
259 return $record;
260 }
261
262 return array(
263 'code' => 'unhandled_exception',
264 'message' => $e->getMessage(),
265 'step' => $step,
266 'class' => 'unknown',
267 'retryable' => false,
268 'skippable' => false,
269 'context' => array(
270 'type' => get_class( $e ),
271 /* Where it was thrown. Without this an unhandled PHP error reaches the
272 * log as a bare message - "Cannot use object of type WP_Error as array"
273 * and nothing else - which says what went wrong but not in which of the
274 * content stage's several dozen call sites. */
275 'file' => $e->getFile(),
276 'line' => $e->getLine(),
277 'trace' => array_slice( explode( "\n", $e->getTraceAsString() ), 0, 6 ),
278 ),
279 );
280 }
281 }
282 }
283