PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.6.3
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.6.3
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.6.3, at includes/API/LogoGeneration.php

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