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 / API / API.php

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

359 lines 10.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\API;
4
5 use WP_Error;
6 use WP_REST_Server;
7 use WP_REST_Request;
8 use function ucfirst;
9
10 use WP_REST_Response;
11 use Templately\Utils\Base;
12 use Templately\Utils\Http;
13 use Templately\Utils\Plan;
14 use Templately\Core\Platform_Registry;
15 use function call_user_func;
16
17 use Templately\Utils\Helper;
18 use Templately\Utils\Options;
19 use Templately\Modules\Auth\REST\Login;
20 use Templately\Utils\Response\Envelope;
21 use function register_rest_route;
22
23 /**
24 * @method Http http()
25 * @method Options options()
26 * @method Options|Http|Helper utils( string $name )
27 */
28 abstract class API extends Base {
29 protected $api_key;
30 protected $request;
31
32 private static $allowed_classes = [
33 'utils' => [
34 'options',
35 'http',
36 'helper'
37 ],
38 'api' => [
39 'dependencies',
40 ],
41 ];
42
43 public function __construct() {
44 /**
45 * Registering as a submodule of the API module
46 */
47 Platform_Registry::get_instance()->add( (object) [
48 'object' => $this
49 ], 'API' );
50 }
51
52 private static function is_allowed( $type, $class ){
53 if( ! array_key_exists( $type, self::$allowed_classes ) ) {
54 return false;
55 }
56 if( ! class_exists( '\\Templately\\'. ucfirst( $type ) .'\\' . ucfirst( $class ) ) ) {
57 return false;
58 }
59 return true;
60 }
61
62 /**
63 * @param $type
64 * @param $parameters
65 *
66 * @return mixed|void
67 */
68 public static function __callStatic( $type, $parameters = [] ){
69 return ( new static )->call_user_func( $type, $parameters );
70 }
71
72 /**
73 * @param $type
74 * @param $parameters
75 *
76 * @return mixed|void
77 */
78 public function __call( $type, $parameters = [] ){
79 return $this->call_user_func( $type, $parameters );
80 }
81
82 /**
83 * @param $type
84 * @param $parameters
85 *
86 * @return mixed|void
87 */
88 protected function call_user_func( $type, $parameters = [] ) {
89 if( $type === 'http' || $type === 'options' ) {
90 $parameters[0] = $type;
91 $type = 'utils';
92 }
93 if( ! empty ( $parameters[0] ) && self::is_allowed( $type, $parameters[0] ) ) {
94 return call_user_func( [ '\\Templately\\'. ucfirst( $type ) .'\\' . ucfirst( $parameters[0] ), 'get_instance' ] );
95 }
96
97 Helper::trigger_error( $this );
98 }
99
100 protected function get_namespace( $endpoint = '' ) {
101 return '/' . TEMPLATELY_API_NAMESPACE . ( ! empty( $endpoint ) ? "/$endpoint" : '' );
102 }
103
104 /**
105 * @param string $param
106 * @param mixed $default
107 * @param string $sanitizer
108 *
109 * @return false|mixed
110 */
111 public function get_param( $param, $default = '', $sanitizer = 'sanitize_text_field' ) {
112 $_value = $this->request->get_param( $param );
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;
127 }
128
129 return self::sanitize_recursive( $_value, $sanitizer );
130 }
131
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 /**
160 * @param $request WP_REST_Request for getting all route request in time.
161 *
162 * @return WP_Error|boolean
163 */
164 public function _permission_check( WP_REST_Request $request ) {
165 $this->request = $request;
166 $this->api_key = $this->utils('options')->get( 'api_key' );
167 if(!current_user_can('delete_posts')){
168 return false;
169 }
170
171 add_filter('wp_redirect', '__return_false', 999);
172
173 return $this->permission_check( $request );
174 }
175
176 /**
177 * @param $request WP_REST_Request for getting all route request in time.
178 *
179 * @return WP_Error|boolean
180 */
181 public function permission_check( WP_REST_Request $request ) {
182 $this->request = $request;
183 $this->api_key = $this->utils('options')->get( 'api_key' );
184
185
186 if ( ! empty( $this->api_key ) ) {
187 return true;
188 }
189
190 $_route = $request->get_route();
191 return $this->permission_error( '', $_route );
192 }
193
194 /**
195 * @param $message
196 * @param $endpoint
197 *
198 * @return WP_Error
199 */
200 protected function permission_error( $message, $endpoint = '') {
201 if( empty( $message ) ) {
202 $message = __( 'Your session has expired. Please log in again.', 'templately' );
203 }
204
205 $_additional_data = [
206 'status' => rest_authorization_required_code(),
207 ];
208
209 if( ! empty( $endpoint ) ) {
210 $_additional_data['endpoint'] = $endpoint;
211 }
212
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();
219
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.
224 return new WP_Error( 'invalid_api_key', $message, $_additional_data );
225 }
226
227 public function get( $endpoint, $callback, $args = [] ){
228 return $this->register_endpoint( $endpoint, $callback, $args, WP_REST_Server::READABLE );
229 }
230 public function post( $endpoint, $callback, $args = [] ){
231 return $this->register_endpoint( $endpoint, $callback, $args );
232 }
233
234 public function register_endpoint( $endpoint, $callback, $args = [], $methods = WP_REST_Server::CREATABLE ) {
235 return register_rest_route(
236 TEMPLATELY_API_NAMESPACE,
237 $endpoint,
238 [
239 'methods' => $methods,
240 'callback' => $callback,
241 'permission_callback' => [ $this, '_permission_check' ],
242 'args' => $args,
243 ]
244 );
245 }
246
247 public function response( $response, $endpoint, $status = 500, $additional_data = [] ) {
248 if ( $response instanceof WP_Error ) {
249 return $this->error(
250 $response->get_error_code(),
251 $response->get_error_message(),
252 $endpoint,
253 $status,
254 $additional_data
255 );
256 }
257
258 return $this->success( $response );
259 }
260
261 /**
262 * @param $data
263 *
264 * @return WP_REST_Response
265 */
266 public function success( $data ) {
267 return new WP_REST_Response( $data, 200 );
268 }
269
270 /**
271 * The canonical success envelope (spec 043 / FR-001).
272 *
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.
277 *
278 * @param mixed $data The payload.
279 * @param array $meta Optional metadata (pagination, timings, …).
280 *
281 * @return WP_REST_Response
282 */
283 public function envelope( $data, $meta = [] ) {
284 return $this->success( Envelope::success( $data, $meta ) );
285 }
286 /**
287 * @param $error_code string
288 * @param $error_message string|array
289 * @param $endpoint string
290 * @param $status int
291 * @param $additional_data array
292 *
293 * @return WP_Error
294 */
295 public function error( $error_code, $error_message, $endpoint = '', $status = 500, $additional_data = [] ) {
296 return Helper::error( $error_code, $error_message, $endpoint, $status, $additional_data );
297 }
298 /**
299 * @param $plan
300 *
301 * @return int
302 */
303 public function get_plan( $plan = 'all' ) {
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 );
358 }
359 }