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
← All changes | includes/API/API.php +119 -30 3.7.13.8.0 View file →
@@ -10,13 +10,15 @@
10 10 use WP_REST_Response;
11 11 use Templately\Utils\Base;
12 12 use Templately\Utils\Http;
13 13 use Templately\Utils\Plan;
14 -use Templately\Core\Module;
14 +use Templately\Core\Platform_Registry;
15 15 use function call_user_func;
16 16
17 17 use Templately\Utils\Helper;
18 18 use Templately\Utils\Options;
19 +use Templately\Modules\Auth\REST\Login;
20 +use Templately\Utils\Response\Envelope;
19 21 use function register_rest_route;
20 22
21 23 /**
22 24 * @method Http http()
@@ -41,9 +43,9 @@
41 43 public function __construct() {
42 44 /**
43 45 * Registering as a submodule of the API module
44 46 */
45 - Module::get_instance()->add( (object) [
47 + Platform_Registry::get_instance()->add( (object) [
46 48 'object' => $this
47 49 ], 'API' );
48 50 }
49 51
@@ -107,21 +109,55 @@
107 109 * @return false|mixed
108 110 */
109 111 public function get_param( $param, $default = '', $sanitizer = 'sanitize_text_field' ) {
110 112 $_value = $this->request->get_param( $param );
111 - if ( ! empty( $_value ) ) {
112 - if( is_callable($sanitizer) && ! is_array( $_value ) ) {
113 - return call_user_func_array( $sanitizer, [ $_value ] );
114 - } elseif ( is_array( $_value ) && is_callable($sanitizer) ) {
115 - return array_map( $sanitizer, $_value );
116 - }
117 - return $_value;
113 +
114 + // An ABSENT param — or an explicitly empty string — falls back to the default.
115 + //
116 + // This used to guard with `! empty()`, which is ALSO false for `0`, `'0'`,
117 + // `false` and `[]`. Those are legitimate values a caller may have sent
118 + // deliberately: a page number of 0, an explicit `false` flag, an empty array
119 + // meaning "clear the selection". They were silently replaced by the default,
120 + // and the endpoint could not tell "not sent" from "sent as zero".
121 + //
122 + // Empty string stays a fallback on purpose — callers like
123 + // `get_param( 'search', '' )` rely on it, and "" carries no information a
124 + // missing param does not.
125 + if ( null === $_value || '' === $_value ) {
126 + return $default;
118 127 }
119 128
120 - return $default;
129 + return self::sanitize_recursive( $_value, $sanitizer );
121 130 }
122 131
123 132 /**
133 + * Apply a sanitizer through nested arrays.
134 + *
135 + * `array_map( $sanitizer, $value )` only reached the FIRST level, so a nested
136 + * array left its inner values untouched.
137 + *
138 + * @param mixed $value
139 + * @param callable|string $sanitizer
140 + * @return mixed
141 + */
142 + private static function sanitize_recursive( $value, $sanitizer ) {
143 + if ( ! is_callable( $sanitizer ) ) {
144 + return $value;
145 + }
146 +
147 + if ( is_array( $value ) ) {
148 + return array_map(
149 + function ( $item ) use ( $sanitizer ) {
150 + return self::sanitize_recursive( $item, $sanitizer );
151 + },
152 + $value
153 + );
154 + }
155 +
156 + return call_user_func( $sanitizer, $value );
157 + }
158 +
159 + /**
124 160 * @param $request WP_REST_Request for getting all route request in time.
125 161 *
126 162 * @return WP_Error|boolean
127 163 */
@@ -173,20 +209,19 @@
173 209 if( ! empty( $endpoint ) ) {
174 210 $_additional_data['endpoint'] = $endpoint;
175 211 }
176 212
177 - // Delete user logged meta data
178 - $this->utils('options')
179 - ->remove('user')
180 - ->remove('favourites')
181 - ->remove('reviews')
182 - ->remove('cloud_activity')
183 - ->remove('api_key');
213 + // One definition of "logged out" (043 / PRD PHP-1). This used to remove its
214 + // own list of FIVE keys while Login removed EIGHT, so a session expiring
215 + // through this path left `global_login`, `total_download_counts` and
216 + // `templates_in_clouds` behind — stale data from the previous account, in a
217 + // state that was neither logged in nor logged out.
218 + Login::force_logout();
184 219
185 - if( $this->utils('options')->who_am_i() === 'global' ) {
186 - $this->utils('options')->remove_global_login();
187 - }
188 -
220 + // The code stays `invalid_api_key`: it is an outward contract several
221 + // callers still branch on, and RestEnvelope already maps it to
222 + // AUTH_EXPIRED on the wire. Changing it here would buy nothing and break
223 + // those callers.
189 224 return new WP_Error( 'invalid_api_key', $message, $_additional_data );
190 225 }
191 226
192 227 public function get( $endpoint, $callback, $args = [] ){
@@ -232,21 +267,22 @@
232 267 return new WP_REST_Response( $data, 200 );
233 268 }
234 269
235 270 /**
236 - * Enhanced success response wrapper that automatically adds success flag and wraps data
271 + * The canonical success envelope (spec 043 / FR-001).
237 272 *
238 - * @param $data mixed The data to be wrapped in the response
273 + * Replaces the former `successWithData()`, which hand-rolled the same
274 + * `{ success, data }` shape in one place while the rest of the plugin
275 + * invented its own. The shape now comes from `Envelope`, which is what the
276 + * schema and both language halves are tested against.
239 277 *
278 + * @param mixed $data The payload.
279 + * @param array $meta Optional metadata (pagination, timings, …).
280 + *
240 281 * @return WP_REST_Response
241 282 */
242 - public function successWithData( $data ) {
243 - $enhanced_response = [
244 - 'success' => true,
245 - 'data' => $data
246 - ];
247 -
248 - return $this->success( $enhanced_response );
283 + public function envelope( $data, $meta = [] ) {
284 + return $this->success( Envelope::success( $data, $meta ) );
249 285 }
250 286 /**
251 287 * @param $error_code string
252 288 * @param $error_message string|array
@@ -265,6 +301,59 @@
265 301 * @return int
266 302 */
267 303 public function get_plan( $plan = 'all' ) {
268 304 return Plan::get( $plan );
305 + }
306 +
307 + /**
308 + * 026/FR-003/D5 — the standard AI-generation response envelope.
309 + *
310 + * Every generation endpoint emits `{ success, terminal, code, message }`. The
311 + * client's retry logic keys off `terminal` ONLY: `terminal:true` ⇒ stop,
312 + * `terminal:false` ⇒ keep polling. `code` is for diagnostics/messages.
313 + *
314 + * The envelope is ADDITIVE — the existing payload ($extra: process_id,
315 + * templates, is_local_site, status, …) is merged in, so current consumers
316 + * keep reading their fields while gaining the uniform terminal/code signal.
317 + *
318 + * Code taxonomy:
319 + * ok terminal, success — content ready / action succeeded
320 + * pending retryable, success — accepted, still working
321 + * not_ready retryable, success — content not on disk yet; keep polling
322 + * invalid_process terminal, failure — unknown/expired process id
323 + * unauthorized terminal, failure — api_key/user mismatch
324 + * remote_failed terminal, failure — remote AI service terminal failure
325 + * expired terminal, failure — process/session past retention
326 + * internal_error terminal, failure — unexpected server error
327 + *
328 + * @param string $code one of the taxonomy slugs
329 + * @param string $message human, i18n
330 + * @param array $extra existing payload fields to merge (envelope keys win)
331 + * @return array
332 + */
333 + public static function ai_envelope( $code, $message = '', $extra = [] ) {
334 + return array_merge( (array) $extra, [
335 + 'success' => self::ai_code_is_success( $code ),
336 + 'terminal' => self::ai_code_is_terminal( $code ),
337 + 'code' => $code,
338 + 'message' => $message,
339 + ] );
340 + }
341 +
342 + /**
343 + * Whether the client should STOP retrying for this code (FR-003).
344 + * `pending` / `not_ready` are the polling states. `internal_error` is also
345 + * non-terminal (mirroring AI_INTERNAL_ERROR's retryable=true in the
346 + * registry — the fold invariant is terminal === !retryable): transient
347 + * manifest/session races produce it, and a single occurrence was killing a
348 + * paid, still-running generation. The client poll is attempt-capped, so a
349 + * genuinely permanent internal error still terminates, just later.
350 + */
351 + public static function ai_code_is_terminal( $code ) {
352 + return ! in_array( $code, [ 'pending', 'not_ready', 'internal_error' ], true );
353 + }
354 +
355 + /** Whether this code represents a non-error response (FR-003). */
356 + public static function ai_code_is_success( $code ) {
357 + return in_array( $code, [ 'ok', 'pending', 'not_ready' ], true );
269 358 }
270 359 }