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.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 / Core / Importer / Utils / AIUtils.php

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

776 lines 24.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace Templately\Core\Importer\Utils;
4
5 use Templately\Utils\Options;
6 use Templately\Utils\Helper;
7
8 /**
9 * AI-related utility functions for Templately
10 * Handles AI process data management using API key-based storage with count-based cleanup
11 */
12 class AIUtils {
13
14 /**
15 * Handle SSE message wait with timeout exit condition
16 * Static utility function for reusable timeout handling across different import contexts
17 *
18 * @param string $session_id Session ID for progress tracking
19 * @param string $progress_id Progress tracking identifier (e.g., 'ai_content_time', 'finalize_time')
20 * @param array $updated_ids Currently updated/processed pages
21 * @param array $ai_page_ids All AI pages that need processing
22 * @param callable $sse_message_callback Callback function for sending SSE messages
23 * @param array $additional_sse_data Additional data to include in SSE message
24 * @param string|null $old_template_id Optional template ID for local site polling
25 * @return bool True if should continue processing, false if should exit
26 */
27 public static function handle_sse_wait_with_timeout($session_id, $progress_id, $updated_ids, $ai_page_ids, $sse_message_callback, $additional_sse_data = [], $old_template_id = null) {
28 $total_pages = count($ai_page_ids);
29 $updated_pages = count($updated_ids['pages'] ?? []);
30
31 // If all pages are processed or credit cost is available, continue processing
32 if ($total_pages <= $updated_pages || isset($updated_ids['credit_cost'])) {
33 return true;
34 }
35
36 // Get timeout tracking data from session
37 $session_data = Utils::get_session_data($session_id);
38 $progress_data = $session_data['progress'][$progress_id] ?? [];
39 $last_progress = $progress_data['last_progress'] ?? 0;
40 $last_time = $progress_data['last_time'] ?? 0;
41 $current_time = time();
42 $progress_percentage = $total_pages > 0 ? round(($updated_pages / $total_pages) * 100) : 0;
43 $is_local_site = $session_data['isLocalSite'] ?? false;
44
45 // Skip timeout check if credit cost is available and time difference is > 10 seconds
46 if (isset($updated_ids['credit_cost']) && !empty($last_time) && ($current_time - $last_time) > 10) {
47 return true;
48 }
49
50 // Check if time difference is less than 5 minutes (timeout condition)
51 if (empty($last_time) || ($current_time - $last_time) < 7 * MINUTE_IN_SECONDS) {
52 // For local sites with template ID, attempt polling before waiting
53 if ($is_local_site && !empty($old_template_id)) {
54 $process_id = $session_data['process_id'] ?? null;
55 if (!empty($process_id)) {
56 // Call generic polling function to process all available templates
57 $polling_result = self::poll_for_template($process_id, $session_id, $ai_page_ids);
58 if ($polling_result === true) {
59 // Check if the specific template file now exists after polling
60 $upload_dir = wp_upload_dir();
61 $tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
62
63 // Find the correct directory for the template ID
64 $template_file_found = false;
65 foreach ($ai_page_ids as $key => $ids) {
66 if (in_array($old_template_id, $ids)) {
67 $page_dir = $tmp_dir . $key . DIRECTORY_SEPARATOR;
68 $file_path = $page_dir . $old_template_id . '.ai.json';
69 if (file_exists($file_path)) {
70 $template_file_found = true;
71 break;
72 }
73 }
74 }
75
76 if ($template_file_found) {
77 // Specific template found and downloaded, continue processing
78 return true;
79 }
80 }
81 }
82 }
83
84 // Only update time if progress has changed
85 if ($progress_percentage !== $last_progress) {
86 $updated_progress = $session_data['progress'] ?? [];
87 $updated_progress[$progress_id] = [
88 'last_progress' => $progress_percentage,
89 'last_time' => $current_time,
90 ];
91 Utils::update_session_data($session_id, ['progress' => $updated_progress]);
92 }
93
94 // Prepare SSE message data
95 $sse_data = array_merge([
96 'type' => 'wait',
97 'action' => 'wait',
98 'generated_pages' => $updated_ids,
99 'all_pages' => $ai_page_ids,
100 ], $additional_sse_data);
101
102 // Send wait message and exit
103 call_user_func($sse_message_callback, $sse_data);
104 exit;
105 }
106
107 // Timeout exceeded - could send error message here if needed
108 // Currently commented out in original AIContent.php
109 /*
110 call_user_func($sse_message_callback, [
111 'action' => 'error',
112 'status' => 'error',
113 'type' => "error",
114 'title' => __("Oops!", "templately"),
115 'message' => __("Taking too long....", "templately"),
116 ]);
117 exit;
118 */
119
120 // Continue processing if timeout exceeded (current behavior)
121 return true;
122 }
123
124 /**
125 * Poll for AI template generation status on local sites
126 * Generic polling utility that processes all available templates
127 *
128 * @param string $process_id The AI process ID
129 * @param string $session_id The session ID
130 * @param array $ai_page_ids The AI page IDs structure
131 * @return bool True if polling was successful, false otherwise
132 */
133 public static function poll_for_template($process_id, $session_id, $ai_page_ids) {
134 // First check if polling is already complete
135 $processed_pages = get_option("templately_ai_processed_pages", []);
136 if (isset($processed_pages[$process_id]['is_last_part']) && $processed_pages[$process_id]['is_last_part']) {
137 return true;
138 }
139
140 // Make GET request to API endpoint
141 $response = Helper::make_api_get_request("v2/ai/{$process_id}/template", [], [], 30);
142
143 if (is_wp_error($response)) {
144 return false;
145 }
146
147 $response_code = wp_remote_retrieve_response_code($response);
148 if ($response_code !== 200) {
149 return false;
150 }
151
152 $body = wp_remote_retrieve_body($response);
153 $api_data = json_decode($body, true);
154
155 if (!isset($api_data['status']) || $api_data['status'] !== 'success') {
156 return false;
157 }
158
159 $response_data = $api_data['data'] ?? [];
160
161 // Extract required fields from API response
162 $is_last_part = $response_data['is_last_part'] ?? false;
163 $credit_cost = $response_data['credit_cost'] ?? 0;
164 $templates = $response_data['templates'] ?? [];
165
166 // Save credit_cost if available
167 if ($credit_cost > 0) {
168 $processed_pages = get_option("templately_ai_processed_pages", []);
169 $processed_pages[$process_id] = $processed_pages[$process_id] ?? [];
170 $processed_pages[$process_id]['credit_cost'] = $credit_cost;
171 update_option("templately_ai_processed_pages", $processed_pages, false);
172 }
173
174 // Save is_last_part if true
175 if ($is_last_part) {
176 $processed_pages = get_option("templately_ai_processed_pages", []);
177 $processed_pages[$process_id] = $processed_pages[$process_id] ?? [];
178 $processed_pages[$process_id]['is_last_part'] = $is_last_part;
179 update_option("templately_ai_processed_pages", $processed_pages, false);
180 }
181
182 // Process ALL templates if available (extracted from FullSiteImport::ai_poll_template)
183 if (!empty($templates) && is_array($templates)) {
184 foreach ($templates as $content_id => $template_data) {
185 // Save template to file using the common helper function
186 $result = self::save_template_to_file(
187 $process_id,
188 $session_id,
189 $content_id,
190 $template_data,
191 $ai_page_ids,
192 false // Not skipped
193 );
194 // Continue processing other templates even if one fails
195 }
196 }
197
198 return true; // Return true if polling was successful, regardless of specific templates
199 }
200
201 /**
202 * Get AI process data from WordPress options
203 *
204 * @return array The AI process data array
205 */
206 public static function get_ai_process_data() {
207 $all_ai_process_data = get_option('templately_ai_process_data', []);
208 return is_array($all_ai_process_data) ? $all_ai_process_data : [];
209 }
210
211 /**
212 * Update AI process data in WordPress options with process_id as key
213 * Appends to existing data
214 *
215 * @param array $data The AI process data to save (process_id => process_data)
216 * @return bool True on success, false on failure
217 */
218 public static function update_ai_process_data($data) {
219 if (!is_array($data)) {
220 return false;
221 }
222
223 // Get existing data
224 $all_ai_process_data = get_option('templately_ai_process_data', []);
225 if (!is_array($all_ai_process_data)) {
226 $all_ai_process_data = [];
227 }
228
229 // Append new data to existing data
230 foreach ($data as $process_id => $process_data) {
231 if (is_array($process_data)) {
232 $all_ai_process_data[$process_id] = $process_data;
233 }
234 }
235
236 return update_option('templately_ai_process_data', $all_ai_process_data);
237 }
238
239 /**
240 * Get the latest AI process data for the current API key or user ID
241 * Used in import_info() to return the most recent AI process
242 * Priority: api_key first, then user_id as fallback
243 *
244 * @return array|null The latest AI process data or null if not found
245 */
246 public static function get_latest_ai_process_by_api_key($id) {
247 $api_key = Options::get_instance()->get('api_key');
248 $user = Options::get_instance()->get('user');
249 $user_id = isset($user['id']) ? $user['id'] : null;
250
251 $all_ai_process_data = get_option('templately_ai_process_data', []);
252 if (!is_array($all_ai_process_data)) {
253 return null;
254 }
255
256 $matching_processes = [];
257
258 // Priority 1: Try to find processes by API key if available
259 if (!empty($api_key)) {
260 foreach ($all_ai_process_data as $process_id => $process_data) {
261 if (is_array($process_data) && isset($process_data['api_key']) && $process_data['api_key'] === $api_key) {
262 $matching_processes[$process_id] = $process_data;
263 }
264 }
265 }
266
267 // Priority 2: If no API key matches found or API key is empty, fallback to user_id
268 if (empty($matching_processes) && !empty($user_id)) {
269 foreach ($all_ai_process_data as $process_id => $process_data) {
270 if (is_array($process_data) && isset($process_data['user_id']) && $process_data['user_id'] === $user_id) {
271 $matching_processes[$process_id] = $process_data;
272 }
273 }
274 }
275
276 if (empty($matching_processes)) {
277 return null;
278 }
279
280 $latest_data = end($matching_processes);
281
282 if($id != $latest_data['pack_id'] && isset($latest_data['imageReplace'])){
283 unset($latest_data['imageReplace']);
284 }
285
286 // Return the last (most recent) process
287 return $latest_data;
288 }
289
290
291
292 /**
293 * Get AI process data by session ID
294 *
295 * @param string $session_id The session ID to search for
296 * @return array|null The AI process data array or null if not found
297 */
298 public static function get_ai_process_data_by_session_id($session_id) {
299 if (empty($session_id)) {
300 return null;
301 }
302
303 $ai_process_data = self::get_ai_process_data();
304
305 foreach ($ai_process_data as $process) {
306 if (is_array($process) && isset($process['session_id']) && $process['session_id'] === $session_id) {
307 return $process;
308 }
309 }
310
311 return null;
312 }
313
314 /**
315 * Get AI process ID by session ID
316 *
317 * @param string $session_id The session ID to search for
318 * @return string|null The process ID (array key) or null if not found
319 */
320 public static function get_ai_process_id_by_session_id($session_id) {
321 if (empty($session_id)) {
322 return null;
323 }
324
325 $ai_process_data = self::get_ai_process_data();
326
327 foreach ($ai_process_data as $process_id => $process) {
328 if (is_array($process) && isset($process['session_id']) && $process['session_id'] === $session_id) {
329 return $process_id;
330 }
331 }
332
333 return null;
334 }
335
336 /**
337 * Get AI process data by process ID
338 *
339 * @param string $process_id The process ID to search for
340 * @return array|null The AI process data array or null if not found
341 */
342 public static function get_ai_process_data_by_process_id($process_id) {
343 if (empty($process_id)) {
344 return null;
345 }
346
347 $ai_process_data = self::get_ai_process_data();
348
349 if (isset($ai_process_data[$process_id]) && is_array($ai_process_data[$process_id])) {
350 return $ai_process_data[$process_id];
351 }
352
353 return null;
354 }
355
356 /**
357 * Clean AI process data by pack ID, keeping only the current process
358 * Removes all AI process entries with the same pack_id except the current process
359 *
360 * @param string $pack_id The pack ID to match for cleanup
361 * @param string $current_process_id The current process ID to preserve (optional)
362 * @return array Array of removed process IDs
363 */
364 public static function clean_ai_process_data_by_pack_id($pack_id, $current_process_id = null) {
365 if (empty($pack_id)) {
366 return [];
367 }
368
369 $all_ai_process_data = get_option('templately_ai_process_data', []);
370 if (!is_array($all_ai_process_data)) {
371 return [];
372 }
373
374 $removed_process_ids = [];
375
376 foreach ($all_ai_process_data as $process_id => $process_data) {
377 if (!is_array($process_data)) {
378 continue;
379 }
380
381 // Skip the current process by process_id if provided
382 if (!empty($current_process_id) && $process_id === $current_process_id) {
383 continue;
384 }
385
386 // Remove processes that have the same pack_id
387 if (isset($process_data['pack_id']) && $process_data['pack_id'] === $pack_id) {
388 unset($all_ai_process_data[$process_id]);
389 $removed_process_ids[] = $process_id;
390 }
391 }
392
393 // Update the options with cleaned data if any processes were removed
394 if (!empty($removed_process_ids)) {
395 update_option('templately_ai_process_data', $all_ai_process_data);
396 }
397
398 return $removed_process_ids;
399 }
400
401 /**
402 * Save template data to file
403 *
404 * Common function for saving templates to files, used by AI content operations
405 *
406 * @param string $process_id The process ID
407 * @param string $session_id The session ID
408 * @param string $content_id The content ID
409 * @param string $template The template data (base64 encoded or raw)
410 * @param array $ai_page_ids Array of AI page IDs
411 * @param bool $is_skipped Whether the template was skipped
412 * @return array|WP_Error Result array with status and data
413 */
414 public static function save_template_to_file($process_id, $session_id, $content_id, $template, $ai_page_ids, $is_skipped = false) {
415 $upload_dir = wp_upload_dir();
416
417 // Always save to tmp directory for AI content workflow
418 $tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
419
420 // Decode template if it's base64 encoded
421 if (! empty($template) && base64_decode($template, true) !== false) {
422 $template = base64_decode($template);
423 }
424
425 // Handle empty template (skipped)
426 if (empty($template)) {
427 $template = json_encode([
428 "isSkipped" => true,
429 ]);
430 }
431
432 // Find the correct directory for the content ID
433 $found_key = null;
434 foreach ($ai_page_ids as $key => $ids) {
435 if (in_array($content_id, $ids)) {
436 $found_key = $key;
437 break;
438 }
439 }
440
441 if ($found_key === null) {
442 return Helper::error('invalid_content_id', __('Content ID not found in AI page IDs.', 'templately'), 'save_template_to_file', 400);
443 }
444
445 // Create directory and file path
446 $page_dir = $tmp_dir . $found_key . DIRECTORY_SEPARATOR;
447 $file_path = $page_dir . $content_id . '.ai.json';
448 wp_mkdir_p($page_dir);
449
450 // Save the file
451 $is_success = file_put_contents($file_path, $template);
452
453 if ($is_success) {
454 // Update processed pages option
455 $processed_pages = get_option("templately_ai_processed_pages", []);
456 $processed_pages[$process_id] = $processed_pages[$process_id] ?? [];
457 $processed_pages[$process_id]['pages'][$content_id] = $is_skipped;
458 update_option("templately_ai_processed_pages", $processed_pages, false);
459
460 return [
461 'status' => 'success',
462 'data' => [
463 'process_id' => $process_id,
464 // 'file_path' => $file_path,
465 'content_id' => $content_id,
466 ],
467 ];
468 }
469
470 return Helper::error('file_save_failed', __('Failed to save template file.', 'templately'), 'save_template_to_file', 500);
471 }
472
473 /**
474 * Get matched session data by process ID
475 *
476 * Helper function to retrieve session data for a given process ID
477 *
478 * @param string $process_id The process ID to match
479 * @return array|false Returns matched data array or false if not found
480 */
481 public static function get_matched_session_data($process_id) {
482 if (empty($process_id)) {
483 return false;
484 }
485
486 $all_data = Utils::get_all_session_data();
487
488 if (empty($all_data) || ! is_array($all_data)) {
489 return false;
490 }
491
492 // INSERT_YOUR_CODE
493 if (is_array($all_data)) {
494 $all_data = array_reverse($all_data);
495 }
496 foreach ($all_data as $data) {
497 if (isset($data['process_id']) && ($data['process_id'] === $process_id)) {
498 return $data;
499 }
500 }
501
502 return false;
503 }
504
505 /**
506 * Generate AI file paths for content processing
507 *
508 * @param string $session_id The session ID
509 * @param string $type The content type (templates, content, etc.)
510 * @param string $sub_type The content sub-type (page, post, etc.)
511 * @param string $template_id The template ID
512 * @param string $dir_path The base directory path for original files
513 * @return array Array containing paths for original and AI files
514 */
515 public static function generate_ai_file_paths($session_id, $type, $sub_type, $template_id, $dir_path) {
516 // Original file path
517 $original_path = $dir_path . $type . DIRECTORY_SEPARATOR;
518 if (!empty($sub_type)) {
519 $original_path .= $sub_type . DIRECTORY_SEPARATOR;
520 }
521 $original_file = $original_path . "{$template_id}.json";
522
523 // AI file path in tmp directory
524 $upload_dir = wp_upload_dir();
525 $tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
526 $ai_path = $tmp_dir . $type . DIRECTORY_SEPARATOR;
527 if (!empty($sub_type)) {
528 $ai_path .= $sub_type . DIRECTORY_SEPARATOR;
529 }
530 $ai_file = $ai_path . "{$template_id}.ai.json";
531
532 return [
533 'original_file' => $original_file,
534 'ai_file_path' => $ai_file,
535 'ai_directory' => $ai_path,
536 'tmp_directory' => $tmp_dir,
537 ];
538 }
539
540 /**
541 * Get AI tmp directory path for a session
542 *
543 * @param string $session_id The session ID
544 * @return string The tmp directory path
545 */
546 public static function get_ai_tmp_directory($session_id) {
547 $upload_dir = wp_upload_dir();
548 return trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
549 }
550
551 /**
552 * Get AI file path for specific content
553 *
554 * @param string $session_id The session ID
555 * @param string $type The content type
556 * @param string $sub_type The content sub-type (optional)
557 * @param string $content_id The content ID
558 * @return string The AI file path
559 */
560 public static function get_ai_file_path($session_id, $type, $sub_type, $content_id) {
561 $tmp_dir = self::get_ai_tmp_directory($session_id);
562 $file_path = $tmp_dir . $type . DIRECTORY_SEPARATOR;
563 if (!empty($sub_type)) {
564 $file_path .= $sub_type . DIRECTORY_SEPARATOR;
565 }
566 return $file_path . "{$content_id}.ai.json";
567 }
568
569 /**
570 * Check if AI file exists and is valid
571 *
572 * @param string $ai_file_path The AI file path
573 * @return bool True if AI file exists and is valid
574 */
575 public static function has_ai_file($ai_file_path) {
576 if (!file_exists($ai_file_path)) {
577 return false;
578 }
579
580 $file_content = file_get_contents($ai_file_path);
581 if (empty($file_content)) {
582 return false;
583 }
584
585 $decoded = json_decode($file_content, true);
586 return json_last_error() === JSON_ERROR_NONE && !empty($decoded);
587 }
588
589 /**
590 * Check if AI file is marked as skipped
591 *
592 * @param string $ai_file_path The AI file path
593 * @return bool True if AI file exists and is marked as skipped
594 */
595 public static function is_ai_file_skipped($ai_file_path) {
596 if (!file_exists($ai_file_path)) {
597 return false;
598 }
599
600 $ai_content = Utils::read_json_file($ai_file_path);
601 return isset($ai_content['isSkipped']) && $ai_content['isSkipped'];
602 }
603
604 /**
605 * Check if content ID is in AI page IDs
606 *
607 * @param string $content_id The content ID to check
608 * @param array $ai_page_ids The AI page IDs structure
609 * @return bool True if content ID is found in AI page IDs
610 */
611 public static function is_ai_content($content_id, $ai_page_ids) {
612 if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
613 return false;
614 }
615
616 foreach ($ai_page_ids as $ids) {
617 if (is_array($ids) && in_array($content_id, $ids)) {
618 return true;
619 }
620 }
621
622 return false;
623 }
624
625 /**
626 * Check if content should be processed as AI content
627 *
628 * @param string $session_id The session ID
629 * @param string $type The content type
630 * @param string $sub_type The content sub-type
631 * @param string $content_id The content ID
632 * @param array $ai_page_ids The AI page IDs structure
633 * @param string $dir_path The base directory path
634 * @return bool True if this is AI content
635 */
636 public static function should_process_as_ai_content($session_id, $type, $sub_type, $content_id, $ai_page_ids, $dir_path) {
637 // Check if content ID is in AI page IDs list
638 $is_ai_template = self::is_ai_content($content_id, $ai_page_ids);
639
640 // Check if AI file exists
641 $paths = self::generate_ai_file_paths($session_id, $type, $sub_type, $content_id, $dir_path);
642 $has_ai_file = self::has_ai_file($paths['ai_file_path']);
643
644 return $is_ai_template || $has_ai_file;
645 }
646
647 /**
648 * Validate and extract AI process data for a given process ID
649 *
650 * @param string $process_id The process ID
651 * @return array|WP_Error Returns process data array or WP_Error on failure
652 */
653 public static function validate_and_get_process_data($process_id) {
654 if (empty($process_id)) {
655 return Helper::error('invalid_process_id', __('Process ID is required.', 'templately'), 'validate_process_data', 400);
656 }
657
658 $ai_process_data = self::get_ai_process_data();
659 if (empty($ai_process_data[$process_id])) {
660 return Helper::error('process_not_found', __('Process ID not found.', 'templately'), 'validate_process_data', 404);
661 }
662
663 $process_data = $ai_process_data[$process_id];
664
665 // Validate required fields
666 if (empty($process_data['session_id'])) {
667 return Helper::error('missing_session_id', __('Session ID missing from process data.', 'templately'), 'validate_process_data', 400);
668 }
669
670 if (empty($process_data['ai_page_ids'])) {
671 return Helper::error('missing_ai_page_ids', __('AI page IDs missing from process data.', 'templately'), 'validate_process_data', 400);
672 }
673
674 return $process_data;
675 }
676
677 /**
678 * Get processed pages data for a process ID
679 *
680 * @param string $process_id The process ID
681 * @return array The processed pages data
682 */
683 public static function get_processed_pages_data($process_id) {
684 $processed_pages = get_option("templately_ai_processed_pages", []);
685 return $processed_pages[$process_id] ?? [];
686 }
687
688 /**
689 * Update processed pages data for a process ID
690 *
691 * @param string $process_id The process ID
692 * @param array $data The data to update
693 * @return bool True on success, false on failure
694 */
695 public static function update_processed_pages_data($process_id, $data) {
696 $processed_pages = get_option("templately_ai_processed_pages", []);
697 $processed_pages[$process_id] = array_merge($processed_pages[$process_id] ?? [], $data);
698 return update_option("templately_ai_processed_pages", $processed_pages, false);
699 }
700
701 /**
702 * Find content type and sub-type for a given content ID in AI page IDs
703 *
704 * @param string $content_id The content ID to find
705 * @param array $ai_page_ids The AI page IDs structure
706 * @return array|null Array with 'type' and 'sub_type' keys, or null if not found
707 */
708 public static function find_content_type_info($content_id, $ai_page_ids) {
709 if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
710 return null;
711 }
712
713 foreach ($ai_page_ids as $key => $ids) {
714 if (is_array($ids) && in_array($content_id, $ids)) {
715 $type_parts = explode('/', $key);
716 return [
717 'type' => $type_parts[0],
718 'sub_type' => isset($type_parts[1]) ? $type_parts[1] : '',
719 'key' => $key
720 ];
721 }
722 }
723
724 return null;
725 }
726
727 /**
728 * Read AI template data directly from files
729 * Common function for reading template data, used by AI content operations
730 *
731 * @param string $session_id The session ID
732 * @param array $ai_page_ids The AI page IDs structure
733 * @param string $dir_path The base directory path
734 * @return array Array of template data indexed by content ID
735 */
736 public static function read_ai_template_data($session_id, $ai_page_ids, $dir_path) {
737 $result = [];
738
739 if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
740 return $result;
741 }
742
743 foreach ($ai_page_ids as $key => $ids) {
744 if (!is_array($ids)) {
745 continue;
746 }
747
748 foreach ($ids as $id) {
749 $type_arr = explode('/', $key);
750 $type = $type_arr[0];
751 $sub_type = isset($type_arr[1]) ? $type_arr[1] : '';
752
753 // Check if this is AI content before processing
754 if (self::should_process_as_ai_content($session_id, $type, $sub_type, $id, $ai_page_ids, $dir_path)) {
755 // Generate AI file paths
756 $ai_paths = self::generate_ai_file_paths($session_id, $type, $sub_type, $id, $dir_path);
757 $ai_file_path = $ai_paths['ai_file_path'];
758
759 if (file_exists($ai_file_path)) {
760 $ai_content = Utils::read_json_file($ai_file_path);
761 if (isset($ai_content['isSkipped']) && $ai_content['isSkipped']) {
762 // Return empty array for skipped content (for JS compatibility)
763 $result[$id] = [];
764 } else {
765 $result[$id] = $ai_content; // Raw AI content
766 }
767 }
768 }
769 }
770 }
771
772 return $result;
773 }
774
775 }
776