PluginProbe
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! / 3.4.0
Templately – Elementor & Gutenberg Template Library: 6500+ Free & Pro Ready Templates And Cloud! v3.4.0
3.8.0 3.7.5 3.7.4 3.7.3 3.7.2 1-final 3.7.1 3.7.0 3.6.8 3.6.7 3.6.6 3.6.5 3.6.4 3.6.3 3.6.2 3.6.1 3.0.3 3.0.4 3.0.5 3.0.6 3.0.7 3.0.8 3.0.9 3.1.0 3.1.1 All 112 releases
templately / includes / Core / Importer / Utils / AIUtils.php

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

765 lines 24.1 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
241 * Used in import_info() to return the most recent AI process
242 *
243 * @return array|null The latest AI process data or null if not found
244 */
245 public static function get_latest_ai_process_by_api_key($id) {
246 $api_key = Options::get_instance()->get('api_key');
247 if (empty($api_key)) {
248 return null;
249 }
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 $api_key_processes = [];
257
258 // Collect processes for current API key
259 foreach ($all_ai_process_data as $process_id => $process_data) {
260 if (is_array($process_data) && isset($process_data['api_key']) && $process_data['api_key'] === $api_key) {
261 $api_key_processes[$process_id] = $process_data;
262 }
263 }
264
265 if (empty($api_key_processes)) {
266 return null;
267 }
268
269 $latest_data = end($api_key_processes);
270
271 if($id != $latest_data['pack_id'] && isset($latest_data['imageReplace'])){
272 unset($latest_data['imageReplace']);
273 }
274
275 // Return the last (most recent) process
276 return $latest_data;
277 }
278
279
280
281 /**
282 * Get AI process data by session ID
283 *
284 * @param string $session_id The session ID to search for
285 * @return array|null The AI process data array or null if not found
286 */
287 public static function get_ai_process_data_by_session_id($session_id) {
288 if (empty($session_id)) {
289 return null;
290 }
291
292 $ai_process_data = self::get_ai_process_data();
293
294 foreach ($ai_process_data as $process) {
295 if (is_array($process) && isset($process['session_id']) && $process['session_id'] === $session_id) {
296 return $process;
297 }
298 }
299
300 return null;
301 }
302
303 /**
304 * Get AI process ID by session ID
305 *
306 * @param string $session_id The session ID to search for
307 * @return string|null The process ID (array key) or null if not found
308 */
309 public static function get_ai_process_id_by_session_id($session_id) {
310 if (empty($session_id)) {
311 return null;
312 }
313
314 $ai_process_data = self::get_ai_process_data();
315
316 foreach ($ai_process_data as $process_id => $process) {
317 if (is_array($process) && isset($process['session_id']) && $process['session_id'] === $session_id) {
318 return $process_id;
319 }
320 }
321
322 return null;
323 }
324
325 /**
326 * Get AI process data by process ID
327 *
328 * @param string $process_id The process ID to search for
329 * @return array|null The AI process data array or null if not found
330 */
331 public static function get_ai_process_data_by_process_id($process_id) {
332 if (empty($process_id)) {
333 return null;
334 }
335
336 $ai_process_data = self::get_ai_process_data();
337
338 if (isset($ai_process_data[$process_id]) && is_array($ai_process_data[$process_id])) {
339 return $ai_process_data[$process_id];
340 }
341
342 return null;
343 }
344
345 /**
346 * Clean AI process data by pack ID, keeping only the current process
347 * Removes all AI process entries with the same pack_id except the current process
348 *
349 * @param string $pack_id The pack ID to match for cleanup
350 * @param string $current_process_id The current process ID to preserve (optional)
351 * @return array Array of removed process IDs
352 */
353 public static function clean_ai_process_data_by_pack_id($pack_id, $current_process_id = null) {
354 if (empty($pack_id)) {
355 return [];
356 }
357
358 $all_ai_process_data = get_option('templately_ai_process_data', []);
359 if (!is_array($all_ai_process_data)) {
360 return [];
361 }
362
363 $removed_process_ids = [];
364
365 foreach ($all_ai_process_data as $process_id => $process_data) {
366 if (!is_array($process_data)) {
367 continue;
368 }
369
370 // Skip the current process by process_id if provided
371 if (!empty($current_process_id) && $process_id === $current_process_id) {
372 continue;
373 }
374
375 // Remove processes that have the same pack_id
376 if (isset($process_data['pack_id']) && $process_data['pack_id'] === $pack_id) {
377 unset($all_ai_process_data[$process_id]);
378 $removed_process_ids[] = $process_id;
379 }
380 }
381
382 // Update the options with cleaned data if any processes were removed
383 if (!empty($removed_process_ids)) {
384 update_option('templately_ai_process_data', $all_ai_process_data);
385 }
386
387 return $removed_process_ids;
388 }
389
390 /**
391 * Save template data to file
392 *
393 * Common function for saving templates to files, used by AI content operations
394 *
395 * @param string $process_id The process ID
396 * @param string $session_id The session ID
397 * @param string $content_id The content ID
398 * @param string $template The template data (base64 encoded or raw)
399 * @param array $ai_page_ids Array of AI page IDs
400 * @param bool $is_skipped Whether the template was skipped
401 * @return array|WP_Error Result array with status and data
402 */
403 public static function save_template_to_file($process_id, $session_id, $content_id, $template, $ai_page_ids, $is_skipped = false) {
404 $upload_dir = wp_upload_dir();
405
406 // Always save to tmp directory for AI content workflow
407 $tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
408
409 // Decode template if it's base64 encoded
410 if (! empty($template) && base64_decode($template, true) !== false) {
411 $template = base64_decode($template);
412 }
413
414 // Handle empty template (skipped)
415 if (empty($template)) {
416 $template = json_encode([
417 "isSkipped" => true,
418 ]);
419 }
420
421 // Find the correct directory for the content ID
422 $found_key = null;
423 foreach ($ai_page_ids as $key => $ids) {
424 if (in_array($content_id, $ids)) {
425 $found_key = $key;
426 break;
427 }
428 }
429
430 if ($found_key === null) {
431 return Helper::error('invalid_content_id', __('Content ID not found in AI page IDs.', 'templately'), 'save_template_to_file', 400);
432 }
433
434 // Create directory and file path
435 $page_dir = $tmp_dir . $found_key . DIRECTORY_SEPARATOR;
436 $file_path = $page_dir . $content_id . '.ai.json';
437 wp_mkdir_p($page_dir);
438
439 // Save the file
440 $is_success = file_put_contents($file_path, $template);
441
442 if ($is_success) {
443 // Update processed pages option
444 $processed_pages = get_option("templately_ai_processed_pages", []);
445 $processed_pages[$process_id] = $processed_pages[$process_id] ?? [];
446 $processed_pages[$process_id]['pages'][$content_id] = $is_skipped;
447 update_option("templately_ai_processed_pages", $processed_pages, false);
448
449 return [
450 'status' => 'success',
451 'data' => [
452 'process_id' => $process_id,
453 // 'file_path' => $file_path,
454 'content_id' => $content_id,
455 ],
456 ];
457 }
458
459 return Helper::error('file_save_failed', __('Failed to save template file.', 'templately'), 'save_template_to_file', 500);
460 }
461
462 /**
463 * Get matched session data by process ID
464 *
465 * Helper function to retrieve session data for a given process ID
466 *
467 * @param string $process_id The process ID to match
468 * @return array|false Returns matched data array or false if not found
469 */
470 public static function get_matched_session_data($process_id) {
471 if (empty($process_id)) {
472 return false;
473 }
474
475 $all_data = Utils::get_all_session_data();
476
477 if (empty($all_data) || ! is_array($all_data)) {
478 return false;
479 }
480
481 // INSERT_YOUR_CODE
482 if (is_array($all_data)) {
483 $all_data = array_reverse($all_data);
484 }
485 foreach ($all_data as $data) {
486 if (isset($data['process_id']) && ($data['process_id'] === $process_id)) {
487 return $data;
488 }
489 }
490
491 return false;
492 }
493
494 /**
495 * Generate AI file paths for content processing
496 *
497 * @param string $session_id The session ID
498 * @param string $type The content type (templates, content, etc.)
499 * @param string $sub_type The content sub-type (page, post, etc.)
500 * @param string $template_id The template ID
501 * @param string $dir_path The base directory path for original files
502 * @return array Array containing paths for original and AI files
503 */
504 public static function generate_ai_file_paths($session_id, $type, $sub_type, $template_id, $dir_path) {
505 // Original file path
506 $original_path = $dir_path . $type . DIRECTORY_SEPARATOR;
507 if (!empty($sub_type)) {
508 $original_path .= $sub_type . DIRECTORY_SEPARATOR;
509 }
510 $original_file = $original_path . "{$template_id}.json";
511
512 // AI file path in tmp directory
513 $upload_dir = wp_upload_dir();
514 $tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
515 $ai_path = $tmp_dir . $type . DIRECTORY_SEPARATOR;
516 if (!empty($sub_type)) {
517 $ai_path .= $sub_type . DIRECTORY_SEPARATOR;
518 }
519 $ai_file = $ai_path . "{$template_id}.ai.json";
520
521 return [
522 'original_file' => $original_file,
523 'ai_file_path' => $ai_file,
524 'ai_directory' => $ai_path,
525 'tmp_directory' => $tmp_dir,
526 ];
527 }
528
529 /**
530 * Get AI tmp directory path for a session
531 *
532 * @param string $session_id The session ID
533 * @return string The tmp directory path
534 */
535 public static function get_ai_tmp_directory($session_id) {
536 $upload_dir = wp_upload_dir();
537 return trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
538 }
539
540 /**
541 * Get AI file path for specific content
542 *
543 * @param string $session_id The session ID
544 * @param string $type The content type
545 * @param string $sub_type The content sub-type (optional)
546 * @param string $content_id The content ID
547 * @return string The AI file path
548 */
549 public static function get_ai_file_path($session_id, $type, $sub_type, $content_id) {
550 $tmp_dir = self::get_ai_tmp_directory($session_id);
551 $file_path = $tmp_dir . $type . DIRECTORY_SEPARATOR;
552 if (!empty($sub_type)) {
553 $file_path .= $sub_type . DIRECTORY_SEPARATOR;
554 }
555 return $file_path . "{$content_id}.ai.json";
556 }
557
558 /**
559 * Check if AI file exists and is valid
560 *
561 * @param string $ai_file_path The AI file path
562 * @return bool True if AI file exists and is valid
563 */
564 public static function has_ai_file($ai_file_path) {
565 if (!file_exists($ai_file_path)) {
566 return false;
567 }
568
569 $file_content = file_get_contents($ai_file_path);
570 if (empty($file_content)) {
571 return false;
572 }
573
574 $decoded = json_decode($file_content, true);
575 return json_last_error() === JSON_ERROR_NONE && !empty($decoded);
576 }
577
578 /**
579 * Check if AI file is marked as skipped
580 *
581 * @param string $ai_file_path The AI file path
582 * @return bool True if AI file exists and is marked as skipped
583 */
584 public static function is_ai_file_skipped($ai_file_path) {
585 if (!file_exists($ai_file_path)) {
586 return false;
587 }
588
589 $ai_content = Utils::read_json_file($ai_file_path);
590 return isset($ai_content['isSkipped']) && $ai_content['isSkipped'];
591 }
592
593 /**
594 * Check if content ID is in AI page IDs
595 *
596 * @param string $content_id The content ID to check
597 * @param array $ai_page_ids The AI page IDs structure
598 * @return bool True if content ID is found in AI page IDs
599 */
600 public static function is_ai_content($content_id, $ai_page_ids) {
601 if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
602 return false;
603 }
604
605 foreach ($ai_page_ids as $ids) {
606 if (is_array($ids) && in_array($content_id, $ids)) {
607 return true;
608 }
609 }
610
611 return false;
612 }
613
614 /**
615 * Check if content should be processed as AI content
616 *
617 * @param string $session_id The session ID
618 * @param string $type The content type
619 * @param string $sub_type The content sub-type
620 * @param string $content_id The content ID
621 * @param array $ai_page_ids The AI page IDs structure
622 * @param string $dir_path The base directory path
623 * @return bool True if this is AI content
624 */
625 public static function should_process_as_ai_content($session_id, $type, $sub_type, $content_id, $ai_page_ids, $dir_path) {
626 // Check if content ID is in AI page IDs list
627 $is_ai_template = self::is_ai_content($content_id, $ai_page_ids);
628
629 // Check if AI file exists
630 $paths = self::generate_ai_file_paths($session_id, $type, $sub_type, $content_id, $dir_path);
631 $has_ai_file = self::has_ai_file($paths['ai_file_path']);
632
633 return $is_ai_template || $has_ai_file;
634 }
635
636 /**
637 * Validate and extract AI process data for a given process ID
638 *
639 * @param string $process_id The process ID
640 * @return array|WP_Error Returns process data array or WP_Error on failure
641 */
642 public static function validate_and_get_process_data($process_id) {
643 if (empty($process_id)) {
644 return Helper::error('invalid_process_id', __('Process ID is required.', 'templately'), 'validate_process_data', 400);
645 }
646
647 $ai_process_data = self::get_ai_process_data();
648 if (empty($ai_process_data[$process_id])) {
649 return Helper::error('process_not_found', __('Process ID not found.', 'templately'), 'validate_process_data', 404);
650 }
651
652 $process_data = $ai_process_data[$process_id];
653
654 // Validate required fields
655 if (empty($process_data['session_id'])) {
656 return Helper::error('missing_session_id', __('Session ID missing from process data.', 'templately'), 'validate_process_data', 400);
657 }
658
659 if (empty($process_data['ai_page_ids'])) {
660 return Helper::error('missing_ai_page_ids', __('AI page IDs missing from process data.', 'templately'), 'validate_process_data', 400);
661 }
662
663 return $process_data;
664 }
665
666 /**
667 * Get processed pages data for a process ID
668 *
669 * @param string $process_id The process ID
670 * @return array The processed pages data
671 */
672 public static function get_processed_pages_data($process_id) {
673 $processed_pages = get_option("templately_ai_processed_pages", []);
674 return $processed_pages[$process_id] ?? [];
675 }
676
677 /**
678 * Update processed pages data for a process ID
679 *
680 * @param string $process_id The process ID
681 * @param array $data The data to update
682 * @return bool True on success, false on failure
683 */
684 public static function update_processed_pages_data($process_id, $data) {
685 $processed_pages = get_option("templately_ai_processed_pages", []);
686 $processed_pages[$process_id] = array_merge($processed_pages[$process_id] ?? [], $data);
687 return update_option("templately_ai_processed_pages", $processed_pages, false);
688 }
689
690 /**
691 * Find content type and sub-type for a given content ID in AI page IDs
692 *
693 * @param string $content_id The content ID to find
694 * @param array $ai_page_ids The AI page IDs structure
695 * @return array|null Array with 'type' and 'sub_type' keys, or null if not found
696 */
697 public static function find_content_type_info($content_id, $ai_page_ids) {
698 if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
699 return null;
700 }
701
702 foreach ($ai_page_ids as $key => $ids) {
703 if (is_array($ids) && in_array($content_id, $ids)) {
704 $type_parts = explode('/', $key);
705 return [
706 'type' => $type_parts[0],
707 'sub_type' => isset($type_parts[1]) ? $type_parts[1] : '',
708 'key' => $key
709 ];
710 }
711 }
712
713 return null;
714 }
715
716 /**
717 * Read AI template data directly from files
718 * Common function for reading template data, used by AI content operations
719 *
720 * @param string $session_id The session ID
721 * @param array $ai_page_ids The AI page IDs structure
722 * @param string $dir_path The base directory path
723 * @return array Array of template data indexed by content ID
724 */
725 public static function read_ai_template_data($session_id, $ai_page_ids, $dir_path) {
726 $result = [];
727
728 if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
729 return $result;
730 }
731
732 foreach ($ai_page_ids as $key => $ids) {
733 if (!is_array($ids)) {
734 continue;
735 }
736
737 foreach ($ids as $id) {
738 $type_arr = explode('/', $key);
739 $type = $type_arr[0];
740 $sub_type = isset($type_arr[1]) ? $type_arr[1] : '';
741
742 // Check if this is AI content before processing
743 if (self::should_process_as_ai_content($session_id, $type, $sub_type, $id, $ai_page_ids, $dir_path)) {
744 // Generate AI file paths
745 $ai_paths = self::generate_ai_file_paths($session_id, $type, $sub_type, $id, $dir_path);
746 $ai_file_path = $ai_paths['ai_file_path'];
747
748 if (file_exists($ai_file_path)) {
749 $ai_content = Utils::read_json_file($ai_file_path);
750 if (isset($ai_content['isSkipped']) && $ai_content['isSkipped']) {
751 // Return empty array for skipped content (for JS compatibility)
752 $result[$id] = [];
753 } else {
754 $result[$id] = $ai_content; // Raw AI content
755 }
756 }
757 }
758 }
759 }
760
761 return $result;
762 }
763
764 }
765