PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.8.0
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.8.0
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / includes / Utils / Response / ErrorCode.php

ErrorCode.php in Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! 3.8.0, at includes/Utils/Response/ErrorCode.php

297 lines 13.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Utils\Response;
4
5 /**
6 * The canonical machine-code registry (spec 043 / FR-002).
7 *
8 * Every downstream error carries one of these codes. The set here MUST be
9 * identical to the keys of `TEMPLATELY_ERROR_CODES` in
10 * `react-src/utils/errors/errorCodes.ts` — a cross-language sync test fails on
11 * any divergence (spec 043 US4). When you add a code, add it in BOTH files in
12 * the same change.
13 *
14 * Source of truth for the table: `specs/043-core-api-response-contract/contracts/error-codes.md`.
15 */
16 class ErrorCode {
17 // Auth / account.
18 const AUTH_EXPIRED = 'templately_auth_expired';
19 const NOT_VERIFIED = 'templately_not_verified';
20 const ACCOUNT_DISABLED = 'templately_account_disabled';
21 const SITE_DISCONNECTED = 'templately_site_disconnected';
22 const SITE_URL_REQUIRED = 'templately_site_url_required';
23 const AGENT_KEY_NOT_ALLOWED = 'templately_agent_key_not_allowed';
24 const LIMIT_REACHED = 'templately_limit_reached';
25 const INVALID_API_KEY = 'templately_invalid_api_key';
26 const AUTH_STATE_INVALID = 'templately_auth_state_invalid';
27 const AUTH_MISSING_API_KEY = 'templately_auth_missing_api_key';
28 const AUTH_PROVIDER_FAILED = 'templately_auth_provider_failed';
29
30 // Transport / server.
31 const NETWORK_ERROR = 'templately_network_error';
32 const NETWORK_OFFLINE = 'templately_network_offline';
33 const TIMEOUT = 'templately_timeout';
34 const SERVER_ERROR = 'templately_server_error';
35 // A PHP fatal on THIS site (shutdown-captured) — unlike SERVER_ERROR (the
36 // Templately cloud), never retryable: the same request dies the same way.
37 const FATAL_ERROR = 'templately_fatal_error';
38 const SERVER_HTML_RESPONSE = 'templately_server_html_response';
39 const EMPTY_RESPONSE = 'templately_empty_response';
40 const MALFORMED_JSON = 'templately_malformed_json';
41 const MALFORMED_QUERY = 'templately_malformed_query';
42 const RATE_LIMITED = 'templately_rate_limited';
43
44 // Request / client.
45 const INVALID_NONCE = 'templately_invalid_nonce';
46 const FORBIDDEN = 'templately_forbidden';
47 const INVALID_REQUEST = 'templately_invalid_request';
48 const VALIDATION_FAILED = 'templately_validation_failed';
49 const NOT_FOUND = 'templately_not_found';
50
51 // Domain.
52 const SITE_LIMIT_REACHED = 'templately_site_limit_reached';
53 const NO_CREDITS = 'templately_no_credits';
54 const CLOUD_SPACE_FULL = 'templately_cloud_space_full';
55 const PACK_NOT_FOUND = 'templately_pack_not_found';
56 const UPDATE_REQUIRED = 'templately_update_required';
57 const ALREADY_SUBMITTED = 'templately_already_submitted';
58
59 // AI generation (spec 026 taxonomy folded in; `terminal === ! retryable`).
60 const AI_PENDING = 'templately_ai_pending';
61 const AI_NOT_READY = 'templately_ai_not_ready';
62 const AI_INVALID_PROCESS = 'templately_ai_invalid_process';
63 const AI_UNAUTHORIZED = 'templately_ai_unauthorized';
64 const AI_REMOTE_FAILED = 'templately_ai_remote_failed';
65 const AI_EXPIRED = 'templately_ai_expired';
66 const AI_INTERNAL_ERROR = 'templately_ai_internal_error';
67
68 // Expected cancellation (FR-009a) — `info`, never surfaced to the user.
69 const CANCELLED = 'templately_cancelled';
70
71 /**
72 * severity + default retryable per code.
73 *
74 * severity: fatal | error | warning | info — drives the surface (modal /
75 * sticky notice / auto-dismiss notice / silent).
76 */
77 private static $meta = [
78 self::AUTH_EXPIRED => [ 'severity' => 'error', 'retryable' => false, 'status' => 401 ],
79 self::NOT_VERIFIED => [ 'severity' => 'warning', 'retryable' => false, 'status' => 403 ],
80 self::ACCOUNT_DISABLED => [ 'severity' => 'error', 'retryable' => false, 'status' => 403 ],
81 self::SITE_DISCONNECTED => [ 'severity' => 'error', 'retryable' => false, 'status' => 403 ],
82 self::SITE_URL_REQUIRED => [ 'severity' => 'fatal', 'retryable' => false, 'status' => 404 ],
83 self::AGENT_KEY_NOT_ALLOWED => [ 'severity' => 'error', 'retryable' => false, 'status' => 403 ],
84 self::LIMIT_REACHED => [ 'severity' => 'error', 'retryable' => false, 'status' => 409 ],
85 self::INVALID_API_KEY => [ 'severity' => 'error', 'retryable' => false, 'status' => 401 ],
86 self::AUTH_STATE_INVALID => [ 'severity' => 'error', 'retryable' => true, 'status' => 400 ],
87 self::AUTH_MISSING_API_KEY => [ 'severity' => 'error', 'retryable' => true, 'status' => 400 ],
88 self::AUTH_PROVIDER_FAILED => [ 'severity' => 'error', 'retryable' => true, 'status' => 502 ],
89
90 self::NETWORK_ERROR => [ 'severity' => 'error', 'retryable' => true, 'status' => 0 ],
91 self::NETWORK_OFFLINE => [ 'severity' => 'warning', 'retryable' => true, 'status' => 0 ],
92 self::TIMEOUT => [ 'severity' => 'error', 'retryable' => true, 'status' => 0 ],
93 self::SERVER_ERROR => [ 'severity' => 'error', 'retryable' => true, 'status' => 500 ],
94 self::FATAL_ERROR => [ 'severity' => 'fatal', 'retryable' => false, 'status' => 500 ],
95 self::SERVER_HTML_RESPONSE => [ 'severity' => 'error', 'retryable' => true, 'status' => 502 ],
96 self::EMPTY_RESPONSE => [ 'severity' => 'error', 'retryable' => true, 'status' => 502 ],
97 self::MALFORMED_JSON => [ 'severity' => 'error', 'retryable' => false, 'status' => 502 ],
98 self::MALFORMED_QUERY => [ 'severity' => 'fatal', 'retryable' => false, 'status' => 500 ],
99 self::RATE_LIMITED => [ 'severity' => 'warning', 'retryable' => true, 'status' => 429 ],
100
101 // Retryable: a nonce expires with the page, and the client's existing
102 // `retry_again` path reloads assets and repeats the call — which succeeds.
103 self::INVALID_NONCE => [ 'severity' => 'error', 'retryable' => true, 'status' => 403 ],
104 self::FORBIDDEN => [ 'severity' => 'error', 'retryable' => false, 'status' => 403 ],
105 self::INVALID_REQUEST => [ 'severity' => 'error', 'retryable' => false, 'status' => 400 ],
106 self::VALIDATION_FAILED => [ 'severity' => 'error', 'retryable' => false, 'status' => 422 ],
107 self::NOT_FOUND => [ 'severity' => 'error', 'retryable' => false, 'status' => 404 ],
108
109 self::SITE_LIMIT_REACHED => [ 'severity' => 'error', 'retryable' => false, 'status' => 403 ],
110 self::NO_CREDITS => [ 'severity' => 'warning', 'retryable' => false, 'status' => 402 ],
111 self::CLOUD_SPACE_FULL => [ 'severity' => 'error', 'retryable' => false, 'status' => 507 ],
112 self::PACK_NOT_FOUND => [ 'severity' => 'error', 'retryable' => false, 'status' => 404 ],
113 self::UPDATE_REQUIRED => [ 'severity' => 'error', 'retryable' => false, 'status' => 426 ],
114 self::ALREADY_SUBMITTED => [ 'severity' => 'info', 'retryable' => false, 'status' => 200 ],
115
116 self::AI_PENDING => [ 'severity' => 'info', 'retryable' => true, 'status' => 202 ],
117 self::AI_NOT_READY => [ 'severity' => 'info', 'retryable' => true, 'status' => 202 ],
118 self::AI_INVALID_PROCESS => [ 'severity' => 'error', 'retryable' => false, 'status' => 400 ],
119 self::AI_UNAUTHORIZED => [ 'severity' => 'error', 'retryable' => false, 'status' => 401 ],
120 self::AI_REMOTE_FAILED => [ 'severity' => 'error', 'retryable' => false, 'status' => 502 ],
121 self::AI_EXPIRED => [ 'severity' => 'error', 'retryable' => false, 'status' => 410 ],
122 // Retryable: a transient manifest/session race produced this code and a
123 // single occurrence was killing a paid, still-running generation. The
124 // client poll is attempt-capped, so retrying a permanent internal error
125 // is bounded, while a transient one now recovers.
126 self::AI_INTERNAL_ERROR => [ 'severity' => 'error', 'retryable' => true, 'status' => 500 ],
127
128 self::CANCELLED => [ 'severity' => 'info', 'retryable' => false, 'status' => 0 ],
129 ];
130
131 /**
132 * Every registered code, in declaration order.
133 *
134 * @return string[]
135 */
136 public static function all() {
137 return array_keys( self::$meta );
138 }
139
140 /**
141 * Whether $code is a member of the registry.
142 *
143 * @param string $code
144 * @return bool
145 */
146 public static function exists( $code ) {
147 return is_string( $code ) && isset( self::$meta[ $code ] );
148 }
149
150 /**
151 * severity / retryable / status defaults for a code.
152 *
153 * Unknown codes degrade to a generic error rather than throwing — the
154 * normalizer must never fail on an unexpected upstream value (FR-006).
155 *
156 * @param string $code
157 * @return array{severity:string,retryable:bool,status:int}
158 */
159 public static function meta( $code ) {
160 if ( self::exists( $code ) ) {
161 return self::$meta[ $code ];
162 }
163
164 return [ 'severity' => 'error', 'retryable' => false, 'status' => 500 ];
165 }
166
167 /**
168 * @param string $code
169 * @return string one of fatal|error|warning|info
170 */
171 public static function severity( $code ) {
172 $meta = self::meta( $code );
173 return $meta['severity'];
174 }
175
176 /**
177 * @param string $code
178 * @return bool
179 */
180 public static function retryable( $code ) {
181 $meta = self::meta( $code );
182 return $meta['retryable'];
183 }
184
185 /**
186 * The default HTTP-equivalent status for a code.
187 *
188 * @param string $code
189 * @return int
190 */
191 public static function status( $code ) {
192 $meta = self::meta( $code );
193 return $meta['status'];
194 }
195
196 /**
197 * The default user-facing sentence for a code.
198 *
199 * Lives here, not in the normalizer, because more than one producer needs it:
200 * the normalizer when upstream sent no usable message, and
201 * `TemplatelyException::get_user_message()` when the thrown error carries only
202 * an internal one. Two copies of this wording is two things to keep in step.
203 *
204 * Deliberately generic — a message the user can act on, never a description of
205 * what went wrong internally (FR-008).
206 *
207 * @param string $code
208 * @return string
209 */
210 public static function default_message( $code ) {
211 switch ( $code ) {
212 case self::AUTH_EXPIRED:
213 case self::INVALID_API_KEY:
214 return __( 'Your session has expired. Please log in again.', 'templately' );
215 case self::NOT_VERIFIED:
216 return __( 'Please verify your email address to continue.', 'templately' );
217 case self::ACCOUNT_DISABLED:
218 return __( 'This account is not active. Please contact support.', 'templately' );
219 case self::SITE_DISCONNECTED:
220 return __( 'This site is not connected to Templately. Please log in again.', 'templately' );
221 case self::SITE_URL_REQUIRED:
222 return __( 'Templately could not identify this site. Please contact support.', 'templately' );
223 case self::LIMIT_REACHED:
224 return __( 'You have reached your plan limit.', 'templately' );
225 case self::NOT_FOUND:
226 case self::PACK_NOT_FOUND:
227 return __( 'The requested resource was not found.', 'templately' );
228 case self::RATE_LIMITED:
229 return __( 'Too many requests. Please wait a moment and try again.', 'templately' );
230 case self::VALIDATION_FAILED:
231 return __( 'Validation failed.', 'templately' );
232 case self::NETWORK_OFFLINE:
233 return __( 'You appear to be offline. Check your connection and try again.', 'templately' );
234 case self::NETWORK_ERROR:
235 return __( 'Could not reach the Templately server. Please check your connection and try again.', 'templately' );
236 case self::TIMEOUT:
237 return __( 'The request timed out. Please try again.', 'templately' );
238 case self::MALFORMED_QUERY:
239 return __( 'Templately could not complete this request due to an internal error. Please update the plugin or contact support.', 'templately' );
240 case self::ALREADY_SUBMITTED:
241 return __( 'This has already been submitted. Thank you!', 'templately' );
242 case self::CANCELLED:
243 return __( 'Request cancelled.', 'templately' );
244 case self::INVALID_NONCE:
245 return __( 'This page has expired. Please refresh and try again.', 'templately' );
246 case self::FORBIDDEN:
247 return __( 'You do not have permission to do this.', 'templately' );
248 case self::SERVER_ERROR:
249 return __( 'Something went wrong on the Templately server. Please try again in a moment.', 'templately' );
250 case self::FATAL_ERROR:
251 return __( 'A critical error occurred on your website while processing this request. Details were saved to the Templately log — please contact support if it keeps happening.', 'templately' );
252 default:
253 return __( 'The request could not be completed.', 'templately' );
254 }
255 }
256
257 /**
258 * spec 026's AI generation slug → this registry (043 FR-014).
259 *
260 * The AI endpoints shipped their own four-field envelope
261 * (`{success, terminal, code, message}`) before this contract existed, and
262 * the poller keys off `terminal` only. Folding it in ADDITIVELY — rather
263 * than rewriting it — keeps that poller byte-stable while giving the AI
264 * failures the same machine codes as everything else.
265 *
266 * The two taxonomies line up exactly: `terminal === ! retryable`. Only
267 * `pending`/`not_ready` are retryable, and only those two are non-terminal.
268 * `ai_terminal_matches_retryable()` asserts that rather than assuming it.
269 */
270 private static $ai_code_map = [
271 'pending' => self::AI_PENDING,
272 'not_ready' => self::AI_NOT_READY,
273 'invalid_process' => self::AI_INVALID_PROCESS,
274 'unauthorized' => self::AI_UNAUTHORIZED,
275 'remote_failed' => self::AI_REMOTE_FAILED,
276 'expired' => self::AI_EXPIRED,
277 'internal_error' => self::AI_INTERNAL_ERROR,
278 ];
279
280 /**
281 * @param string $ai_code One of spec 026's taxonomy slugs.
282 * @return string|null the registry code, or null for `ok` (a success, not an error).
283 */
284 public static function from_ai_code( $ai_code ) {
285 return isset( self::$ai_code_map[ $ai_code ] ) ? self::$ai_code_map[ $ai_code ] : null;
286 }
287
288 /**
289 * Every AI slug this registry knows about.
290 *
291 * @return string[]
292 */
293 public static function ai_codes() {
294 return array_keys( self::$ai_code_map );
295 }
296 }
297