PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.4.8
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.4.8
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 / AIContent.php

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

1,044 lines 32.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Templately AI Content Importer
5 *
6 * @package Templately
7 * @since 1.0.0
8 */
9
10 namespace Templately\API;
11
12 use Error;
13 use Exception;
14 use Templately\Utils\Helper;
15 use WP_REST_Request;
16 use WP_Error;
17 use Templately\Core\Importer\Utils\Utils;
18 use Templately\Core\Importer\Utils\AIUtils;
19 use Templately\Core\Importer\Parsers\WXR_Parser;
20
21 class AIContent extends API {
22 private $endpoint = 'ai-content';
23 private $dev_mode = false;
24
25
26 /**
27 * AIContent constructor.
28 *
29 * @param string $file File path.
30 * @param array $settings Settings.
31 */
32 public function __construct() {
33
34 parent::__construct();
35
36 }
37
38 public function _permission_check(WP_REST_Request $request) {
39 $this->request = $request;
40 $this->api_key = $this->utils('options')->get( 'api_key' );
41 $process_id = $this->get_param('process_id');
42
43 $_route = $request->get_route();
44 if ('/templately/v1/ai-content/ai-update' === $_route || '/templately/v1/ai-content/ai-update-preview' === $_route) {
45 if (empty($process_id)) {
46 return $this->error('invalid_id', __('Invalid ID.', 'templately'), 'calculate_credit', 400);
47 }
48
49 // Check AI process data using API key-based storage
50 $ai_process_data = AIUtils::get_ai_process_data();
51 if (is_array($ai_process_data) && !empty($ai_process_data[$process_id])) {
52 return true;
53 }
54
55 return (bool) AIUtils::get_matched_session_data($process_id);
56 }
57
58 // // Allow access to attachments endpoint
59 // if ('/templately/v1/ai-content/attachments' === $_route) {
60 // return true;
61 // }
62
63 return parent::permission_check($request);
64 }
65
66
67 public function register_routes() {
68 // $this->get( $this->endpoint . '/calculate-credit', [ $this, 'calculate_credit' ] );
69 $this->post($this->endpoint . '/modify-content', [$this, 'modify_content']);
70 $this->post($this->endpoint . '/ai-update', [$this, 'ai_update']);
71 $this->post($this->endpoint . '/ai-update-preview', [$this, 'ai_update_preview']);
72 $this->post($this->endpoint . '/generate-logo', [$this, 'generate_logo']);
73 $this->post($this->endpoint . '/generate-meta', [$this, 'generate_meta']);
74 $this->get($this->endpoint . '/attachments', [$this, 'get_attachments'], [
75 'type' => [
76 'default' => 'pack',
77 'required' => false,
78 'sanitize_callback' => 'sanitize_text_field',
79 ],
80 'id' => [
81 'required' => false,
82 'sanitize_callback' => 'sanitize_text_field',
83 ],
84 'pack_id' => [
85 'required' => false,
86 'sanitize_callback' => 'sanitize_text_field',
87 ],
88 ]);
89 $this->get($this->endpoint . '/images', [$this, 'search_images'], [
90 'query' => [
91 'required' => false,
92 'sanitize_callback' => 'sanitize_text_field',
93 'validate_callback' => function($param, $request, $key) {
94 return is_string($param) && strlen($param) <= 255;
95 },
96 ],
97 'orientation' => [
98 'required' => false,
99 'default' => 'all',
100 'sanitize_callback' => 'sanitize_text_field',
101 'validate_callback' => function($param, $request, $key) {
102 $allowed_orientations = ['all', 'landscape', 'portrait', 'square'];
103 return in_array($param, $allowed_orientations, true);
104 },
105 ],
106 'size' => [
107 'required' => false,
108 'default' => 'medium',
109 'sanitize_callback' => 'sanitize_text_field',
110 'validate_callback' => function($param, $request, $key) {
111 $allowed_sizes = ['small', 'medium', 'large'];
112 return in_array($param, $allowed_sizes, true);
113 },
114 ],
115 'color' => [
116 'required' => false,
117 'sanitize_callback' => 'sanitize_text_field',
118 'validate_callback' => function($param, $request, $key) {
119 return is_string($param) && strlen($param) <= 50;
120 },
121 ],
122 'page' => [
123 'required' => false,
124 'default' => 1,
125 'sanitize_callback' => 'absint',
126 'validate_callback' => function($param, $request, $key) {
127 return is_numeric($param) && $param > 0 && $param <= 1000;
128 },
129 ],
130 'per_page' => [
131 'required' => false,
132 'default' => 20,
133 'sanitize_callback' => 'absint',
134 'validate_callback' => function($param, $request, $key) {
135 return is_numeric($param) && $param > 0 && $param <= 100;
136 },
137 ],
138 ]);
139 // die(rest_url( 'templately/v1/ai-content/ai-update' ));
140 }
141
142 public function calculate_credit() {
143 $pack_id = $this->get_param('pack_id');
144
145 return [
146 'status' => 'success',
147 'data' => [
148 'availableCredit' => 100,
149 ],
150 ];
151
152 if (empty($pack_id)) {
153 return $this->error('invalid_id', __('Invalid ID.', 'templately'), 'calculate_credit', 400);
154 }
155
156 $extra_headers = [
157 'Accept' => 'application/json',
158 ];
159 $response = Helper::make_api_get_request("v2/ai/calculate-credit/pack/$pack_id", [], $extra_headers, 30);
160
161
162 // return $response;
163 if (is_wp_error($response)) {
164 return $this->error('request_failed', __('Request failed.', 'templately'), 'calculate_credit', 500, $response);
165 }
166
167 $body = wp_remote_retrieve_body($response);
168 $data = json_decode($body, true);
169 // error status is ok
170 if (! is_array($data) || ! isset($data['status'])) {
171 return $this->error('invalid_response', __('Invalid response.', 'templately'), 'calculate_credit', 500);
172 }
173
174
175 return $data;
176 }
177
178 public function modify_content() {
179 add_filter('wp_redirect', '__return_false', 999);
180 set_time_limit(3 * MINUTE_IN_SECONDS);
181 ini_set('max_execution_time', 3 * MINUTE_IN_SECONDS);
182
183 $pack_id = $this->get_param('pack_id');
184 $isBusinessNichesNew = $this->get_param('isBusinessNichesNew', false);
185 $ai_page_ids = $this->get_param('ai_page_ids', [], null);
186 $content_ids = $this->get_param('content_ids', [], null);
187 $session_id = $this->get_param('session_id'); // Add session_id parameter
188 $preview_pages = $this->get_param('preview_pages', [], null);
189 $image_replace = $this->get_param('imageReplace', [], null);
190 $platform = $this->get_param('platform');
191 $language = $this->get_param('language', 'English');
192
193 // ai content fields
194 $name = $this->get_param('name');
195 $category = $this->get_param('category');
196 $description = $this->get_param('description');
197 $email = $this->get_param('email');
198 $contactNumber = $this->get_param('contactNumber');
199 $businessAddress = $this->get_param('businessAddress');
200 $openingHour = $this->get_param('openingHour');
201 $requested_platform = $this->get_param('requested_platform', 'templately');
202
203 if (empty($pack_id)) {
204 return $this->error('invalid_id', __('Invalid ID.', 'templately'), 'modify_content', 400);
205 }
206 if (empty($category)) {
207 return $this->error('invalid_prompt', __('Invalid prompt.', 'templately'), 'modify_content', 400);
208 }
209 if (empty($content_ids) && empty($preview_pages)) {
210 return $this->error('invalid_content_ids', __('Invalid content ids.', 'templately'), 'modify_content', 400);
211 }
212 if (empty($platform)) {
213 return $this->error('invalid_platform', __('Invalid platform.', 'templately'), 'modify_content', 400);
214 }
215
216
217 // $response = get_transient( '__templately_ai_process_id' );
218
219 // if(empty($response)) {
220 $extra_headers = [
221 'Accept' => 'application/json',
222 'x-templately-session-id' => $session_id,
223 'x-templately-requested-platform' => $requested_platform,
224 ];
225 $body_data = [
226 'business_name' => $name,
227 'business_niches' => $category,
228 'prompt' => $description,
229 'email' => $email,
230 'phone' => $contactNumber,
231 'address' => $businessAddress,
232 'openingHour' => $openingHour,
233 'pack_id' => $pack_id,
234 'content_ids' => $content_ids,
235 'platform' => $platform,
236 'preview_pages' => $preview_pages,
237 'language' => $language,
238 'callback' => defined('TEMPLATELY_CALLBACK') ? TEMPLATELY_CALLBACK . '/wp-json/templately/v1/ai-content/ai-update' : rest_url('templately/v1/ai-content/ai-update'),
239 ];
240 $response = Helper::make_api_post_request('v2/ai/modify-content/pack', $body_data, $extra_headers, 15 * MINUTE_IN_SECONDS);
241
242 // set_transient( '__templately_ai_process_id', $response, 60 * 60 * 24 * 30 );
243 // }
244
245 $bk_ai_business_niches = get_option('templately_ai_business_niches', []);
246 if (!empty($business_niches) && $isBusinessNichesNew && ! in_array($business_niches, $bk_ai_business_niches)) {
247 $bk_ai_business_niches[] = $business_niches;
248 update_option('templately_ai_business_niches', $bk_ai_business_niches, false);
249 }
250
251 // return $response;
252 if (is_wp_error($response)) {
253 error_log(print_r($response, true));
254 return $this->error('request_failed', __('Request failed.', 'templately'), 'modify_content', 500, $response->get_error_data());
255 }
256
257 $body = wp_remote_retrieve_body($response);
258 $data = json_decode($body, true);
259 // error status is ok, if status is error then return as is
260 if (! is_array($data) || ! isset($data['status'])) {
261 return $this->error('invalid_response', __('Invalid response.', 'templately'), 'modify_content', 500, $data);
262 }
263
264 // "{"status":"success","message":"The content is being generated in the queue","process_id":"01JRQQD39GNWTNF18EWF8YH0BG-271838-pack-408"}"
265 if (isset($data['status']) && $data['status'] === 'success' && isset($data['process_id'])) {
266 $process_id = $data['process_id'];
267
268 // // Save templates to files if available using the common function
269 // if (!empty($data['templates']) && is_array($data['templates'])) {
270 // foreach ($data['templates'] as $content_id => $template_data) {
271 // // Decode template if it's base64 encoded
272 // if (! empty($template_data) && base64_decode($template_data, true) !== false) {
273 // $data['templates'][$content_id] = base64_decode($template_data);
274 // }
275
276 // if (!empty($template_data)) {
277 // AIUtils::save_template_to_file(
278 // $process_id,
279 // $content_id,
280 // $template_data,
281 // $ai_page_ids,
282 // true, // Always use preview mode for AI content workflow
283 // isset($template_data['isSkipped']) ? $template_data['isSkipped'] : false
284 // );
285 // }
286 // }
287 // }
288
289 $user = $this->utils('options')->get('user');
290
291 $ai_process_data[$process_id] = [
292 'name' => $name,
293 'category' => $category,
294 'description' => $description,
295 'email' => $email,
296 'contactNumber' => $contactNumber,
297 'businessAddress' => $businessAddress,
298 'openingHour' => $openingHour,
299 'process_id' => $process_id,
300 'pack_id' => $pack_id,
301 'ai_page_ids' => $ai_page_ids,
302 'ai_preview_ids' => $preview_pages,
303 'content_ids' => $content_ids,
304 'platform' => $platform,
305 'api_key' => $this->api_key,
306 'user_id' => isset($user['id']) ? $user['id'] : null,
307 'session_id' => $session_id, // Store session_id for coordination
308 'imageReplace' => $image_replace, // Store session_id for coordination
309 'language' => $language,
310 ];
311
312 // Update using API key-based storage with automatic count-based cleanup
313 AIUtils::update_ai_process_data($ai_process_data);
314
315 return [
316 'status' => 'success',
317 'message' => __('The content is being generated in the queue', 'templately'),
318 'process_id' => $process_id,
319 'templates' => !empty($data['templates']) ? $data['templates'] : null,
320 'is_local_site' => !empty($data['is_local_site']) ? $data['is_local_site'] : null,
321 ];
322 }
323
324 return $data;
325 }
326
327 public function ai_update() {
328 add_filter('wp_redirect', '__return_false', 999);
329
330 $template = $this->get_param('template');
331 $process_id = $this->get_param('process_id');
332 $template_id = $this->get_param('template_id');
333 $content_id = $this->get_param('content_id');
334 $type = $this->get_param('type');
335 $isSkipped = $this->get_param('isSkipped', false);
336 $credit_cost = $this->request->get_param('credit_cost');
337
338 error_log('process_id: ' . $process_id);
339
340 // Handle credit cost updates separately
341 if ($this->request->has_param('credit_cost')) {
342 $processed_pages = get_option("templately_ai_processed_pages", []);
343 $processed_pages[$process_id] = $processed_pages[$process_id] ?? [];
344 $processed_pages[$process_id]['credit_cost'] = $credit_cost;
345 update_option("templately_ai_processed_pages", $processed_pages, false);
346
347 return [
348 'status' => 'success',
349 'data' => [
350 'process_id' => $process_id,
351 'credit_cost' => $credit_cost,
352 ],
353 ];
354 }
355
356 // Always use preview mode for AI content workflow
357 // Validate and get process data using centralized method
358 $process_data = AIUtils::validate_and_get_process_data($process_id);
359 if (is_wp_error($process_data)) {
360 return $process_data;
361 }
362
363 $session_id = $process_data['session_id'];
364 $ai_page_ids = $process_data['ai_page_ids'];
365
366 // Use the common helper function to save the template
367 $result = AIUtils::save_template_to_file(
368 $process_id,
369 $session_id,
370 $content_id,
371 $template,
372 $ai_page_ids,
373 $isSkipped
374 );
375
376 if(is_wp_error($result)){
377 return $result;
378 }
379
380 // Return the result from the helper function
381 if (isset($result['status']) && $result['status'] === 'success') {
382 return $result;
383 }
384
385 // Return error if the helper function failed
386 return $result;
387 }
388
389 public function ai_update_preview() {
390 add_filter('wp_redirect', '__return_false', 999);
391
392 $template = $this->get_param('templates'); // Now expects an array with content_id as keys
393 $process_id = $this->get_param('process_id');
394 $isSkipped = $this->get_param('isSkipped', false);
395 $error = $this->get_param('error', null);
396
397 error_log('process_id: ' . $process_id);
398
399 if (!empty($isSkipped) || !empty($error)) {
400 // Update AI process data with error using API key-based storage
401 $ai_process_data = AIUtils::get_ai_process_data();
402 if (isset($ai_process_data[$process_id])) {
403 $ai_process_data[$process_id]['preview_error'] = $error;
404 AIUtils::update_ai_process_data($ai_process_data);
405 }
406 wp_send_json_error([
407 'status' => 'error',
408 'message' => $error,
409 ]);
410 }
411
412 // Validate template parameter is an array
413 if (!is_array($template) || empty($template)) {
414 return $this->error('invalid_template', __('Template must be a non-empty array with content_id as keys.', 'templately'), 'ai-content/ai-update-preview', 400);
415 }
416
417 // Always use preview mode for AI content workflow
418 // Validate and get process data using centralized method
419 $process_data = AIUtils::validate_and_get_process_data($process_id);
420 if (is_wp_error($process_data)) {
421 return $process_data;
422 }
423
424 $session_id = $process_data['session_id'];
425 $ai_page_ids = $process_data['ai_page_ids'];
426 $results = [];
427 $success_count = 0;
428 $error_count = 0;
429
430 // Process each content_id/template pair
431 foreach ($template as $content_id => $template_data) {
432 // Use the common helper function to save the template (always preview mode)
433 $result = AIUtils::save_template_to_file(
434 $process_id,
435 $session_id,
436 $content_id,
437 $template_data,
438 $ai_page_ids,
439 $isSkipped
440 );
441
442 $results[$content_id] = $result;
443
444 // Track success/error counts
445 if (isset($result['status']) && $result['status'] === 'success') {
446 $success_count++;
447 } else {
448 $error_count++;
449 }
450 }
451
452 // Return consolidated response
453 $overall_status = $error_count === 0 ? 'success' : ($success_count === 0 ? 'error' : 'partial_success');
454
455 // Note: No cleanup needed with API key-based storage and count-based management
456
457 return [
458 'status' => $overall_status,
459 'message' => sprintf(
460 __('Processed %d templates: %d successful, %d failed.', 'templately'),
461 count($template),
462 $success_count,
463 $error_count
464 ),
465 ];
466 }
467
468 /**
469 * Get attachments from API endpoint
470 *
471 * @return array
472 */
473 public function get_attachments() {
474 // Get parameters from request
475 $type = $this->get_param('type', 'pack');
476 $id = $this->get_param('pack_id');
477
478 // Require ID parameter - return error if not provided
479 if (empty($id)) {
480 return $this->error('missing_id', __('Pack ID or ID parameter is required.', 'templately'), 'get_attachments', 400);
481 }
482
483 try {
484 // Construct API endpoint URL
485 $api_endpoint = "get-xml-attachment/{$type}/{$id}";
486
487 // Make API call
488 $extra_headers = [
489 'Accept' => 'application/xml, text/xml',
490 ];
491 $response = Helper::make_api_get_request("v2/$api_endpoint", [], $extra_headers, 30);
492
493 // Check for HTTP errors
494 if (is_wp_error($response)) {
495 return $this->error('api_request_failed', __('Failed to fetch attachments from API.', 'templately'), 'get_attachments', 500, $response->get_error_message());
496 }
497
498 $response_code = wp_remote_retrieve_response_code($response);
499 $xml_content = wp_remote_retrieve_body($response);
500
501 if ($response_code !== 200) {
502 // check if $xml_content contains valid json
503 // ex. '{"status":"error","message":"Attachment XML file not found in pack archive."}'
504 $error_data = @json_decode($xml_content, true);
505 if(is_array($error_data) && isset($error_data['status']) && $error_data['status'] === 'error' && !empty($error_data['message'])){
506 return $this->error('api_http_error', $error_data['message'], 'get_attachments', $response_code);
507 }
508 return $this->error('api_http_error', sprintf(__('API returned HTTP %d error.', 'templately'), $response_code), 'get_attachments', $response_code);
509 }
510
511 // Validate we have XML content
512 if (empty($xml_content)) {
513 return $this->error('no_xml_content', __('No XML content found in API response.', 'templately'), 'get_attachments', 404);
514 }
515
516 // Parse the XML content from API response
517 $parsed_data = $this->parse_xml_content($xml_content);
518
519 if (is_wp_error($parsed_data)) {
520 return $this->error('xml_parse_error', __('Failed to parse XML content.', 'templately'), 'get_attachments', 500, $parsed_data->get_error_message());
521 }
522
523 // Extract attachments from parsed data
524 $attachments = $this->extract_attachments_from_parsed_data($parsed_data);
525
526 return [
527 'status' => 'success',
528 'data' => $attachments,
529 'message' => sprintf(__('Found %d attachments.', 'templately'), count($attachments)),
530 ];
531
532 } catch (Exception $e) {
533 return $this->error('exception', __('An unexpected error occurred while fetching attachments.', 'templately'), 'get_attachments', 500, $e->getMessage());
534 }
535 }
536
537
538
539 /**
540 * Parse XML content string using WXR Parser
541 *
542 * @param string $xml_content XML content string
543 * @return array|WP_Error Parsed data or error
544 */
545 private function parse_xml_content($xml_content) {
546 // Ensure WordPress filesystem functions are available
547 if (!function_exists('wp_tempnam')) {
548 require_once(ABSPATH . 'wp-admin/includes/file.php');
549 }
550
551 // Create a temporary file to store XML content
552 $temp_file = wp_tempnam('templately_attachments');
553 if (!$temp_file) {
554 return new WP_Error('temp_file_failed', __('Failed to create temporary file.', 'templately'));
555 }
556
557 // Write XML content to temporary file
558 $bytes_written = file_put_contents($temp_file, $xml_content);
559 if ($bytes_written === false) {
560 unlink($temp_file);
561 return new WP_Error('write_failed', __('Failed to write XML content to temporary file.', 'templately'));
562 }
563
564 try {
565 // Initialize WXR Parser
566 $parser = new WXR_Parser();
567
568 // Parse the temporary XML file
569 $parsed_data = $parser->parse($temp_file);
570
571 // Clean up temporary file
572 unlink($temp_file);
573
574 return $parsed_data;
575
576 } catch (Exception $e) {
577 // Clean up temporary file on exception
578 if (file_exists($temp_file)) {
579 unlink($temp_file);
580 }
581 return new WP_Error('parse_exception', $e->getMessage());
582 }
583 }
584
585 /**
586 * Extract attachments from parsed WXR data
587 *
588 * @param array $parsed_data Parsed WXR data
589 * @return array Array of attachment data
590 */
591 private function extract_attachments_from_parsed_data($parsed_data) {
592 $attachments = [];
593
594 if (isset($parsed_data['posts']) && is_array($parsed_data['posts'])) {
595 foreach ($parsed_data['posts'] as $post) {
596 // Check if this is an attachment
597 if (isset($post['post_type']) && $post['post_type'] === 'attachment') {
598 $attachment = [
599 'id' => isset($post['post_id']) ? (int) $post['post_id'] : 0,
600 'url' => isset($post['attachment_url']) ? (string) $post['attachment_url'] : '',
601 'title' => isset($post['post_title']) ? (string) $post['post_title'] : '',
602 'type' => isset($post['attachment_type']) ? (string) $post['attachment_type'] : '',
603 ];
604
605 // Extract metadata including dimensions and medium URL
606 $metadata = $this->extract_medium_size_url($post, $attachment['url']);
607
608 // Filter out small images (width or height <= 150px) to ignore small icons
609 if ($metadata && isset($metadata['width']) && isset($metadata['height'])) {
610 if ($metadata['width'] < 150 || $metadata['height'] < 150) {
611 continue; // Skip small images/icons
612 }
613
614 // Add dimensions to attachment data
615 $attachment['width'] = $metadata['width'];
616 $attachment['height'] = $metadata['height'];
617
618 // Add medium URL if available
619 if (isset($metadata['medium_url'])) {
620 $attachment['medium_url'] = $metadata['medium_url'];
621 }
622 } else {
623 // Skip attachments without metadata or dimensions
624 continue;
625 }
626
627 // Only add if we have the required data
628 if ($attachment['id'] && $attachment['url'] && $attachment['title']) {
629 $attachments[] = $attachment;
630 }
631 }
632 }
633 }
634
635 return $attachments;
636 }
637
638 /**
639 * Extract medium size URL from attachment metadata and get image dimensions
640 *
641 * @param array $post Post data from WXR parser
642 * @param string $original_url Original attachment URL
643 * @return array|null Array with medium_url and dimensions if found, null otherwise
644 */
645 private function extract_medium_size_url($post, $original_url) {
646 if (!isset($post['postmeta']) || !is_array($post['postmeta'])) {
647 return null;
648 }
649
650 foreach ($post['postmeta'] as $meta) {
651 if (!isset($meta['key']) || !isset($meta['value'])) {
652 continue;
653 }
654
655 // Only check _wp_attachment_metadata
656 if ($meta['key'] === '_wp_attachment_metadata') {
657 $attachment_metadata = @unserialize($meta['value']);
658 if (is_array($attachment_metadata)) {
659 $result = [];
660
661 // Get original image dimensions
662 $width = isset($attachment_metadata['width']) ? (int) $attachment_metadata['width'] : 0;
663 $height = isset($attachment_metadata['height']) ? (int) $attachment_metadata['height'] : 0;
664
665 $result['width'] = $width;
666 $result['height'] = $height;
667
668 // Check if medium size exists
669 if (isset($attachment_metadata['sizes']['medium']['file'])) {
670 // Construct medium URL from original URL and medium filename
671 $medium_filename = $attachment_metadata['sizes']['medium']['file'];
672 $original_path = dirname(parse_url($original_url, PHP_URL_PATH));
673 $base_url = str_replace(parse_url($original_url, PHP_URL_PATH), '', $original_url);
674 $result['medium_url'] = $base_url . $original_path . '/' . $medium_filename;
675 }
676
677 return $result;
678 }
679 }
680 }
681
682 return null;
683 }
684
685
686
687 /**
688 * Search images endpoint
689 *
690 * @param WP_REST_Request $request
691 * @return WP_REST_Response|WP_Error
692 */
693 public function search_images(WP_REST_Request $request) {
694 // Get and sanitize parameters
695 $query = $this->get_param('query', '');
696 $orientation = $this->get_param('orientation', 'all');
697 $size = $this->get_param('size', 'medium');
698 $color = $this->get_param('color', '');
699 $page = $this->get_param('page', 1, 'absint');
700 $per_page = $this->get_param('per_page', 20, 'absint');
701
702 // Validate required query parameter
703 if (empty($query)) {
704 return $this->error(
705 'missing_query',
706 __('Search query is required.', 'templately'),
707 'search_images',
708 400
709 );
710 }
711
712 // Prepare API request parameters
713 $api_params = [
714 'query' => urlencode($query),
715 'page' => $page,
716 'per_page' => $per_page,
717 ];
718
719 // Add optional parameters if provided
720 if ($orientation !== 'all') {
721 $api_params['orientation'] = $orientation;
722 }
723
724 if (!empty($size)) {
725 $api_params['size'] = $size;
726 }
727
728 if (!empty($color)) {
729 $api_params['color'] = $color;
730 }
731
732 // Make API request to external image service
733 $extra_headers = [
734 'Content-Type' => 'application/json',
735 ];
736
737 $response = Helper::make_api_get_request('v2/images', $api_params, $extra_headers, 30);
738
739 // Handle API response errors
740 if (is_wp_error($response)) {
741 return $this->error(
742 'api_request_failed',
743 __('Failed to fetch images from external service.', 'templately'),
744 'search_images',
745 500
746 );
747 }
748
749 $response_code = wp_remote_retrieve_response_code($response);
750 $response_body = wp_remote_retrieve_body($response);
751
752 if ($response_code !== 200) {
753 return $this->error(
754 'api_response_error',
755 sprintf(__('External API returned error code: %d', 'templately'), $response_code),
756 'search_images',
757 $response_code
758 );
759 }
760
761 // Parse and validate response
762 $data = json_decode($response_body, true);
763 if (json_last_error() !== JSON_ERROR_NONE) {
764 return $this->error(
765 'invalid_response',
766 __('Invalid response from external service.', 'templately'),
767 'search_images',
768 500
769 );
770 }
771
772 // Check if the response has the expected structure and success status
773 if (!isset($data['status']) || $data['status'] !== 'success') {
774 return $this->error(
775 'api_response_error',
776 __('External API returned an error status.', 'templately'),
777 'search_images',
778 500
779 );
780 }
781
782 // Extract nested data from the response
783 $response_data = $data['data'] ?? [];
784 $images = $response_data['images'] ?? [];
785 $total_results = $response_data['total_results'] ?? 0;
786 $current_page = $response_data['page'] ?? $page;
787 $per_page_count = $response_data['per_page'] ?? $per_page;
788
789 // Return successful response with properly mapped data
790 return $this->success([
791 'images' => $images,
792 'total' => $total_results,
793 'page' => $current_page,
794 'per_page' => $per_page_count,
795 'total_pages' => $total_results > 0 ? ceil($total_results / $per_page_count) : 0,
796 ]);
797 }
798
799 /**
800 * Generate logo using AI
801 *
802 * @return array|WP_Error
803 */
804 public function generate_logo() {
805 // Get parameters
806 $business_name = $this->get_param('business_name', '');
807 $description = $this->get_param('description');
808 $logo_type = $this->get_param('logo_type');
809 $requested_platform = $this->get_param('requested_platform', 'templately');
810
811 // Validate required parameters
812 if (empty($description)) {
813 return $this->error(
814 'missing_description',
815 __('Description is required for logo generation.', 'templately'),
816 'generate_logo',
817 400
818 );
819 }
820
821 if (empty($logo_type)) {
822 return $this->error(
823 'missing_logo_type',
824 __('Logo type is required.', 'templately'),
825 'generate_logo',
826 400
827 );
828 }
829
830 // Prepare request body
831 $body_data = [
832 'business_name' => $business_name,
833 'description' => $description,
834 'logo_type' => $logo_type,
835 ];
836
837 // Make API request
838 $extra_headers = [
839 'Content-Type' => 'application/json',
840 'x-templately-requested-platform' => $requested_platform,
841 ];
842
843 $response = Helper::make_api_post_request('v2/generate-logo', $body_data, $extra_headers, 60);
844
845 // Handle API response errors
846 if (is_wp_error($response)) {
847 return $this->error(
848 'api_request_failed',
849 __('Something went wrong. Please try again or contact support.', 'templately'),
850 'generate_logo',
851 500,
852 $response->get_error_message()
853 );
854 }
855
856 $response_code = wp_remote_retrieve_response_code($response);
857 $response_body = wp_remote_retrieve_body($response);
858
859 if ($response_code !== 200) {
860 // Try to parse the response body as JSON to get specific error details
861 $data = json_decode($response_body, true);
862
863 // If valid JSON, extract error message and return with proper status code
864 if (json_last_error() === JSON_ERROR_NONE && is_array($data)) {
865 $error_message = isset($data['message']) ? $data['message'] : __('Something went wrong. Please try again or contact support.', 'templately');
866 return $this->error(
867 'api_response_error',
868 $error_message,
869 'generate_logo',
870 $response_code
871 );
872 }
873
874 // Otherwise, return generic error
875 return $this->error(
876 'api_response_error',
877 __('Something went wrong. Please try again or contact support.', 'templately'),
878 'generate_logo',
879 $response_code
880 );
881 }
882
883 // Parse and validate response
884 $data = json_decode($response_body, true);
885 if (json_last_error() !== JSON_ERROR_NONE) {
886 return $this->error(
887 'invalid_response',
888 __('Invalid response from API.', 'templately'),
889 'generate_logo',
890 500
891 );
892 }
893
894 // Check if the response has the expected structure
895 if (!isset($data['status'])) {
896 return $this->error(
897 'api_response_error',
898 __('API returned an unexpected response.', 'templately'),
899 'generate_logo',
900 500
901 );
902 }
903
904 // Extract image_base64 from nested data field if present
905 $image_base64 = null;
906 if (isset($data['data']['image_base64'])) {
907 $image_base64 = $data['data']['image_base64'];
908 } elseif (isset($data['image_base64'])) {
909 // Fallback for legacy response format
910 $image_base64 = $data['image_base64'];
911 }
912
913 // Upload base64 image to media library if provided
914 if (!empty($image_base64)) {
915 $upload_result = Utils::upload_logo_base64($image_base64);
916
917 if (is_array($upload_result) && isset($upload_result['error'])) {
918 // Log error but don't fail the entire response
919 error_log('Logo upload error: ' . $upload_result['error']);
920 } else if (is_array($upload_result) && isset($upload_result['id'])) {
921 // Return clean response with only essential uploaded logo information
922 // Overwrite the data field to remove base64 image data and return only ID and URL
923 return [
924 'status' => 'success',
925 'data' => [
926 'id' => $upload_result['id'],
927 'url' => $upload_result['url'],
928 ],
929 ];
930 }
931 }
932
933 // Return the original response if no logo was uploaded
934 return $data;
935 }
936
937 /**
938 * Generate meta (title or tagline) using AI
939 *
940 * @return array|WP_Error
941 */
942 public function generate_meta() {
943 // Get parameters
944 $field = $this->get_param('field');
945 $text = $this->get_param('text');
946 $requested_platform = $this->get_param('requested_platform', 'templately');
947
948 // Validate required parameters
949 if (empty($field)) {
950 return $this->error(
951 'missing_field',
952 __('Field is required.', 'templately'),
953 'generate_meta',
954 400
955 );
956 }
957
958 if (empty($text)) {
959 return $this->error(
960 'missing_text',
961 __('Text is required for meta generation.', 'templately'),
962 'generate_meta',
963 400
964 );
965 }
966
967 // Prepare request body
968 $body_data = [
969 'field' => $field,
970 'text' => $text,
971 ];
972
973 // Make API request
974 $extra_headers = [
975 'Content-Type' => 'application/json',
976 'x-templately-requested-platform' => $requested_platform,
977 ];
978
979 $response = Helper::make_api_post_request('v2/generate-meta', $body_data, $extra_headers, 30);
980
981 // Handle API response errors
982 if (is_wp_error($response)) {
983 return $this->error(
984 'api_request_failed',
985 __('Failed to generate meta.', 'templately'),
986 'generate_meta',
987 500,
988 $response->get_error_message()
989 );
990 }
991
992 $response_code = wp_remote_retrieve_response_code($response);
993 $response_body = wp_remote_retrieve_body($response);
994
995 if ($response_code !== 200) {
996 // Try to parse the response body as JSON to get specific error details
997 $data = json_decode($response_body, true);
998
999 // If valid JSON, extract error message and return with proper status code
1000 if (json_last_error() === JSON_ERROR_NONE && is_array($data)) {
1001 $error_message = isset($data['message']) ? $data['message'] : __('Something went wrong. Please try again or contact support.', 'templately');
1002 return $this->error(
1003 'api_response_error',
1004 $error_message,
1005 'generate_meta',
1006 $response_code
1007 );
1008 }
1009
1010 // Otherwise, return generic error
1011 return $this->error(
1012 'api_response_error',
1013 __('Something went wrong. Please try again or contact support.', 'templately'),
1014 'generate_meta',
1015 $response_code
1016 );
1017 }
1018
1019 // Parse and validate response
1020 $data = json_decode($response_body, true);
1021 if (json_last_error() !== JSON_ERROR_NONE) {
1022 return $this->error(
1023 'invalid_response',
1024 __('Invalid response from API.', 'templately'),
1025 'generate_meta',
1026 500
1027 );
1028 }
1029
1030 // Check if the response has the expected structure
1031 if (!isset($data['status'])) {
1032 return $this->error(
1033 'api_response_error',
1034 __('API returned an unexpected response.', 'templately'),
1035 'generate_meta',
1036 500
1037 );
1038 }
1039
1040 // Return the response as-is
1041 return $data;
1042 }
1043 }
1044