PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.7.5
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.7.5
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 3.1.10 All 111 releases
templately / includes / API / LogoGeneration.php

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

589 lines 19.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Templately Logo Generation API
5 *
6 * @package Templately
7 * @since 3.4.0
8 */
9
10 namespace Templately\API;
11
12 use Templately\Utils\Helper;
13 use WP_REST_Request;
14 use Templately\Core\Importer\Utils\Utils;
15 use Templately\Core\Importer\Utils\AIUtils;
16
17 class LogoGeneration extends API {
18 private $endpoint = 'logo-generation';
19
20 /**
21 * LogoGeneration constructor.
22 */
23 public function __construct() {
24 parent::__construct();
25 }
26
27 public function _permission_check( WP_REST_Request $request ) {
28 $this->request = $request;
29 $this->api_key = $this->utils( 'options' )->get( 'api_key' );
30 $process_id = $this->get_param( 'process_id' );
31
32 $_route = $request->get_route();
33
34 // Handle logo generation callback endpoint
35 if ( '/templately/v1/logo-generation/callback' === $_route ) {
36 // Disabled: the headers carry X-Templately-Apikey, and Helper::log()
37 // writes to debug.log, which is web-readable on plenty of hosts. That
38 // key authorizes this very route.
39 // Helper::log(
40 // [
41 // 'headers' => $request->get_headers(),
42 // 'body' => $request->get_params(),
43 // ],
44 // 'logo_generation_callback_request'
45 // );
46
47 if ( empty( $process_id ) ) {
48 return $this->error( 'invalid_id', __( 'Invalid ID.', 'templately' ), 'logo_generation_callback', 400 );
49 }
50
51 $header_api_key = sanitize_text_field( $request->get_header( 'x_templately_apikey' ) );
52 if ( empty( $header_api_key ) ) {
53 $header_api_key = sanitize_text_field( $request->get_header( 'X-Templately-Apikey' ) );
54 }
55
56 $is_valid_key = $this->validate_api_key_in_db( $header_api_key );
57 if ( ! $is_valid_key ) {
58 return $this->error( 'invalid_api_key', __( 'Invalid API key provided in header.', 'templately' ), 'logo-generation/permission', 403 );
59 }
60
61 if(defined('TEMPLATELY_DEV_API') && TEMPLATELY_DEV_API){
62 sleep(5);
63 }
64
65 // Check if process_id exists in logo generation processes
66 $logo_generation_processes = get_option( 'templately_logo_generation_processes', [] );
67 if ( isset( $logo_generation_processes[ $process_id ] ) ) {
68 return true;
69 }
70
71 Helper::log( 'Invalid or expired logo generation process ID.', 'logo_generation_callback' );
72
73 return $this->error( 'invalid_process_id', __( 'Invalid or expired logo generation process ID.', 'templately' ), 'logo_generation_callback', 400 );
74 }
75
76 return parent::_permission_check( $request );
77 }
78
79 public function register_routes() {
80 $this->post( $this->endpoint . '/generate', [ $this, 'generate_logo' ] );
81 $this->post( $this->endpoint . '/callback', [ $this, 'ai_update_logo_generation' ] );
82 $this->get( $this->endpoint . '/data', [ $this, 'get_logo_generation_data' ] );
83 $this->post( $this->endpoint . '/poll', [ $this, 'poll_logo_generation' ] );
84 $this->get( $this->endpoint . '/available-credits', [ $this, 'get_available_credits' ] );
85 }
86
87 /**
88 * Generate logo using AI - Async callback pattern
89 *
90 * @return array|\WP_Error
91 */
92 public function generate_logo() {
93 // Get parameters
94 $business_name = $this->get_param( 'business_name', '' );
95 $description = $this->get_param( 'description' );
96 $quality = $this->get_param( 'quality', 'auto' );
97 $size = $this->get_param( 'size', 'auto' );
98 $output_format = $this->get_param( 'output_format', 'png' );
99 $category = $this->get_param( 'category', '' );
100 $quantity = $this->get_param( 'quantity', 1 );
101 $requested_platform = $this->get_param( 'requested_platform', 'templately' );
102
103 // Validate required parameters
104 if ( empty( $description ) ) {
105 return $this->error(
106 'missing_description',
107 __( 'Description is required for logo generation.', 'templately' ),
108 'generate_logo',
109 400
110 );
111 }
112
113 // Validate size parameter
114 $allowed_sizes = [ 'auto', '1024x1024', '1536x1024', '1024x1536' ];
115 if ( ! empty( $size ) && ! in_array( $size, $allowed_sizes, true ) ) {
116 return $this->error(
117 'invalid_size',
118 __( 'Invalid size parameter. Allowed values: auto, 1024x1024, 1536x1024, 1024x1536', 'templately' ),
119 'generate_logo',
120 400
121 );
122 }
123
124 // Validate quality parameter
125 $allowed_qualities = [ 'auto', 'high', 'medium', 'low' ];
126 if ( ! empty( $quality ) && ! in_array( $quality, $allowed_qualities, true ) ) {
127 return $this->error(
128 'invalid_quality',
129 __( 'Invalid quality parameter. Allowed values: auto, high, medium, low', 'templately' ),
130 'generate_logo',
131 400
132 );
133 }
134
135 // Validate output_format parameter
136 $allowed_formats = [ 'png', 'jpeg' ];
137 if ( ! empty( $output_format ) && ! in_array( $output_format, $allowed_formats, true ) ) {
138 return $this->error(
139 'invalid_output_format',
140 __( 'Invalid output format. Allowed values: png, jpeg', 'templately' ),
141 'generate_logo',
142 400
143 );
144 }
145
146 // Validate quantity parameter
147 if ( ! empty( $quantity ) && ( ! is_numeric( $quantity ) || $quantity < 1 || $quantity > 10 ) ) {
148 return $this->error(
149 'invalid_quantity',
150 __( 'Invalid quantity. Must be between 1 and 10', 'templately' ),
151 'generate_logo',
152 400
153 );
154 }
155
156 // Prepare request body
157 $body_data = [
158 'business_name' => $business_name,
159 'description' => $description,
160 'quality' => $quality,
161 'size' => $size,
162 'output_format' => $output_format,
163 'category' => $category,
164 'quantity' => (int) $quantity,
165 'call_back_url' => defined( 'TEMPLATELY_CALLBACK' ) ? TEMPLATELY_CALLBACK . '/wp-json/templately/v1/logo-generation/callback' : rest_url( 'templately/v1/logo-generation/callback' ),
166 ];
167
168 // Make API request
169 $extra_headers = [
170 'Content-Type' => 'application/json',
171 'x-templately-requested-platform' => $requested_platform,
172 ];
173
174 $response = Helper::make_api_post_request( 'v2/generate-logo', $body_data, $extra_headers, 60 );
175
176 // Handle API response errors
177 if ( is_wp_error( $response ) ) {
178 return $this->error(
179 'api_request_failed',
180 __( 'Something went wrong. Please try again or contact support.', 'templately' ),
181 'generate_logo',
182 500,
183 [ 'error_detail' => $response->get_error_message() ]
184 );
185 }
186
187 $response_code = wp_remote_retrieve_response_code( $response );
188 $response_body = wp_remote_retrieve_body( $response );
189
190 if ( 200 !== $response_code ) {
191 // Try to parse the response body as JSON to get specific error details
192 $data = json_decode( $response_body, true );
193
194 // If valid JSON, extract error message and return with proper status code
195 if ( JSON_ERROR_NONE === json_last_error() && is_array( $data ) ) {
196 $error_message = isset( $data['message'] ) ? $data['message'] : __( 'Something went wrong. Please try again or contact support.', 'templately' );
197 return $this->error(
198 'api_response_error',
199 $error_message,
200 'generate_logo',
201 $response_code
202 );
203 }
204
205 // Otherwise, return generic error
206 return $this->error(
207 'api_response_error',
208 __( 'Something went wrong. Please try again or contact support.', 'templately' ),
209 'generate_logo',
210 $response_code
211 );
212 }
213
214 // Parse and validate response
215 $data = json_decode( $response_body, true );
216 if ( JSON_ERROR_NONE !== json_last_error() ) {
217 return $this->error(
218 'invalid_response',
219 __( 'Invalid response from API.', 'templately' ),
220 'generate_logo',
221 500
222 );
223 }
224
225 // Check if the response has the expected structure
226 if ( ! isset( $data['status'] ) ) {
227 return $this->error(
228 'api_response_error',
229 __( 'API returned an unexpected response.', 'templately' ),
230 'generate_logo',
231 500
232 );
233 }
234
235 // Expected response format: { status: 'success', message: '...', statusCode: 200, process_id: '...', is_local_site: true/false }
236 if ( 'success' === $data['status'] && isset( $data['process_id'] ) ) {
237 $process_id = $data['process_id'];
238
239 // Save process_id in a global option for security validation in callback
240 $logo_generation_processes = get_option( 'templately_logo_generation_processes', [] );
241 $logo_generation_processes[ $process_id ] = [
242 'created_at' => time(),
243 'api_key' => $this->api_key,
244 ];
245 update_option( 'templately_logo_generation_processes', $logo_generation_processes, false );
246
247 // Return the process_id to frontend for polling, including is_local_site flag
248 $response_data = [
249 'status' => 'success',
250 'message' => $data['message'] ?? __( 'Logo generation started', 'templately' ),
251 'process_id' => $process_id,
252 ];
253
254 // Include is_local_site flag from API response if available
255 if ( isset( $data['is_local_site'] ) ) {
256 $response_data['is_local_site'] = (bool) $data['is_local_site'];
257 }
258
259 return $response_data;
260 }
261
262 // Return error response from API
263 return $data;
264 }
265
266 /**
267 * Handle logo generation callback from API
268 *
269 * This endpoint is called by the Templately API when logo generation is complete.
270 * It processes the generated images and stores them for frontend retrieval.
271 *
272 * @return array|\WP_Error
273 */
274 public function ai_update_logo_generation() {
275 add_filter( 'wp_redirect', '__return_false', 999 );
276
277 $process_id = $this->get_param( 'process_id' );
278 $credit_cost = $this->request->get_param( 'credit_cost' );
279 $remaining_credit = $this->request->get_param( 'remaining_credit' );
280
281 if ( null === $remaining_credit ) {
282 $remaining_credit = $this->request->get_param( 'available_credit' );
283 }
284
285 error_log( 'Logo generation callback - process_id: ' . $process_id . ' remaining_credit: ' . ( $remaining_credit ?? 'null' ) );
286 // NOTE: Pass null as sanitizer to preserve base64-encoded image data in the images array
287 // The images array contains b64_json fields with base64-encoded image data
288 // Sanitization would corrupt the base64 encoding
289 $images = $this->get_param( 'images', [], null );
290
291
292 // Validate process_id exists in our saved processes
293 // Note: API key validation is already done in _permission_check() before this handler is called
294 $logo_generation_processes = get_option( 'templately_logo_generation_processes', [] );
295 if ( empty( $logo_generation_processes[ $process_id ] ) ) {
296 return $this->error(
297 'invalid_process_id',
298 __( 'Invalid or expired logo generation process ID.', 'templately' ),
299 'ai_update_logo_generation',
300 400
301 );
302 }
303
304 // Process logo images
305 $uploaded_images = [];
306 $has_errors = false;
307
308 if ( is_array( $images ) && ! empty( $images ) ) {
309 foreach ( $images as $index => $image_data ) {
310 if ( isset( $image_data['b64_json'] ) && ! empty( $image_data['b64_json'] ) ) {
311 $upload_result = Utils::upload_logo_base64( $image_data['b64_json'] );
312
313 if ( is_array( $upload_result ) && isset( $upload_result['error'] ) ) {
314 error_log( 'Logo upload error for image ' . $index . ': ' . $upload_result['error'] );
315 $has_errors = true;
316 } elseif ( is_array( $upload_result ) && isset( $upload_result['id'] ) ) {
317 $uploaded_images[] = [
318 'id' => $upload_result['id'],
319 'url' => $upload_result['url'],
320 ];
321 }
322 }
323 }
324 }
325
326 // Store uploaded logo data for frontend retrieval
327 $logo_generation_data = get_option( 'templately_logo_generation_data', [] );
328 $logo_generation_data[ $process_id ] = [
329 'images' => $uploaded_images,
330 'credit_cost' => $credit_cost,
331 'remaining_credit' => $remaining_credit,
332 'completed_at' => time(),
333 ];
334 update_option( 'templately_logo_generation_data', $logo_generation_data, false );
335
336 // Clean up the process from the processes list
337 unset( $logo_generation_processes[ $process_id ] );
338 update_option( 'templately_logo_generation_processes', $logo_generation_processes, false );
339
340 // Return success response
341 $response_data = [
342 'status' => 'success',
343 'message' => __( 'Logo generation completed successfully.', 'templately' ),
344 'data' => [
345 'process_id' => $process_id,
346 'images' => $uploaded_images,
347 ],
348 ];
349
350 if ( null !== $credit_cost ) {
351 $response_data['data']['credit_cost'] = $credit_cost;
352 }
353
354 if ( null !== $remaining_credit ) {
355 $response_data['data']['remaining_credit'] = $remaining_credit;
356 }
357
358 return $response_data;
359 }
360
361 /**
362 * Get logo generation data by process_id
363 *
364 * This endpoint is called by the frontend to retrieve completed logo generation data.
365 * For local sites, it will attempt to poll the API if data is not found locally.
366 * For production sites, the data is stored by the ai_update_logo_generation() callback endpoint.
367 *
368 * @return array|\WP_Error
369 */
370 public function get_logo_generation_data() {
371 $process_id = $this->get_param( 'process_id' );
372 $is_local_site = $this->get_param( 'is_local_site', false );
373
374 // Get logo generation data
375 $logo_generation_data = get_option( 'templately_logo_generation_data', [] );
376
377 // If data exists locally, return it
378 if ( isset( $logo_generation_data[ $process_id ] ) ) {
379 $data = $logo_generation_data[ $process_id ];
380
381 return [
382 'status' => 'success',
383 'data' => $data,
384 ];
385 }
386
387 // For local sites, attempt to poll the API if data not found
388 // This must be done BEFORE checking if process is active, to ensure we get fresh data
389 if ( $is_local_site ) {
390 // Use the polling helper function from AIUtils
391 $polling_result = AIUtils::poll_for_logo_generation( $process_id );
392
393 if ( ! is_wp_error( $polling_result ) ) {
394 // Extract logo data from polling result
395 $logo_data = $polling_result['data'] ?? [];
396
397 // Process and upload images if available
398 if ( ! empty( $logo_data['images'] ) && is_array( $logo_data['images'] ) ) {
399 $uploaded_images = [];
400
401 foreach ( $logo_data['images'] as $image ) {
402 // Upload image using the same method as ai_update_logo_generation
403 $upload_result = Utils::upload_logo_base64( $image['b64_json'] );
404
405 if ( ! is_wp_error( $upload_result ) ) {
406 $uploaded_images[] = $upload_result;
407 }
408 }
409
410 $logo_data['images'] = $uploaded_images;
411 }
412
413 // Store the polled data in the option for future retrieval
414 $logo_generation_data[ $process_id ] = $logo_data;
415 update_option( 'templately_logo_generation_data', $logo_generation_data, false );
416
417 // Clean up the process from the processes list
418 $logo_generation_processes = get_option( 'templately_logo_generation_processes', [] );
419 unset( $logo_generation_processes[ $process_id ] );
420 update_option( 'templately_logo_generation_processes', $logo_generation_processes, false );
421
422 // Return the polled data
423 return [
424 'status' => 'success',
425 'data' => $logo_data,
426 ];
427 }
428 }
429
430 // Check if process is still active in the processes list
431 // For local sites, this is checked AFTER attempting API polling
432 // For production sites, this is checked early to avoid unnecessary API calls
433 $logo_generation_processes = get_option( 'templately_logo_generation_processes', [] );
434 if ( isset( $logo_generation_processes[ $process_id ] ) ) {
435 // Process is still running - return pending status to continue polling
436 return [
437 'status' => 'pending',
438 'message' => __( 'Logo generation in progress', 'templately' ),
439 ];
440 }
441
442 // Data not found, process not active, and polling failed or not a local site
443 return $this->error(
444 'not_found',
445 __( 'Logo generation data not found.', 'templately' ),
446 'get_logo_generation_data',
447 404
448 );
449 }
450
451 /**
452 * Poll for logo generation status on local sites
453 *
454 * This endpoint is called by the frontend on local sites to poll for logo generation completion.
455 * It makes a GET request to the API endpoint and processes the results.
456 *
457 * @return array|\WP_Error
458 */
459 public function poll_logo_generation() {
460 $process_id = $this->get_param( 'process_id' );
461
462 if ( empty( $process_id ) ) {
463 return $this->error(
464 'missing_process_id',
465 __( 'Process ID is required for logo polling.', 'templately' ),
466 'poll_logo_generation',
467 400
468 );
469 }
470
471 // Use the polling helper function from AIUtils
472 $polling_result = AIUtils::poll_for_logo_generation( $process_id );
473
474 if ( is_wp_error( $polling_result ) ) {
475 return $polling_result;
476 }
477
478 // Extract logo data from polling result
479 $logo_data = $polling_result['data'] ?? [];
480
481 // Process and upload images if available
482 if ( ! empty( $logo_data['images'] ) && is_array( $logo_data['images'] ) ) {
483 $uploaded_images = [];
484
485 foreach ( $logo_data['images'] as $image ) {
486 // Upload image using the same method as ai_update_logo_generation
487 $upload_result = Utils::upload_logo_base64( $image );
488
489 if ( ! is_wp_error( $upload_result ) ) {
490 $uploaded_images[] = $upload_result;
491 }
492 }
493
494 $logo_data['images'] = $uploaded_images;
495 }
496
497 // Store the polled data in the option for future retrieval
498 $logo_generation_data = get_option( 'templately_logo_generation_data', [] );
499 $logo_generation_data[ $process_id ] = $logo_data;
500 update_option( 'templately_logo_generation_data', $logo_generation_data, false );
501
502 // Clean up the process from the processes list
503 $logo_generation_processes = get_option( 'templately_logo_generation_processes', [] );
504 unset( $logo_generation_processes[ $process_id ] );
505 update_option( 'templately_logo_generation_processes', $logo_generation_processes, false );
506
507 // Return success response
508 $response_data = [
509 'status' => 'success',
510 'message' => __( 'Logo generation data retrieved successfully.', 'templately' ),
511 'data' => [
512 'process_id' => $process_id,
513 'images' => $logo_data['images'] ?? [],
514 ],
515 ];
516
517 if ( isset( $logo_data['credit_cost'] ) ) {
518 $response_data['data']['credit_cost'] = $logo_data['credit_cost'];
519 }
520
521 if ( isset( $logo_data['remaining_credit'] ) ) {
522 $response_data['data']['remaining_credit'] = $logo_data['remaining_credit'];
523 } elseif ( isset( $logo_data['available_credit'] ) ) {
524 $response_data['data']['remaining_credit'] = $logo_data['available_credit'];
525 }
526
527 return $response_data;
528 }
529
530 /**
531 * Get available credits for the user
532 *
533 * @return array
534 */
535 public function get_available_credits() {
536 $response = Helper::make_api_get_request( 'v2/ai/available-credits' );
537
538 if ( is_wp_error( $response ) ) {
539 return [
540 'status' => 'error',
541 'message' => $response->get_error_message(),
542 'data' => [
543 'available_credit' => 0,
544 ]
545 ];
546 }
547
548 $body = json_decode( wp_remote_retrieve_body( $response ), true );
549 $credits = isset( $body['data']['available_credit'] ) ? (int) $body['data']['available_credit'] : 0;
550
551 return [
552 'status' => 'success',
553 'data' => [
554 'available_credit' => $credits,
555 ],
556 ];
557 }
558
559 /**
560 * Validate API key against database
561 * Checks if the provided API key exists for any user on the current site
562 * Uses Options class for proper multisite handling
563 *
564 * @param string $api_key The API key to validate
565 * @return bool True if valid, false otherwise
566 */
567 private function validate_api_key_in_db( $api_key ) {
568 $api_key = sanitize_text_field( $api_key );
569
570 if ( empty( $api_key ) ) {
571 return false;
572 }
573
574 // Get all users and check their API keys
575 $users = get_users( [ 'fields' => 'ID' ] );
576 $options = $this->utils( 'options' );
577
578 foreach ( $users as $user_id ) {
579 $stored_api_key = $options->get_user_meta( $user_id, '_templately_api_key', true );
580 if ( $stored_api_key === $api_key ) {
581 return true;
582 }
583 }
584
585 return false;
586 }
587 }
588
589