<?php
namespace Templately\Core\Importer\Utils;
use Templately\Utils\Options;
use Templately\Utils\Helper;
/**
* AI-related utility functions for Templately
* Handles AI process data management using API key-based storage with count-based cleanup
*/
class AIUtils {
/**
* Sanitize path component for security - prevents path traversal attacks
*
* @param mixed $value The value to sanitize (string or numeric)
* @param string $type Type for error message ('session_id', 'content_id', 'path_key')
* @return string|WP_Error Sanitized value or error if invalid
*/
public static function sanitize_path_component($value, $type = 'path_component') {
// Convert numeric values to string (content IDs like 136 are valid)
if (is_numeric($value)) {
$value = (string) $value;
}
// Check for empty or non-string values
if (empty($value) || !is_string($value)) {
return Helper::error(
'invalid_' . $type,
sprintf(__('Invalid %s: empty or not a valid value.', 'templately'), $type),
'sanitize_path_component',
400
);
}
// Check for path traversal attempts BEFORE sanitizing
if (strpos($value, '..') !== false ||
strpos($value, '/') !== false ||
strpos($value, '\\') !== false) {
return Helper::error(
'invalid_' . $type,
sprintf(__('Invalid %s: contains path separators.', 'templately'), $type),
'sanitize_path_component',
400
);
}
// Apply WordPress sanitize_file_name for additional safety
return sanitize_file_name($value);
}
/**
* Validate that a file path is within WordPress upload directory
* Prevents path traversal attacks
*
* @param string $file_path The file path to validate
* @return bool|WP_Error True if valid, WP_Error otherwise
*/
public static function validate_file_path($file_path) {
// Get WordPress upload directory
$upload_dir = wp_upload_dir();
$real_upload_base = realpath($upload_dir['basedir']);
if ($real_upload_base === false) {
return Helper::error(
'invalid_upload_dir',
__('WordPress upload directory is not accessible.', 'templately'),
'validate_file_path',
500
);
}
// For file path, check the directory if file doesn't exist yet
$dir_to_check = dirname($file_path);
// Create directory if it doesn't exist (for pre-write validation)
if (!file_exists($dir_to_check)) {
wp_mkdir_p($dir_to_check);
}
$real_path = realpath($dir_to_check);
if ($real_path === false) {
return Helper::error(
'path_not_exists',
__('File path does not exist or is not accessible.', 'templately'),
'validate_file_path',
400
);
}
// Normalize paths with trailing directory separator
$normalized_upload_base = rtrim($real_upload_base, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$normalized_path = rtrim($real_path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
// Check if path starts with upload directory (PHP 8+ style with polyfill)
// This prevents partial matches like /var/www/uploads-backup matching /var/www/uploads
$is_subdirectory = false;
if (function_exists('str_starts_with')) {
// PHP 8+
$is_subdirectory = str_starts_with($normalized_path, $normalized_upload_base);
} else {
// PHP 7.4 polyfill
$is_subdirectory = substr($normalized_path, 0, strlen($normalized_upload_base)) === $normalized_upload_base;
}
if (!$is_subdirectory) {
return Helper::error(
'path_outside_uploads',
__('Invalid file path: path is outside WordPress upload directory.', 'templately'),
'validate_file_path',
400
);
}
return true;
}
/**
* Handle SSE message wait with timeout exit condition
* Static utility function for reusable timeout handling across different import contexts
*
* @param string $session_id Session ID for progress tracking
* @param string $progress_id Progress tracking identifier (e.g., 'ai_content_time', 'finalize_time')
* @param array $updated_ids Currently updated/processed pages
* @param array $ai_page_ids All AI pages that need processing
* @param callable $sse_message_callback Callback function for sending SSE messages
* @param array $additional_sse_data Additional data to include in SSE message
* @param string|null $old_template_id Optional template ID for local site polling
* @return bool True if should continue processing, false if should exit
*/
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, $timeout_seconds = 420) {
$total_pages = count($ai_page_ids);
$updated_pages = count($updated_ids['pages'] ?? []);
// If all pages are processed or credit cost is available, continue processing
if ($total_pages <= $updated_pages || isset($updated_ids['credit_cost'])) {
return true;
}
// Get timeout tracking data from session
$session_data = Utils::get_session_data($session_id);
$progress_data = $session_data['progress'][$progress_id] ?? [];
$last_progress = $progress_data['last_progress'] ?? 0;
$last_time = $progress_data['last_time'] ?? 0;
$current_time = time();
$progress_percentage = $total_pages > 0 ? round(($updated_pages / $total_pages) * 100) : 0;
$is_local_site = $session_data['isLocalSite'] ?? false;
// Skip timeout check if credit cost is available and time difference is > 10 seconds
if (isset($updated_ids['credit_cost']) && !empty($last_time) && ($current_time - $last_time) > 10) {
return true;
}
// Check if time difference is less than timeout(default 7 minutes) (timeout condition)
if (empty($last_time) || ($current_time - $last_time) < $timeout_seconds) {
// For local sites with template ID, attempt polling before waiting
if ($is_local_site && !empty($old_template_id)) {
$process_id = $session_data['process_id'] ?? null;
if (!empty($process_id)) {
// Call generic polling function to process all available templates
$polling_result = self::poll_for_template($process_id, $session_id, $ai_page_ids);
if ($polling_result === true) {
// Check if the specific template file now exists after polling
$upload_dir = wp_upload_dir();
$tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
// Find the correct directory for the template ID
$template_file_found = false;
foreach ($ai_page_ids as $key => $ids) {
if (in_array($old_template_id, $ids)) {
$page_dir = $tmp_dir . $key . DIRECTORY_SEPARATOR;
$file_path = $page_dir . $old_template_id . '.ai.json';
if (file_exists($file_path)) {
$template_file_found = true;
break;
}
}
}
if ($template_file_found) {
// Specific template found and downloaded, continue processing
return true;
}
}
}
}
// Only update time if progress has changed
if ($progress_percentage !== $last_progress) {
$updated_progress = $session_data['progress'] ?? [];
$updated_progress[$progress_id] = [
'last_progress' => $progress_percentage,
'last_time' => $current_time,
];
Utils::update_session_data($session_id, ['progress' => $updated_progress]);
}
// Prepare SSE message data
$sse_data = array_merge([
'type' => 'wait',
'action' => 'wait',
'generated_pages' => $updated_ids,
'all_pages' => $ai_page_ids,
], $additional_sse_data);
// Send wait message and exit
call_user_func($sse_message_callback, $sse_data);
exit;
}
// Timeout exceeded - could send error message here if needed
// Currently commented out in original AIContent.php
/*
call_user_func($sse_message_callback, [
'action' => 'error',
'status' => 'error',
'type' => "error",
'title' => __("Oops!", "templately"),
'message' => __("Taking too long....", "templately"),
]);
exit;
*/
// Continue processing if timeout exceeded (current behavior)
return true;
}
/**
* Poll for AI template generation status on local sites
* Generic polling utility that processes all available templates
*
* @param string $process_id The AI process ID
* @param string $session_id The session ID
* @param array $ai_page_ids The AI page IDs structure
* @return bool True if polling was successful, false otherwise
*/
public static function poll_for_template($process_id, $session_id, $ai_page_ids) {
// First check if polling is already complete
$processed_pages = get_option("templately_ai_processed_pages", []);
if (isset($processed_pages[$process_id]['is_last_part']) && $processed_pages[$process_id]['is_last_part']) {
return true;
}
// Make GET request to API endpoint
$response = Helper::make_api_get_request("v2/ai/{$process_id}/template", [], [], 30);
if (is_wp_error($response)) {
return false;
}
$response_code = wp_remote_retrieve_response_code($response);
if ($response_code !== 200) {
return false;
}
$body = wp_remote_retrieve_body($response);
$api_data = json_decode($body, true);
if (!isset($api_data['status']) || $api_data['status'] !== 'success') {
return false;
}
$response_data = $api_data['data'] ?? [];
// Extract required fields from API response
$is_last_part = $response_data['is_last_part'] ?? false;
$credit_cost = $response_data['credit_cost'] ?? 0;
$templates = $response_data['templates'] ?? [];
// Save credit_cost if available
if ($credit_cost > 0) {
$processed_pages = get_option("templately_ai_processed_pages", []);
$processed_pages[$process_id] = $processed_pages[$process_id] ?? [];
$processed_pages[$process_id]['credit_cost'] = $credit_cost;
update_option("templately_ai_processed_pages", $processed_pages, false);
}
// Save is_last_part if true
if ($is_last_part) {
$processed_pages = get_option("templately_ai_processed_pages", []);
$processed_pages[$process_id] = $processed_pages[$process_id] ?? [];
$processed_pages[$process_id]['is_last_part'] = $is_last_part;
update_option("templately_ai_processed_pages", $processed_pages, false);
}
// Process ALL templates if available (extracted from FullSiteImport::ai_poll_template)
if (!empty($templates) && is_array($templates)) {
foreach ($templates as $content_id => $template_data) {
// Save template to file using the common helper function
$result = self::save_template_to_file(
$process_id,
$session_id,
$content_id,
$template_data,
$ai_page_ids,
false // Not skipped
);
// Continue processing other templates even if one fails
}
}
return true; // Return true if polling was successful, regardless of specific templates
}
/**
* Get AI process data from WordPress options
*
* @return array The AI process data array
*/
public static function get_ai_process_data() {
$all_ai_process_data = get_option('templately_ai_process_data', []);
return is_array($all_ai_process_data) ? $all_ai_process_data : [];
}
/**
* Update AI process data in WordPress options with process_id as key
* Appends to existing data
*
* @param array $data The AI process data to save (process_id => process_data)
* @return bool True on success, false on failure
*/
public static function update_ai_process_data($data) {
if (!is_array($data)) {
return false;
}
// Get existing data
$all_ai_process_data = get_option('templately_ai_process_data', []);
if (!is_array($all_ai_process_data)) {
$all_ai_process_data = [];
}
// Append new data to existing data
foreach ($data as $process_id => $process_data) {
if (is_array($process_data)) {
$all_ai_process_data[$process_id] = $process_data;
}
}
return update_option('templately_ai_process_data', $all_ai_process_data);
}
/**
* Get the latest AI process data for the current API key or user ID
* Used in import_info() to return the most recent AI process
* Priority: api_key first, then user_id as fallback
*
* @return array|null The latest AI process data or null if not found
*/
public static function get_latest_ai_process_by_api_key($id) {
$api_key = Options::get_instance()->get('api_key');
$user = Options::get_instance()->get('user');
$user_id = isset($user['id']) ? $user['id'] : null;
$all_ai_process_data = get_option('templately_ai_process_data', []);
if (!is_array($all_ai_process_data)) {
return null;
}
$matching_processes = [];
// Priority 1: Try to find processes by API key if available
if (!empty($api_key)) {
foreach ($all_ai_process_data as $process_id => $process_data) {
if (is_array($process_data) && isset($process_data['api_key']) && $process_data['api_key'] === $api_key) {
$matching_processes[$process_id] = $process_data;
}
}
}
// Priority 2: If no API key matches found or API key is empty, fallback to user_id
if (empty($matching_processes) && !empty($user_id)) {
foreach ($all_ai_process_data as $process_id => $process_data) {
if (is_array($process_data) && isset($process_data['user_id']) && $process_data['user_id'] === $user_id) {
$matching_processes[$process_id] = $process_data;
}
}
}
if (empty($matching_processes)) {
return null;
}
$latest_data = end($matching_processes);
if($id != $latest_data['pack_id'] && isset($latest_data['imageReplace'])){
unset($latest_data['imageReplace']);
}
// Return the last (most recent) process
return $latest_data;
}
/**
* Get AI process data by session ID
*
* @param string $session_id The session ID to search for
* @return array|null The AI process data array or null if not found
*/
public static function get_ai_process_data_by_session_id($session_id) {
if (empty($session_id)) {
return null;
}
$ai_process_data = self::get_ai_process_data();
foreach ($ai_process_data as $process) {
if (is_array($process) && isset($process['session_id']) && $process['session_id'] === $session_id) {
return $process;
}
}
return null;
}
/**
* Get AI process ID by session ID
*
* @param string $session_id The session ID to search for
* @return string|null The process ID (array key) or null if not found
*/
public static function get_ai_process_id_by_session_id($session_id) {
if (empty($session_id)) {
return null;
}
$ai_process_data = self::get_ai_process_data();
foreach ($ai_process_data as $process_id => $process) {
if (is_array($process) && isset($process['session_id']) && $process['session_id'] === $session_id) {
return $process_id;
}
}
return null;
}
/**
* Get AI process data by process ID
*
* @param string $process_id The process ID to search for
* @return array|null The AI process data array or null if not found
*/
public static function get_ai_process_data_by_process_id($process_id) {
if (empty($process_id)) {
return null;
}
$ai_process_data = self::get_ai_process_data();
if (isset($ai_process_data[$process_id]) && is_array($ai_process_data[$process_id])) {
return $ai_process_data[$process_id];
}
return null;
}
/**
* Clean AI process data by pack ID, keeping only the current process
* Removes all AI process entries with the same pack_id except the current process
*
* @param string $pack_id The pack ID to match for cleanup
* @param string $current_process_id The current process ID to preserve (optional)
* @return array Array of removed process IDs
*/
public static function clean_ai_process_data_by_pack_id($pack_id, $current_process_id = null) {
if (empty($pack_id)) {
return [];
}
$all_ai_process_data = get_option('templately_ai_process_data', []);
if (!is_array($all_ai_process_data)) {
return [];
}
$removed_process_ids = [];
foreach ($all_ai_process_data as $process_id => $process_data) {
if (!is_array($process_data)) {
continue;
}
// Skip the current process by process_id if provided
if (!empty($current_process_id) && $process_id === $current_process_id) {
continue;
}
// Remove processes that have the same pack_id
if (isset($process_data['pack_id']) && $process_data['pack_id'] === $pack_id) {
unset($all_ai_process_data[$process_id]);
$removed_process_ids[] = $process_id;
}
}
// Update the options with cleaned data if any processes were removed
if (!empty($removed_process_ids)) {
update_option('templately_ai_process_data', $all_ai_process_data);
}
return $removed_process_ids;
}
/**
* Save template data to file
*
* Common function for saving templates to files, used by AI content operations
*
* @param string $process_id The process ID
* @param string $session_id The session ID
* @param string $content_id The content ID
* @param string $template The template data (base64 encoded or raw)
* @param array $ai_page_ids Array of AI page IDs
* @param bool $is_skipped Whether the template was skipped
* @return array|WP_Error Result array with status and data
*/
public static function save_template_to_file($process_id, $session_id, $content_id, $template, $ai_page_ids, $is_skipped = false) {
// Security: Sanitize session_id
$session_id = self::sanitize_path_component($session_id, 'session_id');
if (is_wp_error($session_id)) {
return $session_id;
}
// Security: Sanitize content_id
$content_id = self::sanitize_path_component($content_id, 'content_id');
if (is_wp_error($content_id)) {
return $content_id;
}
$upload_dir = wp_upload_dir();
// Always save to tmp directory for AI content workflow
$tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
// Decode template if it's base64 encoded
if (! empty($template) && base64_decode($template, true) !== false) {
$template = base64_decode($template);
}
// Handle empty template (skipped)
if (empty($template)) {
$template = json_encode([
"isSkipped" => true,
]);
}
// Find the correct directory for the content ID
$found_key = null;
foreach ($ai_page_ids as $key => $ids) {
if (in_array($content_id, $ids)) {
$found_key = $key;
break;
}
}
if ($found_key === null) {
return Helper::error('invalid_content_id', __('Content ID not found in AI page IDs.', 'templately'), 'save_template_to_file', 400);
}
// Security: Sanitize found_key parts (e.g., "templates/page" -> sanitize each part)
$key_parts = explode('/', $found_key);
$sanitized_parts = [];
foreach ($key_parts as $part) {
$sanitized = self::sanitize_path_component($part, 'path_key');
if (is_wp_error($sanitized)) {
return $sanitized;
}
$sanitized_parts[] = $sanitized;
}
$found_key = implode(DIRECTORY_SEPARATOR, $sanitized_parts);
// Create directory and file path
$page_dir = $tmp_dir . $found_key . DIRECTORY_SEPARATOR;
$file_path = $page_dir . $content_id . '.ai.json';
// Security: Validate path is within expected directory before writing
$validation = self::validate_file_path($file_path);
if (is_wp_error($validation)) {
return $validation;
}
wp_mkdir_p($page_dir);
// Save the file
$is_success = file_put_contents($file_path, $template);
if ($is_success) {
// Update processed pages option
$processed_pages = get_option("templately_ai_processed_pages", []);
$processed_pages[$process_id] = $processed_pages[$process_id] ?? [];
$processed_pages[$process_id]['pages'][$content_id] = $is_skipped;
update_option("templately_ai_processed_pages", $processed_pages, false);
return [
'status' => 'success',
'data' => [
'process_id' => $process_id,
// 'file_path' => $file_path,
'content_id' => $content_id,
],
];
}
return Helper::error('file_save_failed', __('Failed to save template file.', 'templately'), 'save_template_to_file', 500);
}
/**
* Get matched session data by process ID
*
* Helper function to retrieve session data for a given process ID
*
* @param string $process_id The process ID to match
* @return array|false Returns matched data array or false if not found
*/
public static function get_matched_session_data($process_id) {
if (empty($process_id)) {
return false;
}
$all_data = Utils::get_all_session_data();
if (empty($all_data) || ! is_array($all_data)) {
return false;
}
// INSERT_YOUR_CODE
if (is_array($all_data)) {
$all_data = array_reverse($all_data);
}
foreach ($all_data as $data) {
if (isset($data['process_id']) && ($data['process_id'] === $process_id)) {
return $data;
}
}
return false;
}
/**
* Generate AI file paths for content processing
*
* @param string $session_id The session ID
* @param string $type The content type (templates, content, etc.)
* @param string $sub_type The content sub-type (page, post, etc.)
* @param string $template_id The template ID
* @param string $dir_path The base directory path for original files
* @return array Array containing paths for original and AI files
*/
public static function generate_ai_file_paths($session_id, $type, $sub_type, $template_id, $dir_path) {
// Original file path
$original_path = $dir_path . $type . DIRECTORY_SEPARATOR;
if (!empty($sub_type)) {
$original_path .= $sub_type . DIRECTORY_SEPARATOR;
}
$original_file = $original_path . "{$template_id}.json";
// AI file path in tmp directory
$upload_dir = wp_upload_dir();
$tmp_dir = trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
$ai_path = $tmp_dir . $type . DIRECTORY_SEPARATOR;
if (!empty($sub_type)) {
$ai_path .= $sub_type . DIRECTORY_SEPARATOR;
}
$ai_file = $ai_path . "{$template_id}.ai.json";
return [
'original_file' => $original_file,
'ai_file_path' => $ai_file,
'ai_directory' => $ai_path,
'tmp_directory' => $tmp_dir,
];
}
/**
* Get AI tmp directory path for a session
*
* @param string $session_id The session ID
* @return string The tmp directory path
*/
public static function get_ai_tmp_directory($session_id) {
$upload_dir = wp_upload_dir();
return trailingslashit($upload_dir['basedir']) . 'templately' . DIRECTORY_SEPARATOR . 'tmp' . DIRECTORY_SEPARATOR . $session_id . DIRECTORY_SEPARATOR;
}
/**
* Get AI file path for specific content
*
* @param string $session_id The session ID
* @param string $type The content type
* @param string $sub_type The content sub-type (optional)
* @param string $content_id The content ID
* @return string The AI file path
*/
public static function get_ai_file_path($session_id, $type, $sub_type, $content_id) {
$tmp_dir = self::get_ai_tmp_directory($session_id);
$file_path = $tmp_dir . $type . DIRECTORY_SEPARATOR;
if (!empty($sub_type)) {
$file_path .= $sub_type . DIRECTORY_SEPARATOR;
}
return $file_path . "{$content_id}.ai.json";
}
/**
* Check if AI file exists and is valid
*
* @param string $ai_file_path The AI file path
* @return bool True if AI file exists and is valid
*/
public static function has_ai_file($ai_file_path) {
if (!file_exists($ai_file_path)) {
return false;
}
$file_content = file_get_contents($ai_file_path);
if (empty($file_content)) {
return false;
}
$decoded = json_decode($file_content, true);
return json_last_error() === JSON_ERROR_NONE && !empty($decoded);
}
/**
* Check if AI file is marked as skipped
*
* @param string $ai_file_path The AI file path
* @return bool True if AI file exists and is marked as skipped
*/
public static function is_ai_file_skipped($ai_file_path) {
if (!file_exists($ai_file_path)) {
return false;
}
$ai_content = Utils::read_json_file($ai_file_path);
return isset($ai_content['isSkipped']) && $ai_content['isSkipped'];
}
/**
* Check if content ID is in AI page IDs
*
* @param string $content_id The content ID to check
* @param array $ai_page_ids The AI page IDs structure
* @return bool True if content ID is found in AI page IDs
*/
public static function is_ai_content($content_id, $ai_page_ids) {
if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
return false;
}
foreach ($ai_page_ids as $ids) {
if (is_array($ids) && in_array($content_id, $ids)) {
return true;
}
}
return false;
}
/**
* Check if content should be processed as AI content
*
* @param string $session_id The session ID
* @param string $type The content type
* @param string $sub_type The content sub-type
* @param string $content_id The content ID
* @param array $ai_page_ids The AI page IDs structure
* @param string $dir_path The base directory path
* @return bool True if this is AI content
*/
public static function should_process_as_ai_content($session_id, $type, $sub_type, $content_id, $ai_page_ids, $dir_path) {
// Check if content ID is in AI page IDs list
$is_ai_template = self::is_ai_content($content_id, $ai_page_ids);
// Check if AI file exists
$paths = self::generate_ai_file_paths($session_id, $type, $sub_type, $content_id, $dir_path);
$has_ai_file = self::has_ai_file($paths['ai_file_path']);
return $is_ai_template || $has_ai_file;
}
/**
* Validate and extract AI process data for a given process ID
*
* @param string $process_id The process ID
* @return array|WP_Error Returns process data array or WP_Error on failure
*/
public static function validate_and_get_process_data($process_id) {
if (empty($process_id)) {
return Helper::error('invalid_process_id', __('Process ID is required.', 'templately'), 'validate_process_data', 400);
}
$ai_process_data = self::get_ai_process_data();
if (empty($ai_process_data[$process_id])) {
return Helper::error('process_not_found', __('Process ID not found.', 'templately'), 'validate_process_data', 404);
}
$process_data = $ai_process_data[$process_id];
// Validate required fields
if (empty($process_data['session_id'])) {
return Helper::error('missing_session_id', __('Session ID missing from process data.', 'templately'), 'validate_process_data', 400);
}
if (empty($process_data['ai_page_ids'])) {
return Helper::error('missing_ai_page_ids', __('AI page IDs missing from process data.', 'templately'), 'validate_process_data', 400);
}
return $process_data;
}
/**
* Get processed pages data for a process ID
*
* @param string $process_id The process ID
* @return array The processed pages data
*/
public static function get_processed_pages_data($process_id) {
$processed_pages = get_option("templately_ai_processed_pages", []);
return $processed_pages[$process_id] ?? [];
}
/**
* Update processed pages data for a process ID
*
* @param string $process_id The process ID
* @param array $data The data to update
* @return bool True on success, false on failure
*/
public static function update_processed_pages_data($process_id, $data) {
$processed_pages = get_option("templately_ai_processed_pages", []);
$processed_pages[$process_id] = array_merge($processed_pages[$process_id] ?? [], $data);
return update_option("templately_ai_processed_pages", $processed_pages, false);
}
/**
* Find content type and sub-type for a given content ID in AI page IDs
*
* @param string $content_id The content ID to find
* @param array $ai_page_ids The AI page IDs structure
* @return array|null Array with 'type' and 'sub_type' keys, or null if not found
*/
public static function find_content_type_info($content_id, $ai_page_ids) {
if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
return null;
}
foreach ($ai_page_ids as $key => $ids) {
if (is_array($ids) && in_array($content_id, $ids)) {
$type_parts = explode('/', $key);
return [
'type' => $type_parts[0],
'sub_type' => isset($type_parts[1]) ? $type_parts[1] : '',
'key' => $key
];
}
}
return null;
}
/**
* Read AI template data directly from files
* Common function for reading template data, used by AI content operations
*
* @param string $session_id The session ID
* @param array $ai_page_ids The AI page IDs structure
* @param string $dir_path The base directory path
* @return array Array of template data indexed by content ID
*/
public static function read_ai_template_data($session_id, $ai_page_ids, $dir_path) {
$result = [];
if (empty($ai_page_ids) || !is_array($ai_page_ids)) {
return $result;
}
foreach ($ai_page_ids as $key => $ids) {
if (!is_array($ids)) {
continue;
}
foreach ($ids as $id) {
$type_arr = explode('/', $key);
$type = $type_arr[0];
$sub_type = isset($type_arr[1]) ? $type_arr[1] : '';
// Check if this is AI content before processing
if (self::should_process_as_ai_content($session_id, $type, $sub_type, $id, $ai_page_ids, $dir_path)) {
// Generate AI file paths
$ai_paths = self::generate_ai_file_paths($session_id, $type, $sub_type, $id, $dir_path);
$ai_file_path = $ai_paths['ai_file_path'];
if (file_exists($ai_file_path)) {
$ai_content = Utils::read_json_file($ai_file_path);
if (isset($ai_content['isSkipped']) && $ai_content['isSkipped']) {
// Return empty array for skipped content (for JS compatibility)
$result[$id] = [];
} else {
$result[$id] = $ai_content; // Raw AI content
}
}
}
}
}
return $result;
}
}