__DIR__ . '/aismart-core/vendor/autoload.php', 'bootstrap' => __DIR__ . '/aismart-core/bootstrap/app.php', ]; } function aismart_missing_dependencies() { $paths = aismart_dependency_paths(); $missing = []; foreach ($paths as $key => $path) { if (!file_exists($path)) { $missing[$key] = $path; } } return $missing; } function aismart_get_recovery_command() { return 'cd wp-content/plugins/aismart-token/aismart-core && composer install --no-dev --optimize-autoloader --no-interaction'; } function aismart_get_recovery_steps($manual_command = '') { $manual_command = trim((string) $manual_command); if ($manual_command === '') { $manual_command = aismart_get_recovery_command(); } $parts = preg_split('/\s*&&\s*/', $manual_command); if (!is_array($parts)) { $parts = []; } $steps = []; foreach ($parts as $part) { $line = trim((string) $part); if ($line !== '') { $steps[] = $line; } } if (count($steps) < 2) { $steps = [ 'cd wp-content/plugins/aismart-token/aismart-core', 'composer install --no-dev --optimize-autoloader --no-interaction', ]; } return $steps; } function aismart_get_activation_url($run_composer = false) { $plugin = plugin_basename(__FILE__); $url = wp_nonce_url( admin_url('plugins.php?action=activate&plugin=' . rawurlencode($plugin)), 'activate-plugin_' . $plugin ); if ($run_composer) { $url = add_query_arg('aismart_run_composer_install', '1', $url); } return $url; } function aismart_render_recovery_panel_html($args = []) { $defaults = [ 'title' => 'AISmart Content Studio setup needs one more step', 'intro' => 'Some required files are missing, so WordPress paused activation to keep your site safe.', 'button_label' => 'Run Automatic Fix (Composer Install)', 'button_url' => '', 'manual_command' => '', 'missing_files_html' => '', 'output_html' => '', 'show_no_ssh_help' => true, ]; $data = wp_parse_args($args, $defaults); $button_url = $data['button_url'] !== '' ? '
' . esc_html($data['button_label']) . '
' : ''; $manual = ''; if ($data['manual_command'] !== '') { $steps = aismart_get_recovery_steps($data['manual_command']); $steps_html = ''; foreach ($steps as $idx => $step_line) { $steps_html .= '' . esc_html($step_line) . 'Manual command (SSH/Terminal):
' . '' . esc_html($data['manual_command']) . '
Beginner step-by-step (run one line at a time):
' . 'Missing files:
No SSH access? Use this beginner path:
' . '' . esc_html($data['intro']) . '
' . 'Quick steps for admin:
' . 'Composer is missing in server shell.
' . 'Use these copy-ready commands in cPanel Terminal:
' . '' . 'Why auto-fix cannot run from this button:
' . 'This hosting environment disables PHP shell execution functions used by the WordPress button (exec/system/shell_exec), so retrying this button will fail until hosting enables shell functions.
PHP disable_functions: ' . esc_html($disabled_display) . '
Use these copy-ready commands in cPanel Terminal instead:
' . '' . 'Install log:
' . esc_html($result['output']) . '' : ''; $composer_help = (isset($result['error_type']) && $result['error_type'] === 'composer_missing' && !empty($result['composer_help_html'])) ? (string) $result['composer_help_html'] : ''; $shell_help = (isset($result['error_type']) && $result['error_type'] === 'shell_disabled' && !empty($result['shell_help_html'])) ? (string) $result['shell_help_html'] : ''; $output_html = $composer_help . $shell_help . $output; $message = aismart_recovery_failure_intro($result); $show_retry_button = !(isset($result['error_type']) && $result['error_type'] === 'shell_disabled'); $details = ''; foreach (aismart_missing_dependencies() as $path) { $details .= '
' . esc_html($path) . '' . esc_html($path) . '' . esc_html($path) . 'URL mode updated.
Control how AISmart handles default-language URL prefix assumptions for multilingual plugins. Recommended for large-scale deployments: keep Native plugin behavior.
'; echo ''; echo 'URL mode updated. Redirected to Dashboard.
Manage connected workspace and content operations from the links below.
'; echo ''; echo 'Open AISmart Cloud Dashboard'; echo 'Open Content Manager Page'; echo '
'; echo 'Found: $det_e
Found: $det_e
Use this to verify which cloud account is currently linked to this site.
Unlock various AI capabilities for your WordPress site. Connect your site to the cloud platform to get started.
Redirecting to secure sign-in...
Exchange Code:
Debug State:
" . esc_html(print_r($state, true)) . "
Could not load Content Manager inline.
'; echo '' . esc_html($remote->get_error_message()) . '
'; echo ''; echo 'Content Manager returned an unexpected response.
'; echo 'HTTP status: ' . esc_html((string) $status_code) . '
'; echo 'Sync WordPress users to AISmart workspace team members from this page.
| WP ID | Name | Role | Registered | Last Sync | Status | Action | ||
|---|---|---|---|---|---|---|---|---|
| Loading users... | ||||||||
' . $intro . '
' . '' . $safe_topic . ' should be approached with two priorities: ' . $safe_focus_a . ' and ' . $safe_focus_b . '. The best early wins usually come when you ' . $angle . '.
' . 'Set a clear objective and define the audience segment first. For this part of the plan, keep the scope narrow, specify one target result, and remove activities that do not directly support that result.
' . 'Build a small repeatable workflow: draft, ship, measure, and adjust. Track what changed after each iteration, then keep only the improvements that consistently move the main metric.
' . 'Use a weekly checklist with owners, deadlines, and review criteria. Small consistent updates outperform large infrequent changes, especially when decisions are tied to real user feedback.
' . '' . $cta . '
'; $excerpt = wp_trim_words( 'Local fallback draft for ' . $topic . ': priorities include ' . $focus_a . ' and ' . $focus_b . '.', 40, '...' ); return [ 'title' => $topic, 'content' => $content, 'excerpt' => $excerpt, 'image_url' => '', ]; } function aismart_build_backup_image_query($topic, $category = '', $tags = '', $excerpt = '') { $topic = strtolower(trim((string) $topic)); $category = strtolower(trim((string) $category)); $tags = strtolower(trim((string) $tags)); $excerpt = strtolower(trim(wp_strip_all_tags((string) $excerpt))); $stop = [ 'the', 'and', 'for', 'with', 'from', 'this', 'that', 'your', 'you', 'are', 'was', 'were', 'into', 'about', 'how', 'why', 'step', 'steps', 'guide', 'checklist', 'roadmap', 'examples', 'example', 'basics', 'beginner', 'beginners', 'practical', 'auto', 'post', 'draft', 'now', 'new', 'best', 'top', 'tips', 'plan', 'strategy', 'strategic', 'what', 'when', 'where', ]; $stop_index = array_fill_keys($stop, true); $collect = static function ($text) use ($stop_index) { $text = preg_replace('/[^a-z0-9\s,\-]/', ' ', (string) $text); $parts = preg_split('/[\s,\-]+/', (string) $text); if (!is_array($parts)) { return []; } $out = []; foreach ($parts as $part) { $token = trim((string) $part); if ($token === '' || strlen($token) < 3) { continue; } if (isset($stop_index[$token])) { continue; } $out[] = $token; } return $out; }; // Priority order: explicit tags/category first, then topic, then excerpt cues. $token_stream = array_merge( $collect($tags), $collect($category), $collect($topic), $collect($excerpt) ); $seen = []; $keywords = []; foreach ($token_stream as $token) { if (isset($seen[$token])) { continue; } $seen[$token] = true; $keywords[] = $token; if (count($keywords) >= 6) { break; } } if (empty($keywords)) { $fallback_tokens = aismart_topic_queue_title_tokens((string) $topic); foreach ($fallback_tokens as $token) { $token = strtolower(trim((string) $token)); if ($token === '' || strlen($token) < 3 || isset($stop_index[$token])) { continue; } if (isset($seen[$token])) { continue; } $seen[$token] = true; $keywords[] = $token; if (count($keywords) >= 6) { break; } } } if (empty($keywords)) { return 'healthcare,medical,doctor,clinic'; } return implode(',', $keywords); } function aismart_build_backup_image_url($topic, $category = '', $tags = '', $excerpt = '') { $query = aismart_build_backup_image_query($topic, $category, $tags, $excerpt); if ($query === '') { $query = 'healthcare,medical,doctor,clinic'; } // Topic-aware fallback image source to avoid unrelated random landscapes. $lock = (string) abs(crc32((string) $query)); return 'https://loremflickr.com/1200/630/' . rawurlencode($query) . '?lock=' . rawurlencode($lock); } function aismart_build_image_prompt($topic, $category = '', $tags = '', $excerpt = '') { $topic = trim((string) $topic); $category = trim((string) $category); $tags = trim((string) $tags); $excerpt = trim(wp_strip_all_tags((string) $excerpt)); $parts = []; if ($topic !== '') { $parts[] = 'Create a realistic featured image for: ' . $topic; } if ($category !== '' && strtolower($category) !== 'auto') { $parts[] = 'Category context: ' . $category; } if ($tags !== '') { $parts[] = 'Tags: ' . $tags; } if ($excerpt !== '') { $parts[] = 'Visual cues: ' . wp_trim_words($excerpt, 16, ''); } $joined = strtolower(trim(implode(' ', [$topic, $category, $tags, $excerpt]))); $scene = 'a professional, real-world situation that clearly represents the topic'; $avoid = 'Do not generate unrelated landscapes, mountains, abstract scenes, random objects, or generic stock visuals.'; if (preg_match('/\b(health|medical|medicine|hospital|clinic|doctor|patient|telemedicine|healthcare|nurse)\b/u', $joined)) { $scene = 'a healthcare setting with doctors or clinicians, patient care, telemedicine consultation, or medical workflow relevant to the topic'; $avoid = 'Avoid nature-only landscapes, mountains, travel scenes, office-only business scenes, and unrelated objects.'; } elseif (preg_match('/\b(travel|tourism|destination|hotel|flight|trip|vacation|food|restaurant|recipe|kitchen)\b/u', $joined)) { $scene = 'a travel, food, or hospitality scene directly connected to the topic context'; } elseif (preg_match('/\b(finance|bank|insurance|invest|budget|accounting|tax)\b/u', $joined)) { $scene = 'a finance context with people analyzing financial documents, dashboards, or planning decisions'; $avoid = 'Avoid hospitals, nature-only scenes, and unrelated lifestyle imagery.'; } elseif (preg_match('/\b(education|school|teacher|student|course|learning|training)\b/u', $joined)) { $scene = 'an education setting with learners, instructors, classroom or online learning interaction'; $avoid = 'Avoid random business meetings or unrelated landscapes.'; } $parts[] = 'Primary scene must show: ' . $scene . '.'; $parts[] = $avoid; $parts[] = 'Style: editorial photography, realistic lighting, clean composition, topic-related, no text, no watermark, no logo.'; return trim(implode(' ', $parts)); } function aismart_collect_translation_targets_for_usage($post_id = 0) { $post_id = (int) $post_id; $all_locales = []; $source_language = ''; if (function_exists('pll_languages_list')) { $all_locales = (array) pll_languages_list(['fields' => 'slug']); if ($post_id > 0 && function_exists('pll_get_post_language')) { $source_language = (string) pll_get_post_language($post_id, 'slug'); } if ($source_language === '' && function_exists('pll_default_language')) { $source_language = (string) pll_default_language(); } } elseif (function_exists('icl_get_languages')) { $wpml_languages = (array) icl_get_languages('skip_missing=0&orderby=code'); foreach ($wpml_languages as $row) { if (is_array($row) && !empty($row['language_code'])) { $all_locales[] = (string) $row['language_code']; } } if ($post_id > 0 && has_filter('wpml_post_language_details')) { $details = apply_filters('wpml_post_language_details', null, $post_id); if (is_array($details) && !empty($details['language_code'])) { $source_language = (string) $details['language_code']; } } } $normalized_locales = []; foreach ((array) $all_locales as $locale) { $locale = sanitize_key((string) $locale); if ($locale !== '') { $normalized_locales[$locale] = true; } } $source_language = sanitize_key((string) $source_language); if ($source_language !== '' && isset($normalized_locales[$source_language])) { unset($normalized_locales[$source_language]); } $targets = array_values(array_keys($normalized_locales)); return [ 'source_language' => $source_language, 'locales' => $targets, 'count' => count($targets), ]; } function aismart_attach_featured_image_from_url($post_id, $image_url, $desc = '') { $post_id = (int) $post_id; $image_url = trim((string) $image_url); if ($post_id <= 0 || $image_url === '' || !filter_var($image_url, FILTER_VALIDATE_URL)) { return [ 'saved' => false, 'attachment_id' => 0, 'image_url' => $image_url, 'error' => 'invalid-image-input', ]; } if (!function_exists('download_url')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } if (!function_exists('media_handle_sideload')) { require_once ABSPATH . 'wp-admin/includes/media.php'; } if (!function_exists('wp_generate_attachment_metadata')) { require_once ABSPATH . 'wp-admin/includes/image.php'; } $attach_id = media_sideload_image($image_url, $post_id, $desc !== '' ? $desc : 'AISmart generated image', 'id'); if (is_wp_error($attach_id) || !$attach_id) { $tmp_file = download_url($image_url, 30); if (!is_wp_error($tmp_file)) { $path = (string) wp_parse_url($image_url, PHP_URL_PATH); $name = sanitize_file_name((string) wp_basename($path)); if ($name === '' || strpos($name, '.') === false) { $name = 'aismart-image-' . gmdate('Ymd-His') . '.jpg'; } $file_array = [ 'name' => $name, 'tmp_name' => $tmp_file, ]; $fallback_attach_id = media_handle_sideload($file_array, $post_id, $desc !== '' ? $desc : 'AISmart generated image'); if (!is_wp_error($fallback_attach_id) && (int) $fallback_attach_id > 0) { $attach_id = (int) $fallback_attach_id; } else { if (file_exists($tmp_file)) { @unlink($tmp_file); } } } } if (is_wp_error($attach_id) || !$attach_id) { return [ 'saved' => false, 'attachment_id' => 0, 'image_url' => $image_url, 'error' => is_wp_error($attach_id) ? $attach_id->get_error_message() : 'media-sideload-failed', ]; } set_post_thumbnail($post_id, (int) $attach_id); $saved_url = (string) wp_get_attachment_image_url((int) $attach_id, 'full'); if ($saved_url === '') { $saved_url = $image_url; } return [ 'saved' => true, 'attachment_id' => (int) $attach_id, 'image_url' => $saved_url, 'error' => '', ]; } function aismart_require_media_includes() { if (!function_exists('download_url')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } if (!function_exists('media_handle_sideload')) { require_once ABSPATH . 'wp-admin/includes/media.php'; } if (!function_exists('wp_generate_attachment_metadata')) { require_once ABSPATH . 'wp-admin/includes/image.php'; } } add_action('wp_ajax_aismart_create_post', 'aismart_create_post'); function aismart_capture_ajax_runtime_noise() { $noise = ''; while (ob_get_level() > 0) { $chunk = ob_get_contents(); if ($chunk !== false && $chunk !== '') { $noise .= $chunk; } @ob_end_clean(); } $noise = trim($noise); if ($noise !== '') { $noise_preview = substr(preg_replace('/\s+/u', ' ', wp_strip_all_tags($noise)), 0, 500); error_log('AISmart AJAX runtime output suppressed: ' . $noise_preview); } } function aismart_send_json_success_clean($data, $status_code = null) { aismart_capture_ajax_runtime_noise(); if ($status_code === null) { wp_send_json_success($data); } wp_send_json_success($data, (int) $status_code); } function aismart_send_json_error_clean($data, $status_code = null) { aismart_capture_ajax_runtime_noise(); if ($status_code === null) { wp_send_json_error($data); } wp_send_json_error($data, (int) $status_code); } function aismart_create_post() { if (!headers_sent()) { ob_start(); } if (!current_user_can('manage_options')) { aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } if (!aismart_require_positive_credit_or_json_error('Draft creation')) { return; } aismart_require_media_includes(); global $wpdb; $topic = isset($_POST['topic']) ? sanitize_text_field(wp_unslash($_POST['topic'])) : ''; $queue_id = isset($_POST['queue_id']) ? (int) $_POST['queue_id'] : 0; $post_id = isset($_POST['post_id']) && is_numeric($_POST['post_id']) ? intval($_POST['post_id']) : 0; $category_raw = isset($_POST['category']) ? sanitize_text_field(wp_unslash($_POST['category'])) : 'Auto'; $tags_raw = isset($_POST['tags']) ? sanitize_text_field(wp_unslash($_POST['tags'])) : ''; $schedule_at_raw = isset($_POST['schedule_at']) ? sanitize_text_field(wp_unslash($_POST['schedule_at'])) : ''; $manual_content = isset($_POST['content']) ? wp_kses_post(wp_unslash($_POST['content'])) : ''; $publish_now = isset($_POST['publish_now']) ? (int) $_POST['publish_now'] : 0; $force_draft = isset($_POST['force_draft']) ? (int) $_POST['force_draft'] : 0; $force_translate = isset($_POST['force_translate']) ? (int) $_POST['force_translate'] : 0; $auto_translate_enabled = (bool) get_option('aismart_auto_translate', 0); $queue_topic_title = ''; if ($queue_id > 0) { $queue_items = aismart_topic_queue_get_items(); $queue_index = $queue_id - 1; if (isset($queue_items[$queue_index]) && is_array($queue_items[$queue_index])) { $queue_topic_title = aismart_topic_queue_normalize_title($queue_items[$queue_index]['title'] ?? ''); if ($topic === '' && $queue_topic_title !== '') { $topic = $queue_topic_title; } } } if ($topic === '') { aismart_send_json_error_clean(['message' => 'Topic required'], 422); } // Resolve target post: explicit post_id first, otherwise reuse existing post by exact title. $target_post_id = 0; if ($post_id > 0 && get_post($post_id)) { $target_post_id = $post_id; } else { $existing_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status IN ('draft','publish','pending','future') AND post_title = %s ORDER BY ID DESC LIMIT 1", $topic ) ); if ($existing_id > 0) { $target_post_id = $existing_id; } } $is_update = $target_post_id > 0; $manual_update_mode = false; $used_local_fallback = false; $fallback_reason = ''; $publish_requested = $publish_now === 1; $draft_first_mode = (!$auto_translate_enabled && !$publish_requested) || $force_draft === 1; $scheduled_local = ''; $scheduled_gmt = ''; $schedule_requested = ($schedule_at_raw !== ''); if ($schedule_at_raw !== '') { $schedule_input = trim((string) $schedule_at_raw); $schedule_input = preg_replace('/\s+/u', ' ', str_replace(',', ' ', $schedule_input)); try { $site_tz = function_exists('wp_timezone') ? wp_timezone() : new DateTimeZone('UTC'); $now_dt = new DateTime('now', $site_tz); $scheduled_dt = false; $format_candidates = [ 'Y-m-d\\TH:i', 'Y-m-d\\TH:i:s', 'Y-m-d H:i', 'Y-m-d H:i:s', 'd/m/Y H:i', 'd/m/Y h:i A', 'd/m/Y, h:i A', 'm/d/Y h:i A', 'm/d/Y, h:i A', ]; foreach ($format_candidates as $format) { $candidate = DateTime::createFromFormat($format, $schedule_input, $site_tz); if ($candidate instanceof DateTime) { $scheduled_dt = $candidate; break; } } if (!$scheduled_dt && preg_match('/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$/', $schedule_input)) { $scheduled_dt = new DateTime($schedule_input, $site_tz); } // Last-resort parser for ISO variants with timezone offsets/milliseconds. if (!$scheduled_dt) { try { $generic_dt = new DateTime($schedule_input); $generic_dt->setTimezone($site_tz); $scheduled_dt = $generic_dt; } catch (Exception $e) { $scheduled_dt = false; } } if ($scheduled_dt instanceof DateTime && $scheduled_dt > $now_dt) { $scheduled_local = $scheduled_dt->format('Y-m-d H:i:s'); $scheduled_gmt = get_gmt_from_date($scheduled_local); } } catch (Exception $e) { $scheduled_local = ''; $scheduled_gmt = ''; } } if ($schedule_requested && $scheduled_local === '') { $site_tz_name = function_exists('wp_timezone_string') ? (string) wp_timezone_string() : ''; if ($site_tz_name === '') { $site_tz_name = (string) get_option('timezone_string', 'UTC'); if ($site_tz_name === '') { $site_tz_name = 'UTC'; } } aismart_send_json_error_clean([ 'message' => 'Schedule time must be in the future (site timezone: ' . sanitize_text_field($site_tz_name) . ').', ], 422); } if ($is_update && trim((string) $manual_content) !== '') { $manual_update_mode = true; $data = [ 'title' => $topic, 'content' => $manual_content, 'excerpt' => wp_trim_words(wp_strip_all_tags($manual_content), 40, '...'), ]; error_log('AISmart Create Post: using local manual content for update post_id=' . $target_post_id); } else { error_log("AISmart Create Post: topic={$topic}, post_id={$post_id}, target_post_id={$target_post_id}, is_update=" . ($is_update ? '1' : '0')); $api_result = aismart_request_posts_api([ 'site' => 'site2', 'topic' => $topic, 'skip_image' => $is_update ? 1 : 0, 'image_prompt' => aismart_build_image_prompt($topic, $category_raw, $tags_raw, $manual_content), ], 60); if (empty($api_result['ok'])) { $code = isset($api_result['status']) ? (int) $api_result['status'] : 500; $msg = !empty($api_result['message']) ? (string) $api_result['message'] : 'Create post failed'; $body = isset($api_result['body']) ? (string) $api_result['body'] : ''; error_log('AISmart Create Post failed: status=' . $code . ' message=' . $msg . ' body=' . $body); $data = aismart_build_local_fallback_post_data($topic, $manual_content); $used_local_fallback = true; $fallback_reason = $msg; } else { $data = (array) $api_result['data']; } } // Publish post or update existing post (no duplicate IDs on repeated Create Now) $resolved_title = aismart_normalize_post_title((string) ($data['title'] ?? $topic)); if ($resolved_title === '') { $resolved_title = $topic; } // Local text fallback should not force draft when user explicitly requested publish/translate flow. $force_draft_for_fallback = $used_local_fallback && !$publish_requested; $final_post_status = ($force_draft_for_fallback || $draft_first_mode) ? 'draft' : 'publish'; if ($scheduled_local !== '' && $final_post_status !== 'draft') { $final_post_status = 'future'; } if ($schedule_at_raw !== '' && function_exists('aismart_queue_log')) { aismart_queue_log( 'Create post schedule parse topic=' . $topic . ' schedule_at_raw=' . sanitize_text_field((string) $schedule_at_raw) . ' scheduled_local=' . ($scheduled_local !== '' ? $scheduled_local : '(none)') . ' final_post_status=' . $final_post_status ); } $post_data = [ 'post_title' => $resolved_title, 'post_content' => aismart_cleanup_translated_post_content((string) ($data['content'] ?? '')), 'post_excerpt' => '', 'post_status' => $final_post_status, 'post_author' => get_current_user_id(), 'post_type' => 'post', ]; if ($final_post_status === 'future' && $scheduled_local !== '') { $post_data['post_date'] = $scheduled_local; if ($scheduled_gmt !== '') { $post_data['post_date_gmt'] = $scheduled_gmt; } // Required on updates so WordPress persists the provided future date. // Without this, wp_update_post may keep current post_date and immediately publish. if ($is_update) { $post_data['edit_date'] = true; } } $resolved_content = (string) ($post_data['post_content'] ?? ''); $resolved_excerpt = wp_trim_words(wp_strip_all_tags($resolved_content), 40, '...'); $post_data['post_excerpt'] = $resolved_excerpt; if ($is_update) { $post_data['ID'] = $target_post_id; $result = wp_update_post($post_data, true); if (is_wp_error($result)) { error_log('AISmart Update Post Failed: ' . $result->get_error_message()); aismart_send_json_error_clean(['message' => 'Failed to update post'], 500); } $post_id = $target_post_id; } else { $post_id = wp_insert_post($post_data); } if (is_wp_error($post_id)) { aismart_send_json_error_clean(['message' => 'Failed to insert post'], 500); } $created_provider = aismart_resolve_selected_provider_key(); if ($created_provider !== '') { update_post_meta((int) $post_id, '_aismart_created_provider', $created_provider); update_post_meta((int) $post_id, '_aismart_last_multilang_provider', $created_provider); } // Keep queue/list and generated posts in sync. // If a topic was manually typed (not already queued), add it first so it appears in Current Topic List. aismart_topic_queue_add_titles([$topic], 'manual'); if ((function_exists('mb_strtolower') ? mb_strtolower($resolved_title) : strtolower($resolved_title)) !== (function_exists('mb_strtolower') ? mb_strtolower($topic) : strtolower($topic))) { aismart_topic_queue_add_titles([$resolved_title], 'manual'); } $marked_by_queue_id = false; if ($queue_id > 0) { $marked_by_queue_id = aismart_topic_queue_mark_written_by_queue_id($queue_id, (int) $post_id, $created_provider); } if (!$marked_by_queue_id) { aismart_topic_queue_mark_written($topic, (int) $post_id, $created_provider); } if ((function_exists('mb_strtolower') ? mb_strtolower($resolved_title) : strtolower($resolved_title)) !== (function_exists('mb_strtolower') ? mb_strtolower($topic) : strtolower($topic))) { aismart_topic_queue_mark_written($resolved_title, (int) $post_id, $created_provider); } // Best-effort cloud feedback for topic lifecycle events. $topic_feedback_event = $final_post_status === 'publish' ? 'published' : 'generated'; aismart_topic_cloud_feedback($topic, $topic, $topic_feedback_event); if ((function_exists('mb_strtolower') ? mb_strtolower($resolved_title) : strtolower($resolved_title)) !== (function_exists('mb_strtolower') ? mb_strtolower($topic) : strtolower($topic))) { aismart_topic_cloud_feedback($topic, $resolved_title, $topic_feedback_event); } // --- Polylang: Ensure Language is Set for Original Post --- if (function_exists('pll_set_post_language') && function_exists('pll_default_language')) { $current_lang = pll_get_post_language($post_id); if (!$current_lang) { $def_lang = pll_default_language(); pll_set_post_language($post_id, $def_lang); } } update_post_meta((int) $post_id, '_aismart_generated_post', 1); update_post_meta((int) $post_id, '_aismart_generated_at', gmdate('c')); if ($used_local_fallback) { update_post_meta((int) $post_id, '_aismart_local_fallback', 1); if ($fallback_reason !== '') { update_post_meta((int) $post_id, '_aismart_local_fallback_reason', sanitize_text_field($fallback_reason)); } } else { delete_post_meta((int) $post_id, '_aismart_local_fallback'); delete_post_meta((int) $post_id, '_aismart_local_fallback_reason'); } // Assign category (default Auto) $category_name = trim($category_raw); if ($category_name === '' || strtolower($category_name) === 'auto') { $category_name = 'Auto'; } $category_slug = sanitize_title($category_name); $category_term = get_term_by('slug', $category_slug, 'category'); if (!$category_term) { $category_term = get_term_by('name', $category_name, 'category'); } if (!$category_term || is_wp_error($category_term)) { $created_category = wp_insert_term($category_name, 'category', ['slug' => $category_slug]); if (!is_wp_error($created_category) && !empty($created_category['term_id'])) { $category_term = get_term((int) $created_category['term_id'], 'category'); } } if ($category_term && !is_wp_error($category_term)) { wp_set_post_categories($post_id, [(int) $category_term->term_id], false); } // Assign tags from user input only. $tags = []; if ($tags_raw !== '') { $tags = array_filter(array_map('trim', explode(',', $tags_raw))); } $tags = array_values(array_unique(array_map('sanitize_text_field', $tags))); wp_set_post_tags($post_id, $tags, false); // Handle image on first create; manual resume/update mode should not auto-generate images. $image_fallback_used = false; $image_fallback_reason = ''; $image_url = $data['image_url'] ?? ''; if ($image_url === '' && isset($data['featured_image'])) { $image_url = (string) $data['featured_image']; } $should_generate_image = !$is_update || !has_post_thumbnail((int) $post_id); if ($manual_update_mode) { // Resume/publish of existing content should only update post metadata/content unless user explicitly regenerates image. $should_generate_image = false; } if ($should_generate_image) { if ($image_url === '' || !filter_var($image_url, FILTER_VALIDATE_URL)) { $image_only_result = aismart_request_posts_api([ 'site' => 'site2', 'topic' => $topic, 'image_only' => 1, 'image_prompt' => aismart_build_image_prompt($topic, $category_raw, $tags_raw, (string) ($data['excerpt'] ?? '')), ], 90); if (!empty($image_only_result['ok'])) { $image_data = (array) $image_only_result['data']; $candidate = isset($image_data['image_url']) ? (string) $image_data['image_url'] : ''; if ($candidate === '' && isset($image_data['featured_image'])) { $candidate = (string) $image_data['featured_image']; } if ($candidate !== '' && filter_var($candidate, FILTER_VALIDATE_URL)) { $image_url = $candidate; } } else { $image_fallback_used = true; $image_fallback_reason = !empty($image_only_result['message']) ? (string) $image_only_result['message'] : 'Image generation failed'; } } if ($image_url === '' || !filter_var($image_url, FILTER_VALIDATE_URL)) { $image_fallback_used = true; if ($image_fallback_reason === '') { $image_fallback_reason = 'Upstream image URL missing'; } $backup_image_url = aismart_build_backup_image_url($topic, $category_raw, $tags_raw, $resolved_excerpt); if ($backup_image_url !== '' && filter_var($backup_image_url, FILTER_VALIDATE_URL)) { $image_url = $backup_image_url; } } if ($image_url !== '' && filter_var($image_url, FILTER_VALIDATE_URL)) { $attach_result = aismart_attach_featured_image_from_url($post_id, $image_url, 'Generated image for ' . $topic); if (!empty($attach_result['saved'])) { $image_url = (string) ($attach_result['image_url'] ?? $image_url); } else { $image_fallback_used = true; $attach_error = (string) ($attach_result['error'] ?? 'image-attach-failed'); if ($image_fallback_reason === '' || $image_fallback_reason === 'Upstream image URL missing') { $image_fallback_reason = $attach_error; } error_log('AISmart Image Sideload Failed: ' . $image_fallback_reason); } } } if (empty($image_url)) { $image_url = get_the_post_thumbnail_url($post_id, 'full') ?: ''; } // If auto-translate is enabled, queue translation for Create Now updates too. $translation_queued = false; $translation_queued_new = false; $translation_queued_background = false; $translation_pending_on_publish = false; $translation_warning = ''; $translation_usage_targets = [ 'source_language' => '', 'locales' => [], 'count' => 0, ]; if ($final_post_status === 'publish' && ($auto_translate_enabled || $force_translate === 1)) { if (aismart_has_polylang_conveythis_conflict()) { $translation_warning = 'Auto-translation skipped: Polylang + ConveyThis conflict can cause redirect loops.'; } else { $already_queued = (bool) get_post_meta($post_id, '_aismart_translation_queued', true); if ($is_update) { delete_post_meta($post_id, '_aismart_translation_queued'); $already_queued = false; } if (!$already_queued) { $route = aismart_detect_translation_command(); $queue_engine = isset($route['engine']) ? sanitize_key((string) $route['engine']) : 'unknown'; // Some native engines can be slow when many locales are enabled. // Queue them in background so create/publish response stays fast and JSON-safe. if (in_array($queue_engine, ['bogo', 'wpml', 'wpglobus', 'polylang', 'translatepress'], true)) { $queue_engine_for_background = $queue_engine; $args = [(int) $post_id, $queue_engine_for_background]; if (!wp_next_scheduled('aismart_process_translation_event', $args)) { wp_schedule_single_event(time() + 1, 'aismart_process_translation_event', $args); } if (function_exists('spawn_cron')) { @spawn_cron(time()); } aismart_defer_background_translation((int) $post_id, $queue_engine_for_background); $translation_queued = true; $translation_queued_new = true; $translation_queued_background = true; update_post_meta($post_id, '_aismart_translation_queued', 1); if ($translation_warning === '') { $translation_warning = 'Translation queued in background for ' . ucfirst($queue_engine_for_background) . '.'; } $translation_usage_targets = aismart_collect_translation_targets_for_usage((int) $post_id); } else { $queue_runtime_noise = ''; $buffer_started = false; try { if (!headers_sent()) { ob_start(); $buffer_started = true; } $translation_queued = aismart_queue_translation_for_post($post_id); } catch (Throwable $e) { $translation_queued = false; update_post_meta($post_id, '_aismart_translation_last_error', sanitize_text_field($e->getMessage())); error_log('AISmart Translation Queue Exception: post_id=' . (int) $post_id . ' message=' . $e->getMessage()); } finally { if ($buffer_started) { $queue_runtime_noise = trim((string) ob_get_clean()); } } if ($queue_runtime_noise !== '') { $noise_preview = substr(preg_replace('/\\s+/u', ' ', $queue_runtime_noise), 0, 500); error_log('AISmart Translation Queue Runtime Output: post_id=' . (int) $post_id . ' output=' . $noise_preview); } if ($translation_queued) { $translation_queued_new = true; update_post_meta($post_id, '_aismart_translation_queued', 1); $translation_usage_targets = aismart_collect_translation_targets_for_usage((int) $post_id); if ($queue_runtime_noise !== '' && $translation_warning === '') { $translation_warning = 'Post published. Translation queued with runtime warnings; review server logs.'; } } else { $last_translation_error = trim((string) get_post_meta($post_id, '_aismart_translation_last_error', true)); if ($translation_warning === '') { if ($last_translation_error !== '') { $translation_warning = 'Auto-translation skipped: ' . sanitize_text_field($last_translation_error); } else { $translation_warning = 'Post published, but translation could not be queued right now.'; } } } } } else { $translation_queued = true; } } } elseif ($final_post_status === 'future' && ($auto_translate_enabled || $force_translate === 1)) { // For scheduled posts, defer translation queue until WordPress publishes the post. update_post_meta($post_id, '_aismart_force_translate_on_publish', 1); $translation_pending_on_publish = true; if ($translation_warning === '') { $translation_warning = 'Translation will be queued automatically when this scheduled post is published.'; } } else { delete_post_meta($post_id, '_aismart_force_translate_on_publish'); } // Keep WP API unit usage visible to plugin UI (matches backend business-unit model). $wp_api_units_post = $manual_update_mode ? 0 : 1; $wp_api_units_image = $should_generate_image ? 1 : 0; $translation_locale_count = 0; if ($translation_queued_new) { $translation_locale_count = (int) ($translation_usage_targets['count'] ?? 0); if ($translation_locale_count <= 0) { $translation_locale_count = 1; } } $wp_api_units_translation = $translation_locale_count > 0 ? ($translation_locale_count * 2) : 0; $wp_api_units_total = $wp_api_units_post + $wp_api_units_image + $wp_api_units_translation; $token_event_amount = $manual_update_mode ? 0 : (float) $wp_api_units_total; aismart_log_token_event( 'post_generate', $token_event_amount, [ 'topic' => $topic, 'post_id' => (int) $post_id, 'is_update' => $is_update ? 1 : 0, 'manual_update_mode' => $manual_update_mode ? 1 : 0, 'fallback_used' => $used_local_fallback ? 1 : 0, 'image_requested' => $should_generate_image ? 1 : 0, 'image_url' => (string) $image_url, 'image_fallback_used' => $image_fallback_used ? 1 : 0, 'translation_queued' => $translation_queued ? 1 : 0, 'translation_locales' => (array) ($translation_usage_targets['locales'] ?? []), 'translation_count' => (int) ($translation_usage_targets['count'] ?? 0), 'source_language' => (string) ($translation_usage_targets['source_language'] ?? ''), ], 'Generated or updated post draft' ); aismart_send_json_success_clean([ 'post_id' => $post_id, 'queue_id' => $queue_id > 0 ? $queue_id : 0, 'title' => $resolved_title, 'content' => $resolved_content, 'excerpt' => $resolved_excerpt, 'image_url' => $image_url, 'featured_image' => $image_url, 'post_status' => $final_post_status, 'scheduled_at' => $final_post_status === 'future' ? $scheduled_local : '', 'translation_queued' => $translation_queued ? 1 : 0, 'translation_queued_background' => $translation_queued_background ? 1 : 0, 'translation_pending_on_publish' => $translation_pending_on_publish ? 1 : 0, 'translation_warning' => $translation_warning, 'auto_translate_enabled' => $auto_translate_enabled ? 1 : 0, 'draft_first_mode' => $draft_first_mode ? 1 : 0, 'fallback_used' => $used_local_fallback ? 1 : 0, 'fallback_reason' => $fallback_reason, 'fallback_notice' => $used_local_fallback ? 'Text AI temporarily unavailable, so local fallback content was used.' : '', 'wp_api_units_estimated_total' => (int) $wp_api_units_total, 'wp_api_units_breakdown' => [ 'post' => (int) $wp_api_units_post, 'image' => (int) $wp_api_units_image, 'translation' => (int) $wp_api_units_translation, 'translation_locales' => (int) $translation_locale_count, ], 'image_fallback_used' => $image_fallback_used ? 1 : 0, 'image_fallback_reason' => $image_fallback_reason, 'edit_link' => get_edit_post_link($post_id, ''), ]); } // AJAX: Regenerate image via NVL API add_action('wp_ajax_aismart_regenerate_image', 'aismart_regenerate_image'); function aismart_regenerate_image() { if (!current_user_can('manage_options')) { aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } if (!aismart_require_positive_credit_or_json_error('Image regeneration')) { return; } $topic = isset($_POST['topic']) ? sanitize_text_field(wp_unslash($_POST['topic'])) : ''; $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; $category_raw = isset($_POST['category']) ? sanitize_text_field(wp_unslash($_POST['category'])) : ''; $tags_raw = isset($_POST['tags']) ? sanitize_text_field(wp_unslash($_POST['tags'])) : ''; if ($topic === '') { aismart_send_json_error_clean(['message' => 'Topic required'], 422); } error_log('AISmart Regenerate Image: topic=' . $topic); $image_fallback_used = false; $image_fallback_reason = ''; $api_result = aismart_request_posts_api([ 'site' => 'site2', 'topic' => $topic, 'image_only' => 1, 'image_prompt' => aismart_build_image_prompt($topic), ], 90); $image_url = ''; if (!empty($api_result['ok'])) { $data = (array) $api_result['data']; $image_url = isset($data['image_url']) ? (string) $data['image_url'] : ''; if ($image_url === '' && isset($data['featured_image'])) { $image_url = (string) $data['featured_image']; } } else { $image_fallback_used = true; $image_fallback_reason = !empty($api_result['message']) ? (string) $api_result['message'] : 'Image generation failed'; error_log('AISmart Regenerate Image failed, keeping existing image: ' . $image_fallback_reason); } if ($image_url === '' || !filter_var($image_url, FILTER_VALIDATE_URL)) { $image_fallback_used = true; if ($image_fallback_reason === '') { $image_fallback_reason = 'Upstream image URL missing'; } $existing_excerpt = ''; if ($post_id > 0) { $existing_post = get_post($post_id); if ($existing_post instanceof WP_Post) { $existing_excerpt = wp_trim_words(wp_strip_all_tags((string) $existing_post->post_content), 24, ''); } } $backup_image_url = aismart_build_backup_image_url($topic, $category_raw, $tags_raw, $existing_excerpt); if ($backup_image_url !== '' && filter_var($backup_image_url, FILTER_VALIDATE_URL)) { $image_url = $backup_image_url; } if (($image_url === '' || !filter_var($image_url, FILTER_VALIDATE_URL)) && $post_id > 0) { $image_url = get_the_post_thumbnail_url($post_id, 'full') ?: ''; } } $saved_to_post = 0; $attachment_id = 0; if ($post_id > 0 && get_post($post_id) && $image_url !== '') { aismart_require_media_includes(); $attach_result = aismart_attach_featured_image_from_url($post_id, $image_url, 'AISmart regenerated image'); if (!empty($attach_result['saved'])) { $saved_to_post = 1; $attachment_id = (int) ($attach_result['attachment_id'] ?? 0); $image_url = (string) ($attach_result['image_url'] ?? $image_url); } else { $image_fallback_used = true; $image_fallback_reason = (string) ($attach_result['error'] ?? 'image-attach-failed'); error_log('AISmart Regenerate Image save failed: post_id=' . $post_id . ' error=' . $image_fallback_reason); $attachment_id = 0; } } aismart_log_token_event( 'image_regenerate', 1, [ 'topic' => $topic, 'post_id' => (int) $post_id, 'saved_to_post' => $saved_to_post, 'fallback_used' => $image_fallback_used ? 1 : 0, ], 'Regenerated featured image' ); aismart_send_json_success_clean([ 'image_url' => $image_url, 'featured_image' => $image_url, 'saved_to_post' => $saved_to_post, 'saved_post_id' => $saved_to_post ? $post_id : 0, 'attachment_id' => (int) $attachment_id, 'fallback_used' => $image_fallback_used ? 1 : 0, 'fallback_reason' => $image_fallback_reason, ]); } add_action('wp_ajax_aismart_regenerate_image_all_languages', 'aismart_regenerate_image_all_languages'); function aismart_regenerate_image_all_languages() { if (!current_user_can('manage_options')) { aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } if (!aismart_require_positive_credit_or_json_error('Image regeneration across languages')) { return; } aismart_require_media_includes(); $topic = isset($_POST['topic']) ? sanitize_text_field(wp_unslash($_POST['topic'])) : ''; $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; $category_raw = isset($_POST['category']) ? sanitize_text_field(wp_unslash($_POST['category'])) : ''; $tags_raw = isset($_POST['tags']) ? sanitize_text_field(wp_unslash($_POST['tags'])) : ''; if ($topic === '' || $post_id <= 0) { aismart_send_json_error_clean(['message' => 'Topic and post_id are required'], 422); } $image_fallback_used = false; $image_fallback_reason = ''; $api_result = aismart_request_posts_api([ 'site' => 'site2', 'topic' => $topic, 'image_only' => 1, 'image_prompt' => aismart_build_image_prompt($topic), ], 90); $image_url = ''; if (!empty($api_result['ok'])) { $data = (array) $api_result['data']; $image_url = isset($data['image_url']) ? (string) $data['image_url'] : ''; if ($image_url === '' && isset($data['featured_image'])) { $image_url = (string) $data['featured_image']; } } else { $image_fallback_used = true; $image_fallback_reason = !empty($api_result['message']) ? (string) $api_result['message'] : 'Image generation failed'; } if ($image_url === '' || !filter_var($image_url, FILTER_VALIDATE_URL)) { $image_fallback_used = true; if ($image_fallback_reason === '') { $image_fallback_reason = 'Upstream image URL missing'; } $existing_excerpt = ''; $existing_post = get_post($post_id); if ($existing_post instanceof WP_Post) { $existing_excerpt = wp_trim_words(wp_strip_all_tags((string) $existing_post->post_content), 24, ''); } $backup_image_url = aismart_build_backup_image_url($topic, $category_raw, $tags_raw, $existing_excerpt); if ($backup_image_url !== '' && filter_var($backup_image_url, FILTER_VALIDATE_URL)) { $image_url = $backup_image_url; } if ($image_url === '' || !filter_var($image_url, FILTER_VALIDATE_URL)) { $existing_image = get_the_post_thumbnail_url($post_id, 'full') ?: ''; aismart_send_json_success_clean([ 'image_url' => $existing_image, 'featured_image' => $existing_image, 'updated_post_ids' => [], 'updated_count' => 0, 'attachment_id' => 0, 'fallback_used' => 1, 'fallback_reason' => $image_fallback_reason, ]); return; } } $attachment_id = 0; $attach_result = aismart_attach_featured_image_from_url($post_id, $image_url, 'AISmart regenerated image'); if (!empty($attach_result['saved'])) { $attachment_id = (int) ($attach_result['attachment_id'] ?? 0); $image_url = (string) ($attach_result['image_url'] ?? $image_url); } else { $image_fallback_used = true; $image_fallback_reason = (string) ($attach_result['error'] ?? 'image-attach-failed'); aismart_send_json_success_clean([ 'image_url' => $image_url, 'featured_image' => $image_url, 'updated_post_ids' => [], 'updated_count' => 0, 'attachment_id' => 0, 'fallback_used' => 1, 'fallback_reason' => $image_fallback_reason, ]); } $target_ids = [$post_id]; if (function_exists('apply_filters')) { $trid = apply_filters('wpml_element_trid', null, $post_id, 'post_post'); if ($trid) { $translations = apply_filters('wpml_get_element_translations', null, $trid, 'post_post'); if (is_array($translations)) { foreach ($translations as $translation) { $tid = isset($translation->element_id) ? (int) $translation->element_id : 0; if ($tid > 0) { $target_ids[] = $tid; } } } } } $target_ids = array_values(array_unique(array_map('intval', $target_ids))); $updated_ids = []; foreach ($target_ids as $target_id) { if ($target_id > 0 && get_post($target_id)) { set_post_thumbnail($target_id, (int) $attachment_id); $updated_ids[] = $target_id; } } aismart_log_token_event( 'image_regenerate_multilang', max(1, count($updated_ids)), [ 'topic' => $topic, 'source_post_id' => (int) $post_id, 'updated_count' => count($updated_ids), 'updated_post_ids' => array_values(array_map('intval', $updated_ids)), 'fallback_used' => $image_fallback_used ? 1 : 0, ], 'Regenerated featured image and propagated to translations' ); aismart_send_json_success_clean([ 'image_url' => $image_url, 'featured_image' => $image_url, 'updated_post_ids' => $updated_ids, 'updated_count' => count($updated_ids), 'attachment_id' => (int) $attachment_id, 'fallback_used' => $image_fallback_used ? 1 : 0, 'fallback_reason' => $image_fallback_reason, ]); } // AJAX: Get automation settings function aismart_token_logs_table_name() { global $wpdb; static $resolved = null; if ($resolved !== null) { return $resolved; } $candidates = [ 'token_logs', $wpdb->prefix . 'token_logs', ]; foreach ($candidates as $table_name) { $exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table_name)); if (is_string($exists) && $exists === $table_name) { $resolved = $table_name; return $resolved; } } $resolved = ''; return $resolved; } function aismart_plugin_response_data_array($response) { if (!is_array($response)) { return []; } if (isset($response['data']) && is_array($response['data'])) { return $response['data']; } return $response; } function aismart_snapshot_debug_log($tag, $payload = []) { $tag = sanitize_key((string) $tag); if ($tag === '') { $tag = 'snapshot'; } $record = [ 'ts' => gmdate('c'), 'tag' => $tag, 'payload' => is_array($payload) ? $payload : ['value' => (string) $payload], ]; $line = wp_json_encode($record); if (!is_string($line) || $line === '') { return; } $upload_dir = wp_upload_dir(); $base_dir = isset($upload_dir['basedir']) ? (string) $upload_dir['basedir'] : ''; if ($base_dir === '') { return; } $log_dir = trailingslashit($base_dir) . 'aismart-debug'; if (!is_dir($log_dir)) { wp_mkdir_p($log_dir); } if (!is_dir($log_dir) || !is_writable($log_dir)) { return; } $log_file = trailingslashit($log_dir) . 'snapshot-sync.log'; // Keep file size bounded to avoid unbounded growth during polling. if (file_exists($log_file) && filesize($log_file) > 5 * 1024 * 1024) { @rename($log_file, $log_file . '.1'); } @file_put_contents($log_file, $line . "\n", FILE_APPEND | LOCK_EX); } function aismart_snapshot_truncate_json($value, $limit = 1800) { $limit = (int) $limit; if ($limit <= 0) { $limit = 1800; } $json = wp_json_encode($value); if (!is_string($json)) { return ''; } if (strlen($json) <= $limit) { return $json; } return substr($json, 0, $limit) . '...TRUNCATED'; } function aismart_cloud_usage_date_window() { $tz = function_exists('wp_timezone_string') ? (string) wp_timezone_string() : ''; if ($tz !== '' && preg_match('/^[+-]\d{2}:\d{2}$/', $tz)) { $tz = 'UTC'; } if ($tz === '') { $tz = 'UTC'; } return [ 'from' => '2000-01-01', 'to' => gmdate('Y-m-d'), 'timezone' => $tz, ]; } function aismart_cloud_workspace_usage_snapshot($workspace_id, $limit = 50, $filters = []) { $workspace_id = (int) $workspace_id; $limit = (int) $limit; if ($workspace_id <= 0) { return ['ok' => false]; } if ($limit <= 0) { $limit = 50; } if ($limit > 500) { $limit = 500; } $window = aismart_cloud_usage_date_window(); $state_user_id = (int) aismart_onboarding_resolve_state_user_id(); $base_payload = [ 'workspace_id' => $workspace_id, 'from' => $window['from'], 'to' => $window['to'], 'timezone' => $window['timezone'], ]; if ($state_user_id > 0) { $base_payload['user_id'] = $state_user_id; } $summary = [ 'total_logs' => 0, 'unique_users' => 0, 'total_amount' => 0, ]; $credit = [ 'live' => null, 'quota' => null, 'used' => null, 'updated_at' => '', ]; $transactions = []; $ok = false; $credit_payload_req = ['workspace_id' => $workspace_id]; if ($state_user_id > 0) { $credit_payload_req['user_id'] = $state_user_id; } $credit_res = aismart_plugin_api_request('GET', 'api/plugin/v1/credits/balance', $credit_payload_req, false); if (!is_wp_error($credit_res) && is_array($credit_res)) { $ok = true; $credit_payload = aismart_plugin_response_data_array($credit_res); $credit['live'] = (float) aismart_onboarding_deep_find_scalar($credit_payload, ['credit_balance_live', 'credit_balance', 'balance']); $credit['quota'] = (float) aismart_onboarding_deep_find_scalar($credit_payload, ['credit_quota', 'quota']); $credit['used'] = (float) aismart_onboarding_deep_find_scalar($credit_payload, ['credit_used', 'used']); $credit['updated_at'] = sanitize_text_field((string) aismart_onboarding_deep_find_scalar($credit_payload, ['updated_at', 'updatedAt'])); } $tx_payload = [ 'workspace_id' => $workspace_id, 'page' => 1, 'per_page' => $limit, 'from' => $window['from'], 'to' => $window['to'], 'timezone' => $window['timezone'], ]; if ($state_user_id > 0) { $tx_payload['user_id'] = $state_user_id; } if (isset($filters['type']) && is_string($filters['type'])) { $type = sanitize_key($filters['type']); if ($type !== '' && $type !== 'all') { $tx_payload['type'] = $type; } } if (isset($filters['user_id']) && is_numeric($filters['user_id'])) { $uid = (int) $filters['user_id']; if ($uid > 0) { $tx_payload['user_id'] = $uid; } } if (isset($filters['source']) && is_string($filters['source'])) { $source = sanitize_key($filters['source']); if ($source !== '' && $source !== 'all') { $tx_payload['source'] = $source; } } $tx_res = aismart_plugin_api_request('GET', 'api/plugin/v1/usage/transactions-compat', $tx_payload, false); if (is_wp_error($tx_res)) { $tx_res = aismart_plugin_api_request('GET', 'api/plugin/v1/usage/transactions', $tx_payload, false); } if (!is_wp_error($tx_res) && is_array($tx_res)) { $ok = true; $tx_summary = isset($tx_res['summary']) && is_array($tx_res['summary']) ? $tx_res['summary'] : []; if (!empty($tx_summary)) { $summary['total_logs'] = (int) aismart_onboarding_deep_find_scalar($tx_summary, ['total_logs', 'total']); $summary['unique_users'] = (int) aismart_onboarding_deep_find_scalar($tx_summary, ['unique_users', 'users_count']); $total_usage_units = (float) aismart_onboarding_deep_find_scalar($tx_summary, ['total_usage_units', 'usage_units', 'total_amount']); $total_cost = (float) aismart_onboarding_deep_find_scalar($tx_summary, ['total_cost', 'cost']); $summary['total_amount'] = $total_usage_units > 0 ? $total_usage_units : $total_cost; } $tx_data = aismart_plugin_response_data_array($tx_res); $rows = []; if (isset($tx_data['transactions']) && is_array($tx_data['transactions'])) { $rows = $tx_data['transactions']; } elseif (isset($tx_data['items']) && is_array($tx_data['items'])) { $rows = $tx_data['items']; } elseif (isset($tx_data['data']) && is_array($tx_data['data'])) { $rows = $tx_data['data']; } elseif (array_keys($tx_data) === range(0, count($tx_data) - 1)) { $rows = $tx_data; } foreach ($rows as $row) { if (!is_array($row)) { continue; } $amount = 0.0; if (isset($row['usage_units']) && is_numeric($row['usage_units'])) { $amount = (float) $row['usage_units']; } elseif (isset($row['amount']) && is_numeric($row['amount'])) { $amount = (float) $row['amount']; } elseif (isset($row['cost']) && is_numeric($row['cost'])) { $amount = (float) $row['cost']; } $created_at = ''; if (isset($row['created_at'])) { $created_at = (string) $row['created_at']; } elseif (isset($row['logged_at'])) { $created_at = (string) $row['logged_at']; } $raw_type = sanitize_text_field((string) ($row['type'] ?? '')); if ($raw_type !== '' && stripos($raw_type, 'AISmartWPUnit-') !== 0) { $raw_type = 'AISmartWPUnit-' . $raw_type; } $transactions[] = [ 'id' => isset($row['id']) ? (int) $row['id'] : 0, 'user_id' => isset($row['user_id']) ? (int) $row['user_id'] : 0, 'type' => $raw_type, 'platform' => 'wordpress', 'amount' => $amount, 'case_log' => sanitize_textarea_field((string) ($row['case_log'] ?? '')), 'created_at' => sanitize_text_field($created_at), 'created_at_display' => !empty($created_at) ? mysql2date('Y-m-d H:i', $created_at) : '-', ]; } if ($summary['total_logs'] <= 0) { if (isset($tx_data['total']) && is_numeric($tx_data['total'])) { $summary['total_logs'] = (int) $tx_data['total']; } else { $summary['total_logs'] = count($transactions); } } if ($summary['unique_users'] <= 0 && !empty($transactions)) { $user_ids = []; foreach ($transactions as $trow) { if (isset($trow['user_id']) && (int) $trow['user_id'] > 0) { $user_ids[(int) $trow['user_id']] = true; } } $summary['unique_users'] = count($user_ids); } if ($summary['total_amount'] <= 0 && !empty($transactions)) { $sum_amount = 0.0; foreach ($transactions as $trow) { $sum_amount += (float) ($trow['amount'] ?? 0); } $summary['total_amount'] = $sum_amount; } } if ($summary['total_logs'] <= 0 && $summary['total_amount'] <= 0) { $summary_res = aismart_plugin_api_request('GET', 'api/plugin/v1/usage/summary', $base_payload, false); if (!is_wp_error($summary_res) && is_array($summary_res)) { $ok = true; $summary_payload = aismart_plugin_response_data_array($summary_res); $summary['total_logs'] = (int) aismart_onboarding_deep_find_scalar($summary_payload, ['total_logs', 'total']); $summary['unique_users'] = (int) aismart_onboarding_deep_find_scalar($summary_payload, ['unique_users', 'users_count']); $total_usage_units = (float) aismart_onboarding_deep_find_scalar($summary_payload, ['total_usage_units', 'usage_units']); $total_cost = (float) aismart_onboarding_deep_find_scalar($summary_payload, ['total_cost', 'cost']); $summary['total_amount'] = $total_usage_units > 0 ? $total_usage_units : $total_cost; } } return [ 'ok' => $ok, 'summary' => $summary, 'transactions' => $transactions, 'credit' => $credit, ]; } function aismart_cloud_workspace_usage_snapshot_direct($workspace_id, $limit = 50, $filters = []) { $workspace_id = (int) $workspace_id; $limit = (int) $limit; if ($workspace_id <= 0) { return ['ok' => false, 'summary' => ['total_logs' => 0, 'unique_users' => 0, 'total_amount' => 0], 'transactions' => [], 'credit' => ['live' => null, 'quota' => null, 'used' => null, 'updated_at' => '']]; } if ($limit <= 0) { $limit = 50; } if ($limit > 500) { $limit = 500; } $window = aismart_cloud_usage_date_window(); $state_user_id = (int) aismart_onboarding_resolve_state_user_id(); $tx_payload = [ 'workspace_id' => $workspace_id, 'page' => 1, 'per_page' => $limit, 'from' => $window['from'], 'to' => $window['to'], 'timezone' => $window['timezone'], ]; if ($state_user_id > 0) { $tx_payload['user_id'] = $state_user_id; } if (isset($filters['user_id']) && is_numeric($filters['user_id'])) { $uid = (int) $filters['user_id']; if ($uid > 0) { $tx_payload['user_id'] = $uid; } } if (isset($filters['type']) && is_string($filters['type'])) { $type = sanitize_key($filters['type']); if ($type !== '' && $type !== 'all') { $tx_payload['type'] = $type; } } if (isset($filters['source']) && is_string($filters['source'])) { $source = sanitize_key($filters['source']); if ($source !== '' && $source !== 'all') { $tx_payload['source'] = $source; } } $debug = [ 'endpoint' => 'api/plugin/v1/usage/transactions-compat', 'request' => $tx_payload, 'tx_error' => '', 'tx_response_keys' => [], 'tx_summary_total' => 0, 'tx_data_count' => 0, 'mapped_count' => 0, 'credit_error' => '', 'credit_response_keys' => [], ]; $tx_res = aismart_plugin_api_request('GET', 'api/plugin/v1/usage/transactions-compat', $tx_payload, false); if (is_wp_error($tx_res)) { $tx_res = aismart_plugin_api_request('GET', 'api/plugin/v1/usage/transactions', $tx_payload, false); $debug['endpoint'] = 'api/plugin/v1/usage/transactions'; } if (!is_wp_error($tx_res) && is_array($tx_res)) { $debug['tx_raw_preview'] = aismart_snapshot_truncate_json($tx_res, 2500); } $summary = [ 'total_logs' => 0, 'unique_users' => 0, 'total_amount' => 0, ]; $transactions = []; $ok = false; if (!is_wp_error($tx_res) && is_array($tx_res)) { $ok = true; $debug['tx_response_keys'] = array_values(array_slice(array_keys($tx_res), 0, 15)); if (isset($tx_res['pagination']['total']) && is_numeric($tx_res['pagination']['total'])) { $debug['tx_summary_total'] = (int) $tx_res['pagination']['total']; } $tx_summary = isset($tx_res['summary']) && is_array($tx_res['summary']) ? $tx_res['summary'] : []; if (!empty($tx_summary)) { $summary['total_logs'] = (int) aismart_onboarding_deep_find_scalar($tx_summary, ['total_logs', 'total']); $summary['unique_users'] = (int) aismart_onboarding_deep_find_scalar($tx_summary, ['unique_users', 'users_count']); $total_usage_units = (float) aismart_onboarding_deep_find_scalar($tx_summary, ['total_usage_units', 'usage_units', 'total_amount']); $total_cost = (float) aismart_onboarding_deep_find_scalar($tx_summary, ['total_cost', 'cost']); $summary['total_amount'] = $total_usage_units > 0 ? $total_usage_units : $total_cost; } $tx_data = aismart_plugin_response_data_array($tx_res); $rows = []; if (isset($tx_data['transactions']) && is_array($tx_data['transactions'])) { $rows = $tx_data['transactions']; } elseif (isset($tx_data['items']) && is_array($tx_data['items'])) { $rows = $tx_data['items']; } elseif (isset($tx_data['data']) && is_array($tx_data['data'])) { $rows = $tx_data['data']; } elseif (is_array($tx_data) && array_keys($tx_data) === range(0, count($tx_data) - 1)) { $rows = $tx_data; } $debug['tx_data_count'] = is_array($rows) ? count($rows) : 0; foreach ($rows as $row) { if (!is_array($row)) { continue; } $amount = 0.0; if (isset($row['usage_units']) && is_numeric($row['usage_units'])) { $amount = (float) $row['usage_units']; } elseif (isset($row['amount']) && is_numeric($row['amount'])) { $amount = (float) $row['amount']; } elseif (isset($row['cost']) && is_numeric($row['cost'])) { $amount = (float) $row['cost']; } $created_at = ''; if (isset($row['created_at'])) { $created_at = (string) $row['created_at']; } elseif (isset($row['logged_at'])) { $created_at = (string) $row['logged_at']; } $raw_type = sanitize_text_field((string) ($row['type'] ?? '')); if ($raw_type !== '' && stripos($raw_type, 'AISmartWPUnit-') !== 0) { $raw_type = 'AISmartWPUnit-' . $raw_type; } $transactions[] = [ 'id' => isset($row['id']) ? (int) $row['id'] : 0, 'user_id' => isset($row['user_id']) ? (int) $row['user_id'] : 0, 'type' => $raw_type, 'platform' => 'wordpress', 'amount' => $amount, 'case_log' => sanitize_textarea_field((string) ($row['case_log'] ?? '')), 'created_at' => sanitize_text_field($created_at), 'created_at_display' => !empty($created_at) ? mysql2date('Y-m-d H:i', $created_at) : '-', ]; } if ($summary['total_logs'] <= 0) { if (isset($tx_res['pagination']['total']) && is_numeric($tx_res['pagination']['total'])) { $summary['total_logs'] = (int) $tx_res['pagination']['total']; } else { $summary['total_logs'] = count($transactions); } } if ($summary['unique_users'] <= 0 && !empty($transactions)) { $unique = []; foreach ($transactions as $trow) { if (!empty($trow['user_id'])) { $unique[(int) $trow['user_id']] = true; } } $summary['unique_users'] = count($unique); } if ($summary['total_amount'] <= 0 && !empty($transactions)) { $sum_amount = 0.0; foreach ($transactions as $trow) { $sum_amount += (float) ($trow['amount'] ?? 0); } $summary['total_amount'] = $sum_amount; } $debug['mapped_count'] = count($transactions); } elseif (is_wp_error($tx_res)) { $debug['tx_error'] = $tx_res->get_error_code() . ': ' . $tx_res->get_error_message(); } $credit = ['live' => null, 'quota' => null, 'used' => null, 'updated_at' => '']; $credit_payload_req = ['workspace_id' => $workspace_id]; if (isset($tx_payload['user_id']) && (int) $tx_payload['user_id'] > 0) { $credit_payload_req['user_id'] = (int) $tx_payload['user_id']; } $credit_res = aismart_plugin_api_request('GET', 'api/plugin/v1/credits/balance', $credit_payload_req, false); if (!is_wp_error($credit_res) && is_array($credit_res)) { $ok = true; $debug['credit_response_keys'] = array_values(array_slice(array_keys($credit_res), 0, 15)); $debug['credit_raw_preview'] = aismart_snapshot_truncate_json($credit_res, 1200); $credit_payload = aismart_plugin_response_data_array($credit_res); $credit['live'] = (float) aismart_onboarding_deep_find_scalar($credit_payload, ['credit_balance_live', 'credit_balance', 'balance']); $credit['quota'] = (float) aismart_onboarding_deep_find_scalar($credit_payload, ['credit_quota', 'quota']); $credit['used'] = (float) aismart_onboarding_deep_find_scalar($credit_payload, ['credit_used', 'used']); $credit['updated_at'] = sanitize_text_field((string) aismart_onboarding_deep_find_scalar($credit_payload, ['updated_at', 'updatedAt'])); } elseif (is_wp_error($credit_res)) { $debug['credit_error'] = $credit_res->get_error_code() . ': ' . $credit_res->get_error_message(); } return [ 'ok' => $ok, 'summary' => $summary, 'transactions' => $transactions, 'credit' => $credit, '_debug' => $debug, ]; } function aismart_get_workspace_usage_snapshot($workspace_id, $limit = 10) { global $wpdb; $workspace_id = (int) $workspace_id; $limit = (int) $limit; if ($workspace_id <= 0) { return [ 'summary' => [ 'total_logs' => 0, 'unique_users' => 0, 'total_amount' => 0, ], 'transactions' => [], ]; } if ($limit <= 0) { $limit = 50; } if ($limit > 500) { $limit = 500; } $table = aismart_token_logs_table_name(); if ($table === '') { return [ 'summary' => [ 'total_logs' => 0, 'unique_users' => 0, 'total_amount' => 0, ], 'transactions' => [], ]; } $summary = [ 'total_logs' => 0, 'unique_users' => 0, 'total_amount' => 0, ]; $summary_row = $wpdb->get_row( $wpdb->prepare( "SELECT COUNT(*) AS total_logs, COUNT(DISTINCT user_id) AS unique_users, COALESCE(SUM(amount), 0) AS total_amount FROM {$table} WHERE workspace_id = %d", $workspace_id ) ); if ($summary_row) { $summary['total_logs'] = (int) ($summary_row->total_logs ?? 0); $summary['unique_users'] = (int) ($summary_row->unique_users ?? 0); $summary['total_amount'] = (float) ($summary_row->total_amount ?? 0); } $rows = $wpdb->get_results( $wpdb->prepare( "SELECT id, user_id, type, platform, amount, case_log, created_at FROM {$table} WHERE workspace_id = %d ORDER BY id DESC LIMIT %d", $workspace_id, $limit ) ); $transactions = []; if (is_array($rows)) { foreach ($rows as $row) { $raw_type = sanitize_text_field((string) ($row->type ?? '')); if ($raw_type !== '' && stripos($raw_type, 'AISmartWPUnit-') !== 0) { $raw_type = 'AISmartWPUnit-' . $raw_type; } $transactions[] = [ 'id' => (int) ($row->id ?? 0), 'user_id' => (int) ($row->user_id ?? 0), 'type' => $raw_type, 'platform' => sanitize_text_field((string) ($row->platform ?? '')), 'amount' => (float) ($row->amount ?? 0), 'case_log' => sanitize_textarea_field((string) ($row->case_log ?? '')), 'created_at' => sanitize_text_field((string) ($row->created_at ?? '')), 'created_at_display' => !empty($row->created_at) ? mysql2date('Y-m-d H:i', (string) $row->created_at) : '-', ]; } } return [ 'summary' => $summary, 'transactions' => $transactions, ]; } function aismart_compute_workspace_live_credit($workspace_id, $quota_hint = 0.0, $limit = 10) { $workspace_id = (int) $workspace_id; $quota_hint = (float) $quota_hint; $limit = (int) $limit; if ($limit <= 0) { $limit = 10; } if ($limit > 500) { $limit = 500; } $empty_summary = [ 'total_logs' => 0, 'unique_users' => 0, 'total_amount' => 0, ]; if ($workspace_id <= 0) { return [ 'live' => max(0.0, $quota_hint), 'quota' => max(0.0, $quota_hint), 'used' => 0.0, 'summary' => $empty_summary, 'source' => 'none', ]; } $local_snapshot = aismart_get_workspace_usage_snapshot($workspace_id, $limit); $local_summary = isset($local_snapshot['summary']) && is_array($local_snapshot['summary']) ? $local_snapshot['summary'] : $empty_summary; $cloud_snapshot = aismart_cloud_workspace_usage_snapshot_direct($workspace_id, $limit, []); $cloud_summary = isset($cloud_snapshot['summary']) && is_array($cloud_snapshot['summary']) ? $cloud_snapshot['summary'] : $empty_summary; $cloud_has_payload = ((int) ($cloud_summary['total_logs'] ?? 0) > 0) || ((float) ($cloud_summary['total_amount'] ?? 0) > 0) || (!empty($cloud_snapshot['transactions']) && is_array($cloud_snapshot['transactions'])) || ((isset($cloud_snapshot['credit']['updated_at']) && (string) $cloud_snapshot['credit']['updated_at'] !== '')) || ((isset($cloud_snapshot['credit']['live']) && $cloud_snapshot['credit']['live'] !== null && (float) $cloud_snapshot['credit']['live'] > 0)); $source = (!empty($cloud_snapshot['ok']) && $cloud_has_payload) ? 'cloud' : 'local'; $summary = $source === 'cloud' ? $cloud_summary : $local_summary; $summary_used = (float) ($summary['total_amount'] ?? 0); $quota = isset($cloud_snapshot['credit']['quota']) && $cloud_snapshot['credit']['quota'] !== null ? (float) $cloud_snapshot['credit']['quota'] : 0.0; $used = isset($cloud_snapshot['credit']['used']) && $cloud_snapshot['credit']['used'] !== null ? (float) $cloud_snapshot['credit']['used'] : $summary_used; $live = isset($cloud_snapshot['credit']['live']) && $cloud_snapshot['credit']['live'] !== null ? (float) $cloud_snapshot['credit']['live'] : 0.0; if ($summary_used > 0 && $used <= 0) { $used = $summary_used; } if ($quota <= 0) { $quota = $quota_hint; } if ($quota <= 0) { $quota = (float) get_option('aismart_workspace_credits_' . $workspace_id, 0); } if ($quota <= 0) { $quota = (float) get_option('aismart_credit_balance', 0); } if ($summary_used > 0 && $quota > 0) { $derived_live = max(0.0, $quota - max(0.0, $used)); if ($live <= 0 || $live >= $quota) { $live = $derived_live; } } if ($live <= 0 && $quota > 0 && $used >= 0 && $quota >= $used) { $live = max(0.0, $quota - $used); } if ($live <= 0 && $quota > 0 && $used <= 0) { $live = $quota; } return [ 'live' => max(0.0, (float) $live), 'quota' => max(0.0, (float) $quota), 'used' => max(0.0, (float) $used), 'summary' => $summary, 'source' => $source, ]; } add_action('wp_ajax_aismart_get_credit_snapshot', 'aismart_get_credit_snapshot'); function aismart_get_credit_snapshot() { if (!current_user_can('manage_options')) { aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } $requested_workspace_id = isset($_POST['workspace_id']) ? (int) wp_unslash($_POST['workspace_id']) : 0; $limit = isset($_POST['limit']) ? (int) wp_unslash($_POST['limit']) : 10; $requested_type = isset($_POST['type']) ? sanitize_key((string) wp_unslash($_POST['type'])) : ''; $requested_source = isset($_POST['source']) ? sanitize_key((string) wp_unslash($_POST['source'])) : ''; $requested_user_id = isset($_POST['user_id']) ? (int) wp_unslash($_POST['user_id']) : 0; $workspace_options = aismart_workspace_get_available(true); $ctx = aismart_resolve_workspace_context(); $selected_workspace = null; $workspace_id = (int) ($ctx['workspace_id'] ?? 0); if ($requested_workspace_id > 0) { foreach ($workspace_options as $workspace) { $wid = isset($workspace['workspace_id']) ? (int) $workspace['workspace_id'] : 0; if ($wid !== $requested_workspace_id) { continue; } $selected_workspace = $workspace; $workspace_id = $wid; update_option('aismart_workspace_id', $workspace_id, false); $workspace_name = sanitize_text_field((string) ($workspace['workspace_name'] ?? ('Workspace #' . $workspace_id))); update_option('aismart_workspace_name', $workspace_name, false); if (isset($workspace['credit_balance']) && is_numeric($workspace['credit_balance'])) { update_option('aismart_workspace_credits_' . $workspace_id, (float) $workspace['credit_balance'], false); update_option('aismart_credit_balance', (float) $workspace['credit_balance'], false); update_option('aismart_credits_available', (float) $workspace['credit_balance'], false); } $ctx = aismart_resolve_workspace_context(); break; } } if ($selected_workspace === null && $workspace_id > 0) { foreach ($workspace_options as $workspace) { $wid = isset($workspace['workspace_id']) ? (int) $workspace['workspace_id'] : 0; if ($wid === $workspace_id) { $selected_workspace = $workspace; break; } } } $cloud_snapshot = aismart_cloud_workspace_usage_snapshot_direct($workspace_id, $limit, [ 'type' => $requested_type, 'source' => $requested_source, 'user_id' => $requested_user_id, ]); $cloud_empty = empty($cloud_snapshot['transactions']) && (int) ($cloud_snapshot['summary']['total_logs'] ?? 0) <= 0 && (float) ($cloud_snapshot['summary']['total_amount'] ?? 0) <= 0; $direct_rescue_used = false; if ($cloud_empty) { $window = aismart_cloud_usage_date_window(); $state_user_id = (int) aismart_onboarding_resolve_state_user_id(); $direct_payload = [ 'workspace_id' => $workspace_id, 'page' => 1, 'per_page' => max(1, min(500, (int) $limit)), 'from' => $window['from'], 'to' => $window['to'], 'timezone' => $window['timezone'], ]; if ($state_user_id > 0) { $direct_payload['user_id'] = $state_user_id; } if ($requested_user_id > 0) { $direct_payload['user_id'] = $requested_user_id; } if ($requested_type !== '' && $requested_type !== 'all') { $direct_payload['type'] = $requested_type; } if ($requested_source !== '' && $requested_source !== 'all') { $direct_payload['source'] = $requested_source; } $direct_res = aismart_plugin_api_request('GET', 'api/plugin/v1/usage/transactions-compat', $direct_payload, false); if (!is_wp_error($direct_res) && is_array($direct_res)) { $direct_rows = []; if (isset($direct_res['data']) && is_array($direct_res['data'])) { $direct_rows = $direct_res['data']; } $mapped_rows = []; foreach ($direct_rows as $row) { if (!is_array($row)) { continue; } $amount = 0.0; if (isset($row['usage_units']) && is_numeric($row['usage_units'])) { $amount = (float) $row['usage_units']; } elseif (isset($row['amount']) && is_numeric($row['amount'])) { $amount = (float) $row['amount']; } elseif (isset($row['cost']) && is_numeric($row['cost'])) { $amount = (float) $row['cost']; } $created_at = isset($row['created_at']) ? (string) $row['created_at'] : (isset($row['logged_at']) ? (string) $row['logged_at'] : ''); $raw_type = sanitize_text_field((string) ($row['type'] ?? '')); if ($raw_type !== '' && stripos($raw_type, 'AISmartWPUnit-') !== 0) { $raw_type = 'AISmartWPUnit-' . $raw_type; } $mapped_rows[] = [ 'id' => isset($row['id']) ? (int) $row['id'] : 0, 'user_id' => isset($row['user_id']) ? (int) $row['user_id'] : 0, 'type' => $raw_type, 'platform' => 'wordpress', 'amount' => $amount, 'case_log' => sanitize_textarea_field((string) ($row['case_log'] ?? '')), 'created_at' => sanitize_text_field($created_at), 'created_at_display' => !empty($created_at) ? mysql2date('Y-m-d H:i', $created_at) : '-', ]; } if (!empty($mapped_rows)) { $direct_rescue_used = true; $cloud_snapshot['ok'] = true; $cloud_snapshot['transactions'] = $mapped_rows; $summary_payload = isset($direct_res['summary']) && is_array($direct_res['summary']) ? $direct_res['summary'] : []; $cloud_snapshot['summary'] = [ 'total_logs' => (int) aismart_onboarding_deep_find_scalar($summary_payload, ['total_logs', 'total']), 'unique_users' => (int) aismart_onboarding_deep_find_scalar($summary_payload, ['unique_users', 'users_count']), 'total_amount' => (float) aismart_onboarding_deep_find_scalar($summary_payload, ['total_usage_units', 'usage_units', 'total_amount', 'total_cost', 'cost']), ]; if ((int) $cloud_snapshot['summary']['total_logs'] <= 0) { $cloud_snapshot['summary']['total_logs'] = count($mapped_rows); } } } } $usage = []; $quota_credit = 0.0; $used_credit = 0.0; $live_credit = 0.0; $local_usage_snapshot = aismart_get_workspace_usage_snapshot($workspace_id, $limit); $local_has_payload = ((int) ($local_usage_snapshot['summary']['total_logs'] ?? 0) > 0) || ((float) ($local_usage_snapshot['summary']['total_amount'] ?? 0) > 0) || (!empty($local_usage_snapshot['transactions']) && is_array($local_usage_snapshot['transactions'])); $cloud_has_payload = ((int) ($cloud_snapshot['summary']['total_logs'] ?? 0) > 0) || ((float) ($cloud_snapshot['summary']['total_amount'] ?? 0) > 0) || (!empty($cloud_snapshot['transactions']) && is_array($cloud_snapshot['transactions'])) || ((isset($cloud_snapshot['credit']['updated_at']) && (string) $cloud_snapshot['credit']['updated_at'] !== '')) || ((isset($cloud_snapshot['credit']['live']) && $cloud_snapshot['credit']['live'] !== null && (float) $cloud_snapshot['credit']['live'] > 0)); if (!empty($cloud_snapshot['ok']) && ($cloud_has_payload || !$local_has_payload)) { $usage = [ 'summary' => isset($cloud_snapshot['summary']) && is_array($cloud_snapshot['summary']) ? $cloud_snapshot['summary'] : [ 'total_logs' => 0, 'unique_users' => 0, 'total_amount' => 0, ], 'transactions' => isset($cloud_snapshot['transactions']) && is_array($cloud_snapshot['transactions']) ? $cloud_snapshot['transactions'] : [], ]; $credit = isset($cloud_snapshot['credit']) && is_array($cloud_snapshot['credit']) ? $cloud_snapshot['credit'] : []; $quota_credit = isset($credit['quota']) && $credit['quota'] !== null ? (float) $credit['quota'] : 0.0; $used_credit = isset($credit['used']) && $credit['used'] !== null ? (float) $credit['used'] : (float) ($usage['summary']['total_amount'] ?? 0); $live_credit = isset($credit['live']) && $credit['live'] !== null ? (float) $credit['live'] : 0.0; $summary_used = (float) ($usage['summary']['total_amount'] ?? 0); // Some cloud responses report used/live as 0/full even when usage rows exist. // Prefer summary-derived usage when cloud values look stale. if ($summary_used > 0 && $used_credit <= 0) { $used_credit = $summary_used; } if ($quota_credit <= 0 && is_array($selected_workspace) && isset($selected_workspace['credit_balance']) && is_numeric($selected_workspace['credit_balance'])) { $quota_credit = (float) $selected_workspace['credit_balance']; } if ($summary_used > 0 && $quota_credit > 0) { $derived_live = max(0.0, $quota_credit - max(0.0, $used_credit)); if ($live_credit <= 0 || $live_credit >= $quota_credit) { $live_credit = $derived_live; } } if ($live_credit <= 0 && $quota_credit > 0 && $used_credit >= 0 && $quota_credit >= $used_credit) { $live_credit = max(0.0, $quota_credit - $used_credit); } } else { $usage = $local_usage_snapshot; if (is_array($selected_workspace) && isset($selected_workspace['credit_balance']) && is_numeric($selected_workspace['credit_balance'])) { $quota_credit = (float) $selected_workspace['credit_balance']; } else { $quota_credit = (float) ($ctx['credit_balance'] ?? 0); } $used_credit = (float) (($usage['summary']['total_amount'] ?? 0)); $live_credit = $quota_credit; if ($quota_credit > 0 && $used_credit > 0 && $quota_credit >= $used_credit) { $live_credit = max(0.0, $quota_credit - $used_credit); } } $debug = [ 'requested' => [ 'workspace_id' => $workspace_id, 'limit' => $limit, 'type' => $requested_type, 'source' => $requested_source, 'user_id' => $requested_user_id, ], 'selected_source' => (!empty($cloud_snapshot['ok']) && ($cloud_has_payload || !$local_has_payload)) ? 'cloud' : 'local', 'cloud_ok' => !empty($cloud_snapshot['ok']) ? 1 : 0, 'cloud_has_payload' => $cloud_has_payload ? 1 : 0, 'cloud_summary_logs' => (int) ($cloud_snapshot['summary']['total_logs'] ?? 0), 'cloud_tx_count' => is_array($cloud_snapshot['transactions'] ?? null) ? count($cloud_snapshot['transactions']) : 0, 'local_has_payload' => $local_has_payload ? 1 : 0, 'local_summary_logs' => (int) ($local_usage_snapshot['summary']['total_logs'] ?? 0), 'local_tx_count' => is_array($local_usage_snapshot['transactions'] ?? null) ? count($local_usage_snapshot['transactions']) : 0, 'direct_rescue_used' => $direct_rescue_used ? 1 : 0, 'cloud_debug' => isset($cloud_snapshot['_debug']) && is_array($cloud_snapshot['_debug']) ? $cloud_snapshot['_debug'] : [], ]; aismart_snapshot_debug_log('snapshot_result', $debug); if (defined('WP_DEBUG') && WP_DEBUG) { error_log('AISmart snapshot debug: ' . wp_json_encode($debug)); } aismart_send_json_success_clean([ 'workspace_id' => (int) ($ctx['workspace_id'] ?? 0), 'workspace_label' => (string) ($ctx['workspace_label'] ?? ''), 'credit_balance' => (float) $live_credit, 'wp_api_units' => (float) $live_credit, 'credit_quota' => (float) $quota_credit, 'credit_used' => (float) $used_credit, 'workspaces' => $workspace_options, 'summary' => $usage['summary'], 'transactions' => $usage['transactions'], 'debug' => $debug, ]); } add_action('wp_ajax_aismart_get_settings', 'aismart_get_settings'); function aismart_get_settings() { if (!current_user_can('manage_options')) { aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } global $wpdb; $auto_blog = (int) get_option('aismart_auto_blog', 0); $auto_translate = (int) get_option('aismart_auto_translate', 0); $schedule = sanitize_text_field((string) get_option('aismart_schedule_preset', '3-per-day')); $schedule_time_daily = aismart_normalize_schedule_clock((string) get_option('aismart_schedule_time_daily', '09:30'), '09:30'); $schedule_times_three_raw = get_option('aismart_schedule_times_three', '09:30,13:30,19:30'); $schedule_times_three = aismart_parse_schedule_times_three($schedule_times_three_raw); $default_lang_url_mode = aismart_get_default_lang_url_mode(); $workspace_options = aismart_workspace_get_available(true); $ctx = aismart_resolve_workspace_context(); $workspace_id = (int) $ctx['workspace_id']; $workspace_label = (string) $ctx['workspace_label']; $credit_balance = (float) $ctx['credit_balance']; $credit_runtime = aismart_compute_workspace_live_credit($workspace_id, $credit_balance, 10); $credit_balance = isset($credit_runtime['live']) ? (float) $credit_runtime['live'] : $credit_balance; $multilang = aismart_onboarding_detect_multilang_provider(); $provider = sanitize_key((string) ($multilang['primary'] ?? 'none')); $translation_conflict_active = aismart_has_polylang_conveythis_conflict(); $state = aismart_onboarding_state_get(); $categories = []; $category_query_args = [ 'taxonomy' => 'category', 'hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC', ]; // Ask multilingual plugins (Polylang/WPML compatible args) for all language terms. $category_query_args['lang'] = ''; $category_query_args['suppress_filter'] = true; $category_terms = get_terms($category_query_args); if (!is_wp_error($category_terms) && is_array($category_terms)) { foreach ($category_terms as $term) { if (!($term instanceof WP_Term)) { continue; } $categories[] = [ 'id' => (int) $term->term_id, 'name' => sanitize_text_field((string) $term->name), 'slug' => sanitize_text_field((string) $term->slug), ]; } } if (empty($categories)) { $cat_rows = $wpdb->get_results( "SELECT t.term_id, t.name, t.slug FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id WHERE tt.taxonomy = 'category' ORDER BY t.name ASC" ); if (is_array($cat_rows)) { foreach ($cat_rows as $row) { $categories[] = [ 'id' => (int) ($row->term_id ?? 0), 'name' => sanitize_text_field((string) ($row->name ?? '')), 'slug' => sanitize_text_field((string) ($row->slug ?? '')), ]; } } } $tags = []; $tag_query_args = [ 'taxonomy' => 'post_tag', 'hide_empty' => false, 'orderby' => 'count', 'order' => 'DESC', 'number' => 300, ]; $tag_query_args['lang'] = ''; $tag_query_args['suppress_filter'] = true; $tag_terms = get_terms($tag_query_args); if (!is_wp_error($tag_terms) && is_array($tag_terms)) { $seen_tag_names = []; foreach ($tag_terms as $term) { if (!($term instanceof WP_Term)) { continue; } $name = sanitize_text_field((string) $term->name); if ($name === '') { continue; } $key = function_exists('mb_strtolower') ? mb_strtolower($name) : strtolower($name); if (isset($seen_tag_names[$key])) { continue; } $seen_tag_names[$key] = true; $tags[] = [ 'id' => (int) $term->term_id, 'name' => $name, 'slug' => sanitize_text_field((string) $term->slug), 'count' => (int) $term->count, ]; } } if (empty($tags)) { $tag_rows = $wpdb->get_results( "SELECT t.term_id, t.name, t.slug, tt.count FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id WHERE tt.taxonomy = 'post_tag' ORDER BY tt.count DESC, t.name ASC LIMIT 300" ); if (is_array($tag_rows)) { $seen_tag_names = []; foreach ($tag_rows as $row) { $name = sanitize_text_field((string) ($row->name ?? '')); if ($name === '') { continue; } $key = function_exists('mb_strtolower') ? mb_strtolower($name) : strtolower($name); if (isset($seen_tag_names[$key])) { continue; } $seen_tag_names[$key] = true; $tags[] = [ 'id' => (int) ($row->term_id ?? 0), 'name' => $name, 'slug' => sanitize_text_field((string) ($row->slug ?? '')), 'count' => (int) ($row->count ?? 0), ]; } } } $member_word_remaining = isset($state['member_word_remaining']) && is_numeric($state['member_word_remaining']) ? (float) $state['member_word_remaining'] : null; $member_image_remaining = isset($state['member_image_remaining']) && is_numeric($state['member_image_remaining']) ? (float) $state['member_image_remaining'] : null; $credit_summary_parts = []; $credit_summary_parts[] = 'WP API Units left: ' . number_format_i18n($credit_balance, 0); if ($member_image_remaining !== null) { $credit_summary_parts[] = 'Image credits (reference): ' . number_format_i18n($member_image_remaining, 0); } if ($member_word_remaining !== null) { $credit_summary_parts[] = 'Text credits (reference): ' . number_format_i18n($member_word_remaining, 0); } $credit_summary = implode(' | ', $credit_summary_parts); global $wpdb; $languages = []; if ($provider === 'falang' || function_exists('falang_languages_list') || class_exists('Falang', false)) { if (function_exists('falang_languages_list')) { $falang_languages = (array) falang_languages_list(['hide_empty' => false]); foreach ($falang_languages as $language) { $code = ''; $name = ''; if (is_object($language)) { $code = isset($language->locale) ? (string) $language->locale : (isset($language->slug) ? (string) $language->slug : ''); $name = isset($language->name) ? (string) $language->name : ''; } elseif (is_array($language)) { $code = isset($language['locale']) ? (string) $language['locale'] : (isset($language['slug']) ? (string) $language['slug'] : ''); $name = isset($language['name']) ? (string) $language['name'] : ''; } elseif (is_string($language)) { $code = $language; $name = $language; } $code = sanitize_text_field($code); if ($code === '') { continue; } $languages[] = [ 'code' => $code, 'name' => $name !== '' ? sanitize_text_field($name) : $code, ]; } } if (empty($languages) && taxonomy_exists('language')) { $terms = get_terms([ 'taxonomy' => 'language', 'hide_empty' => false, ]); if (!is_wp_error($terms) && is_array($terms)) { foreach ($terms as $term) { if (!($term instanceof WP_Term)) { continue; } $code = sanitize_text_field((string) $term->slug); if ($code === '') { continue; } $languages[] = [ 'code' => $code, 'name' => sanitize_text_field((string) ($term->name ?: $code)), ]; } } } } elseif ( $provider === 'sublanguage' || class_exists('Sublanguage_core', false) || class_exists('Sublanguage_site', false) || class_exists('Sublanguage_admin', false) || isset($GLOBALS['sublanguage']) ) { $lang_posts = get_posts([ 'post_type' => 'language', 'post_status' => ['publish', 'draft'], 'posts_per_page' => -1, 'orderby' => 'menu_order', 'order' => 'ASC', 'suppress_filters' => true, ]); if (is_array($lang_posts)) { foreach ($lang_posts as $lang_post) { if (!($lang_post instanceof WP_Post)) { continue; } $code = strtolower(sanitize_text_field((string) $lang_post->post_name)); if ($code === '') { continue; } $name = sanitize_text_field((string) $lang_post->post_title); if ($name === '') { $name = strtoupper($code); } $languages[] = [ 'code' => $code, 'name' => $name, ]; } } } elseif (function_exists('pll_languages_list')) { $codes = pll_languages_list(); $names = pll_languages_list(['fields' => 'name']); if (is_array($codes)) { foreach ($codes as $index => $code) { $name = is_array($names) && isset($names[$index]) ? $names[$index] : $code; $languages[] = [ 'code' => (string) $code, 'name' => (string) $name, ]; } } } elseif (function_exists('trp_get_languages') || defined('TRP_PLUGIN_VERSION')) { $trp_languages = []; if (function_exists('trp_get_languages')) { $trp_languages = (array) trp_get_languages(); } if (empty($trp_languages)) { $trp_settings = get_option('trp_settings', []); $trp_codes = []; if (is_array($trp_settings) && !empty($trp_settings['translation-languages']) && is_array($trp_settings['translation-languages'])) { $trp_codes = $trp_settings['translation-languages']; } foreach ($trp_codes as $code) { $code = sanitize_text_field((string) $code); if ($code === '') { continue; } $trp_languages[$code] = $code; } } foreach ($trp_languages as $code => $name) { if (is_int($code)) { $code = (string) $name; } $code = sanitize_text_field((string) $code); if ($code === '') { continue; } $label = is_scalar($name) ? (string) $name : $code; if ($label === '') { $label = $code; } $languages[] = [ 'code' => $code, 'name' => $label, ]; } if (!empty($languages)) { $languages = array_values(array_map('unserialize', array_unique(array_map('serialize', $languages)))); } } elseif ( defined('QTX_VERSION') || function_exists('qtranxf_getSortedLanguages') || function_exists('qtrans_getSortedLanguages') || function_exists('qtrans_getLanguage') ) { $enabled = get_option('qtranslate_enabled_languages', []); if (!is_array($enabled)) { $enabled = []; } if (empty($enabled)) { $cfg_enabled = []; if (!empty($GLOBALS['q_config']) && is_array($GLOBALS['q_config']) && !empty($GLOBALS['q_config']['enabled_languages']) && is_array($GLOBALS['q_config']['enabled_languages'])) { $cfg_enabled = (array) $GLOBALS['q_config']['enabled_languages']; } if (!empty($cfg_enabled)) { $enabled = $cfg_enabled; } } $names = get_option('qtranslate_language_names', []); $locales = get_option('qtranslate_locales', []); if (!is_array($names)) { $names = []; } if (!is_array($locales)) { $locales = []; } foreach ((array) $enabled as $code_raw) { $code = sanitize_text_field((string) $code_raw); if ($code === '') { continue; } $label = ''; if (isset($names[$code]) && is_scalar($names[$code])) { $label = (string) $names[$code]; } elseif (isset($locales[$code]) && is_scalar($locales[$code])) { $label = (string) $locales[$code]; } if ($label === '') { $label = strtoupper(str_replace('-', '_', $code)); } $languages[] = [ 'code' => $code, 'name' => sanitize_text_field($label), ]; } if (empty($languages) && !empty($GLOBALS['q_config']) && is_array($GLOBALS['q_config'])) { $cfg_enabled = !empty($GLOBALS['q_config']['enabled_languages']) && is_array($GLOBALS['q_config']['enabled_languages']) ? (array) $GLOBALS['q_config']['enabled_languages'] : []; foreach ($cfg_enabled as $code_raw) { $code = sanitize_text_field((string) $code_raw); if ($code === '') { continue; } $label = ''; if (!empty($GLOBALS['q_config']['language_name'][$code])) { $label = (string) $GLOBALS['q_config']['language_name'][$code]; } elseif (!empty($GLOBALS['q_config']['locale'][$code])) { $label = (string) $GLOBALS['q_config']['locale'][$code]; } if ($label === '') { $label = strtoupper(str_replace('-', '_', $code)); } $languages[] = [ 'code' => $code, 'name' => sanitize_text_field($label), ]; } } } elseif ( $provider === 'multilingualpress' || is_multisite() && ( is_plugin_active('multilingual-press/multilingual-press.php') || is_plugin_active_for_network('multilingual-press/multilingual-press.php') || is_plugin_active('multilingual-press/multilingualpress.php') || is_plugin_active_for_network('multilingual-press/multilingualpress.php') || is_plugin_active('multilingualpress/multilingualpress.php') || is_plugin_active_for_network('multilingualpress/multilingualpress.php') ) ) { $mlp_sites = (array) get_site_option('inpsyde_multilingual', []); $name_by_code = [ 'en' => 'English', 'th' => 'Thai', 'ja' => 'Japanese', ]; foreach ($mlp_sites as $site_id => $site_lang) { if (!is_array($site_lang)) { continue; } $raw_locale = sanitize_text_field((string) ($site_lang['lang'] ?? '')); if ($raw_locale === '' || $raw_locale === '-1') { continue; } $code = aismart_mlp_lang_code_from_locale($raw_locale); if ($code === '') { continue; } $label = sanitize_text_field((string) ($site_lang['text'] ?? '')); if ($label === '') { $label = $name_by_code[$code] ?? strtoupper(str_replace('-', '_', $code)); } $languages[] = [ 'code' => $code, 'name' => $label, ]; } } elseif ( $provider === 'bogo' || function_exists('bogo_available_languages') || class_exists('Bogo', false) ) { $bogo_languages = []; if (function_exists('bogo_available_languages')) { $bogo_languages = (array) bogo_available_languages([ 'orderby' => 'key', 'order' => 'ASC', 'exclude_enus_if_inactive' => true, ]); } foreach ($bogo_languages as $locale => $name) { $locale = sanitize_text_field((string) $locale); if ($locale === '') { continue; } $code = aismart_bogo_locale_to_lang_code($locale); if ($code === '') { continue; } $label = is_scalar($name) ? (string) $name : ''; if ($label === '') { $label = strtoupper(str_replace('-', '_', $code)); } $languages[] = [ 'code' => $code, 'name' => sanitize_text_field($label), ]; } if (empty($languages) && function_exists('bogo_available_locales')) { $bogo_locales = (array) bogo_available_locales([ 'exclude_enus_if_inactive' => true, ]); foreach ($bogo_locales as $locale) { $locale = sanitize_text_field((string) $locale); if ($locale === '') { continue; } $code = aismart_bogo_locale_to_lang_code($locale); if ($code === '') { continue; } $languages[] = [ 'code' => $code, 'name' => strtoupper(str_replace('-', '_', $code)), ]; } } } elseif ( $provider === 'wpglobus' || class_exists('WPGlobus', false) || defined('WPGLOBUS_VERSION') ) { $wpglobus_option = get_option('wpglobus_option', []); $enabled_raw = is_array($wpglobus_option) && isset($wpglobus_option['enabled_languages']) && is_array($wpglobus_option['enabled_languages']) ? (array) $wpglobus_option['enabled_languages'] : []; $english_names = get_option('wpglobus_option_en_language_names', []); $native_names = get_option('wpglobus_option_language_names', []); if (!is_array($english_names)) { $english_names = []; } if (!is_array($native_names)) { $native_names = []; } foreach ($enabled_raw as $lang_key => $lang_value) { $code = is_string($lang_key) ? $lang_key : (string) $lang_value; $code = strtolower(sanitize_text_field($code)); if ($code === '') { continue; } if (empty($lang_value) && !is_string($lang_key)) { continue; } $label = ''; if (!empty($english_names[$code]) && is_scalar($english_names[$code])) { $label = (string) $english_names[$code]; } elseif (!empty($native_names[$code]) && is_scalar($native_names[$code])) { $label = (string) $native_names[$code]; } if ($label === '') { $label = strtoupper(str_replace('-', '_', $code)); } $languages[] = [ 'code' => $code, 'name' => sanitize_text_field($label), ]; } } else { $languages_table = $wpdb->prefix . 'icl_languages'; $languages_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $languages_table)); if ($languages_exists) { $rows = $wpdb->get_results("SELECT code, english_name FROM {$languages_table} WHERE active = 1"); foreach ($rows as $row) { $languages[] = [ 'code' => (string) $row->code, 'name' => (string) ($row->english_name ?: $row->code), ]; } } } if (!empty($languages)) { $languages = array_values(array_map('unserialize', array_unique(array_map('serialize', $languages)))); } $migration_active = aismart_detect_active_provider_key(); $migration_source = aismart_guess_previous_provider_key($migration_active); $migration_last_run = get_option('aismart_migration_bridge_last_run', []); if (!is_array($migration_last_run)) { $migration_last_run = []; } $site_timezone_name = function_exists('wp_timezone_string') ? (string) wp_timezone_string() : ''; if ($site_timezone_name === '') { $site_timezone_name = (string) get_option('timezone_string', ''); } if ($site_timezone_name === '') { $site_timezone_name = 'UTC'; } $site_gmt_offset = (float) get_option('gmt_offset', 0); $site_timezone_label = $site_timezone_name; if ($site_gmt_offset >= 0) { $site_timezone_label .= ' (UTC+' . rtrim(rtrim(number_format($site_gmt_offset, 2, '.', ''), '0'), '.') . ')'; } else { $site_timezone_label .= ' (UTC' . rtrim(rtrim(number_format($site_gmt_offset, 2, '.', ''), '0'), '.') . ')'; } aismart_send_json_success_clean([ 'auto_blog' => $auto_blog ? 1 : 0, 'auto_translate' => $auto_translate ? 1 : 0, 'schedule' => $schedule, 'schedule_time_daily' => $schedule_time_daily, 'schedule_times_three' => $schedule_times_three, 'default_lang_url_mode' => $default_lang_url_mode, 'languages' => $languages, 'multilang_provider' => $provider, 'multilang_detected' => (array) ($multilang['detected'] ?? []), 'workspace_id' => $workspace_id, 'workspace_label' => $workspace_label, 'credit_balance' => $credit_balance, 'wp_api_units' => $credit_balance, 'credit_summary' => $credit_summary, 'member_word_remaining' => $member_word_remaining, 'member_image_remaining' => $member_image_remaining, 'workspaces' => $workspace_options, 'categories' => $categories, 'tags' => $tags, 'site_timezone' => $site_timezone_name, 'site_timezone_label' => $site_timezone_label, 'site_gmt_offset' => $site_gmt_offset, 'translation_conflict' => [ 'active' => $translation_conflict_active ? 1 : 0, 'message' => $translation_conflict_active ? 'Polylang and ConveyThis are both active. This can cause language redirect loops. Deactivate ConveyThis (or keep only one multilingual plugin) before queuing new translations.' : '', ], 'migration_providers' => aismart_get_supported_provider_options(), 'migration_active_provider' => $migration_active, 'migration_suggested_source' => $migration_source, 'migration_last_run' => $migration_last_run, ]); } function aismart_normalize_schedule_clock($value, $fallback = '09:30') { $value = sanitize_text_field((string) $value); if (preg_match('/^([01]?\d|2[0-3]):([0-5]\d)$/', $value, $m)) { return str_pad((string) $m[1], 2, '0', STR_PAD_LEFT) . ':' . $m[2]; } return $fallback; } function aismart_parse_schedule_times_three($raw) { $defaults = ['09:30', '13:30', '19:30']; $times = []; if (is_array($raw)) { foreach ($raw as $entry) { $times[] = sanitize_text_field((string) $entry); } } else { $raw = sanitize_text_field((string) $raw); $parts = array_map('trim', explode(',', $raw)); foreach ($parts as $entry) { if ($entry !== '') { $times[] = $entry; } } } $normalized = []; foreach ($times as $entry) { if (preg_match('/^([01]?\d|2[0-3]):([0-5]\d)$/', (string) $entry, $m)) { $normalized[] = str_pad((string) $m[1], 2, '0', STR_PAD_LEFT) . ':' . $m[2]; } } $normalized = array_values(array_unique($normalized)); for ($i = 0; $i < 3; $i++) { if (!isset($normalized[$i])) { $normalized[$i] = $defaults[$i]; } } return array_slice($normalized, 0, 3); } function aismart_auto_blog_due_slot_now() { $enabled = (int) get_option('aismart_auto_blog', 0); if ($enabled !== 1) { return null; } $schedule = sanitize_text_field((string) get_option('aismart_schedule_preset', '3-per-day')); if (!in_array($schedule, ['1-per-day', '3-per-day', '5-per-week'], true)) { $schedule = '3-per-day'; } $daily_time = aismart_normalize_schedule_clock((string) get_option('aismart_schedule_time_daily', '09:30'), '09:30'); $times_three = aismart_parse_schedule_times_three((string) get_option('aismart_schedule_times_three', '09:30,13:30,19:30')); $tz = function_exists('wp_timezone') ? wp_timezone() : new DateTimeZone('UTC'); $now = new DateTime('now', $tz); $hhmm = $now->format('H:i'); $date_key = $now->format('Y-m-d'); $weekday = (int) $now->format('N'); $allowed_times = []; if ($schedule === '1-per-day') { $allowed_times[] = $daily_time; } elseif ($schedule === '3-per-day') { $allowed_times = $times_three; } else { if ($weekday <= 5) { $allowed_times[] = $daily_time; } } if (!in_array($hhmm, $allowed_times, true)) { return null; } return [ 'slot_key' => $date_key . '|' . $schedule . '|' . $hhmm, 'date' => $date_key, 'time' => $hhmm, 'schedule' => $schedule, ]; } function aismart_auto_blog_pick_next_queue_topic() { $items = aismart_topic_queue_get_items(); if (!is_array($items) || empty($items)) { return null; } foreach ($items as $index => $item) { if (!is_array($item)) { continue; } $title = aismart_topic_queue_normalize_title($item['title'] ?? ''); if ($title === '') { continue; } if (!empty($item['written'])) { continue; } return [ 'queue_id' => (int) $index + 1, 'title' => $title, ]; } return null; } function aismart_auto_blog_resolve_author_id() { $current = (int) get_current_user_id(); if ($current > 0) { return $current; } $admin_users = get_users([ 'role' => 'administrator', 'number' => 1, 'fields' => ['ID'], 'orderby' => 'ID', 'order' => 'ASC', ]); if (is_array($admin_users) && !empty($admin_users[0]->ID)) { return (int) $admin_users[0]->ID; } return 1; } function aismart_auto_blog_create_post_from_queue_topic($topic, $queue_id = 0) { global $wpdb; $topic = aismart_topic_queue_normalize_title($topic); if ($topic === '') { return new WP_Error('empty_topic', 'Queue topic is empty'); } if (aismart_get_current_workspace_credit_balance() <= 0) { return new WP_Error('no_credit', 'Workspace has no credits'); } aismart_require_media_includes(); $api_result = aismart_request_posts_api([ 'site' => 'site2', 'topic' => $topic, 'skip_image' => 0, 'image_prompt' => aismart_build_image_prompt($topic, 'Auto', '', ''), ], 60); $used_local_fallback = false; $fallback_reason = ''; if (empty($api_result['ok'])) { $data = aismart_build_local_fallback_post_data($topic, ''); $used_local_fallback = true; $fallback_reason = !empty($api_result['message']) ? (string) $api_result['message'] : 'posts-api-failed'; } else { $data = (array) $api_result['data']; } $resolved_title = aismart_normalize_post_title((string) ($data['title'] ?? $topic)); if ($resolved_title === '') { $resolved_title = $topic; } $target_post_id = (int) $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status IN ('draft','publish','pending','future','private') AND post_title = %s ORDER BY ID DESC LIMIT 1", $resolved_title ) ); $post_content = aismart_cleanup_translated_post_content((string) ($data['content'] ?? '')); $post_excerpt = wp_trim_words(wp_strip_all_tags($post_content), 40, '...'); $post_data = [ 'post_title' => $resolved_title, 'post_content' => $post_content, 'post_excerpt' => $post_excerpt, 'post_status' => 'publish', 'post_author' => aismart_auto_blog_resolve_author_id(), 'post_type' => 'post', ]; if ($target_post_id > 0) { $post_data['ID'] = $target_post_id; $post_id = wp_update_post($post_data, true); } else { $post_id = wp_insert_post($post_data, true); } if (is_wp_error($post_id) || (int) $post_id <= 0) { return new WP_Error('insert_failed', is_wp_error($post_id) ? $post_id->get_error_message() : 'Failed to create post'); } $provider = aismart_resolve_selected_provider_key(); if ($provider !== '') { update_post_meta((int) $post_id, '_aismart_created_provider', $provider); update_post_meta((int) $post_id, '_aismart_last_multilang_provider', $provider); } update_post_meta((int) $post_id, '_aismart_generated_post', 1); update_post_meta((int) $post_id, '_aismart_generated_at', gmdate('c')); if ($used_local_fallback) { update_post_meta((int) $post_id, '_aismart_local_fallback', 1); if ($fallback_reason !== '') { update_post_meta((int) $post_id, '_aismart_local_fallback_reason', sanitize_text_field($fallback_reason)); } } else { delete_post_meta((int) $post_id, '_aismart_local_fallback'); delete_post_meta((int) $post_id, '_aismart_local_fallback_reason'); } $category_name = 'Auto'; $category_slug = sanitize_title($category_name); $category_term = get_term_by('slug', $category_slug, 'category'); if (!$category_term) { $category_term = get_term_by('name', $category_name, 'category'); } if (!$category_term || is_wp_error($category_term)) { $created_category = wp_insert_term($category_name, 'category', ['slug' => $category_slug]); if (!is_wp_error($created_category) && !empty($created_category['term_id'])) { $category_term = get_term((int) $created_category['term_id'], 'category'); } } if ($category_term && !is_wp_error($category_term)) { wp_set_post_categories((int) $post_id, [(int) $category_term->term_id], false); } $image_url = isset($data['image_url']) ? (string) $data['image_url'] : ''; if ($image_url === '' && isset($data['featured_image'])) { $image_url = (string) $data['featured_image']; } if ($image_url === '' || !filter_var($image_url, FILTER_VALIDATE_URL)) { $image_url = aismart_build_backup_image_url($resolved_title, $category_name, '', $post_excerpt); } if ($image_url !== '' && filter_var($image_url, FILTER_VALIDATE_URL)) { aismart_attach_featured_image_from_url((int) $post_id, $image_url, 'Generated image for ' . $resolved_title); } $queue_marked = false; if ((int) $queue_id > 0) { $queue_marked = aismart_topic_queue_mark_written_by_queue_id((int) $queue_id, (int) $post_id, $provider); } if (!$queue_marked) { aismart_topic_queue_mark_written($topic, (int) $post_id, $provider); } if ((function_exists('mb_strtolower') ? mb_strtolower($resolved_title) : strtolower($resolved_title)) !== (function_exists('mb_strtolower') ? mb_strtolower($topic) : strtolower($topic))) { aismart_topic_queue_mark_written($resolved_title, (int) $post_id, $provider); } $translation_queued = false; if ((int) get_option('aismart_auto_translate', 0) === 1) { $route = aismart_detect_translation_command(); $queue_engine = isset($route['engine']) ? sanitize_key((string) $route['engine']) : 'unknown'; if (in_array($queue_engine, ['bogo', 'wpml', 'polylang'], true)) { $args = [(int) $post_id, $queue_engine === 'bogo' ? 'bogo' : $queue_engine]; if (!wp_next_scheduled('aismart_process_translation_event', $args)) { wp_schedule_single_event(time() + 1, 'aismart_process_translation_event', $args); } if (function_exists('spawn_cron')) { @spawn_cron(time()); } aismart_defer_background_translation((int) $post_id, $queue_engine); $translation_queued = true; } else { $translation_queued = (bool) aismart_queue_translation_for_post((int) $post_id); } if ($translation_queued) { update_post_meta((int) $post_id, '_aismart_translation_queued', 1); update_post_meta((int) $post_id, '_aismart_translation_queue_last_at', gmdate('c')); } elseif (function_exists('aismart_queue_log')) { $last_error = (string) get_post_meta((int) $post_id, '_aismart_translation_last_error', true); aismart_queue_log( 'Auto-blog translation queue failed post_id=' . (int) $post_id . ' engine=' . $queue_engine . ' error=' . sanitize_text_field($last_error) ); } } return [ 'post_id' => (int) $post_id, 'title' => $resolved_title, 'fallback' => $used_local_fallback ? 1 : 0, 'fallback_reason' => $fallback_reason, 'translation_queued' => $translation_queued ? 1 : 0, ]; } function aismart_auto_blog_maybe_run($source = 'cron') { static $running = false; if ($running) { return; } $running = true; try { $due = aismart_auto_blog_due_slot_now(); if (!is_array($due) || empty($due['slot_key'])) { return; } $slot_key = sanitize_text_field((string) $due['slot_key']); $last_slot_key = sanitize_text_field((string) get_option('aismart_auto_blog_last_slot_key', '')); if ($slot_key !== '' && $slot_key === $last_slot_key) { return; } if (get_transient('aismart_auto_blog_worker_lock')) { return; } set_transient('aismart_auto_blog_worker_lock', 1, 55); try { $next = aismart_auto_blog_pick_next_queue_topic(); if (!is_array($next) || empty($next['title'])) { if (function_exists('aismart_queue_log')) { aismart_queue_log('Auto-blog skip: no pending queue topic for slot=' . $slot_key . ' source=' . sanitize_key((string) $source)); } } else { $result = aismart_auto_blog_create_post_from_queue_topic((string) $next['title'], (int) ($next['queue_id'] ?? 0)); if (is_wp_error($result)) { if (function_exists('aismart_queue_log')) { aismart_queue_log( 'Auto-blog failed slot=' . $slot_key . ' source=' . sanitize_key((string) $source) . ' topic=' . sanitize_text_field((string) $next['title']) . ' error=' . sanitize_text_field($result->get_error_message()) ); } } else { if (function_exists('aismart_queue_log')) { aismart_queue_log( 'Auto-blog created slot=' . $slot_key . ' source=' . sanitize_key((string) $source) . ' topic=' . sanitize_text_field((string) ($next['title'] ?? '')) . ' post_id=' . (int) ($result['post_id'] ?? 0) . ' fallback=' . (int) ($result['fallback'] ?? 0) ); } } } update_option('aismart_auto_blog_last_slot_key', $slot_key, false); update_option('aismart_auto_blog_last_run', [ 'slot_key' => $slot_key, 'ran_at' => gmdate('c'), 'source' => sanitize_key((string) $source), ], false); } finally { delete_transient('aismart_auto_blog_worker_lock'); } } finally { $running = false; } } add_action('aismart_auto_blog_cron_event', 'aismart_auto_blog_cron_event_handler'); function aismart_auto_blog_cron_event_handler() { aismart_auto_blog_maybe_run('cron'); } function aismart_repair_missed_schedule_worker($source = 'cron') { static $running = false; if ($running) { return; } if (get_transient('aismart_missed_schedule_repair_lock')) { return; } $running = true; set_transient('aismart_missed_schedule_repair_lock', 1, 55); try { $now_gmt = current_time('timestamp', true); $publish_hook = 'publish_future_post'; $window_seconds = 120; $batch_limit = 20; $query = new WP_Query([ 'post_type' => 'any', 'post_status' => 'future', 'posts_per_page' => $batch_limit, 'orderby' => 'date', 'order' => 'ASC', 'fields' => 'ids', 'no_found_rows' => true, 'cache_results' => false, 'update_post_meta_cache' => false, 'update_post_term_cache' => false, ]); if (empty($query->posts) || !is_array($query->posts)) { return; } $published = 0; $requeued = 0; foreach ($query->posts as $post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { continue; } $post = get_post($post_id); if (!$post || $post->post_status !== 'future') { continue; } $gmt_date = (string) $post->post_date_gmt; if ($gmt_date === '' || $gmt_date === '0000-00-00 00:00:00') { $gmt_date = get_gmt_from_date((string) $post->post_date); } if ($gmt_date === '' || $gmt_date === '0000-00-00 00:00:00') { continue; } $target_ts = strtotime($gmt_date . ' GMT'); if (!$target_ts) { continue; } $args = [$post_id]; $scheduled_ts = wp_next_scheduled($publish_hook, $args); if ($target_ts <= ($now_gmt - 30)) { if ($scheduled_ts) { wp_clear_scheduled_hook($publish_hook, $args); } wp_publish_post($post_id); $published++; continue; } if ($target_ts <= ($now_gmt + $window_seconds)) { if (!$scheduled_ts || abs((int) $scheduled_ts - (int) $target_ts) > $window_seconds) { if ($scheduled_ts) { wp_clear_scheduled_hook($publish_hook, $args); } wp_schedule_single_event($target_ts, $publish_hook, $args); $requeued++; } } } if (($published > 0 || $requeued > 0) && function_exists('aismart_queue_log')) { aismart_queue_log( 'Missed-schedule repair source=' . sanitize_key((string) $source) . ' published=' . (int) $published . ' requeued=' . (int) $requeued ); } } finally { delete_transient('aismart_missed_schedule_repair_lock'); $running = false; } } add_action('aismart_repair_missed_schedule_event', 'aismart_repair_missed_schedule_cron_handler'); function aismart_repair_missed_schedule_cron_handler() { aismart_repair_missed_schedule_worker('cron'); } add_action('init', 'aismart_auto_blog_inline_event_handler', 50); function aismart_auto_blog_inline_event_handler() { if (defined('DOING_CRON') && DOING_CRON) { return; } aismart_auto_blog_maybe_run('inline'); } add_action('wp_ajax_aismart_switch_workspace', 'aismart_switch_workspace'); function aismart_switch_workspace() { if (!current_user_can('manage_options')) { aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } $workspace_id = isset($_POST['workspace_id']) ? (int) $_POST['workspace_id'] : 0; if ($workspace_id <= 0) { aismart_send_json_error_clean(['message' => 'Invalid workspace id'], 422); } $workspaces = aismart_workspace_get_available(true); $selected = null; foreach ($workspaces as $workspace) { if ((int) ($workspace['workspace_id'] ?? 0) === $workspace_id) { $selected = $workspace; break; } } if (!$selected) { aismart_send_json_error_clean(['message' => 'Workspace not found for this account'], 404); } $workspace_name = sanitize_text_field((string) ($selected['workspace_name'] ?? ('Workspace #' . $workspace_id))); update_option('aismart_workspace_id', $workspace_id, false); update_option('aismart_workspace_name', $workspace_name, false); if (isset($selected['credit_balance']) && is_numeric($selected['credit_balance'])) { update_option('aismart_workspace_credits_' . $workspace_id, (float) $selected['credit_balance'], false); update_option('aismart_credit_balance', (float) $selected['credit_balance'], false); update_option('aismart_credits_available', (float) $selected['credit_balance'], false); } $state = aismart_onboarding_state_get(); if (!is_array($state)) { $state = []; } $state['workspace_id'] = $workspace_id; $state['workspace_name'] = $workspace_name; $state['workspace_switched_at'] = gmdate('c'); aismart_onboarding_state_set($state); $ctx = aismart_resolve_workspace_context(); aismart_send_json_success_clean([ 'message' => 'Workspace switched', 'workspace_id' => (int) $ctx['workspace_id'], 'workspace_label' => (string) $ctx['workspace_label'], 'credit_balance' => (float) $ctx['credit_balance'], 'workspaces' => $workspaces, ]); } // AJAX: Update automation settings add_action('wp_ajax_aismart_update_settings', 'aismart_update_settings'); function aismart_update_settings() { if (!current_user_can('manage_options')) { error_log('AISmart Update Settings: Unauthorized access attempt'); aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } $auto_blog_val = isset($_POST['auto_blog']) ? wp_unslash($_POST['auto_blog']) : 0; $auto_translate_val = isset($_POST['auto_translate']) ? wp_unslash($_POST['auto_translate']) : 0; $schedule_val = isset($_POST['schedule']) ? wp_unslash($_POST['schedule']) : '3-per-day'; $schedule_time_daily_val = isset($_POST['schedule_time_daily']) ? wp_unslash($_POST['schedule_time_daily']) : '09:30'; $schedule_times_three_val = isset($_POST['schedule_times_three']) ? wp_unslash($_POST['schedule_times_three']) : ['09:30', '13:30', '19:30']; $default_lang_url_mode_val = isset($_POST['default_lang_url_mode']) ? wp_unslash($_POST['default_lang_url_mode']) : null; // Use filter_var for robust boolean conversion (handles "1", "true", "on", 1, true) $auto_blog = filter_var($auto_blog_val, FILTER_VALIDATE_BOOLEAN) ? 1 : 0; $auto_translate = filter_var($auto_translate_val, FILTER_VALIDATE_BOOLEAN) ? 1 : 0; $schedule = sanitize_text_field(wp_unslash($schedule_val)); $schedule_time_daily = aismart_normalize_schedule_clock(wp_unslash((string) $schedule_time_daily_val), '09:30'); $schedule_times_three = aismart_parse_schedule_times_three( is_array($schedule_times_three_val) ? wp_unslash($schedule_times_three_val) : wp_unslash((string) $schedule_times_three_val) ); if ($default_lang_url_mode_val !== null) { $mode = sanitize_key((string) $default_lang_url_mode_val); if (!in_array($mode, ['native', 'prefixed'], true)) { $mode = 'native'; } update_option('aismart_default_lang_url_mode', $mode, false); } update_option('aismart_auto_blog', $auto_blog, false); update_option('aismart_auto_translate', $auto_translate, false); update_option('aismart_schedule_preset', $schedule, false); update_option('aismart_schedule_time_daily', $schedule_time_daily, false); update_option('aismart_schedule_times_three', implode(',', $schedule_times_three), false); error_log("AISmart Saved: Blog=$auto_blog, Trans=$auto_translate, Sched=$schedule"); aismart_send_json_success_clean([ 'message' => 'Settings updated', 'saved' => [ 'blog' => $auto_blog, 'trans' => $auto_translate, 'default_lang_url_mode' => aismart_get_default_lang_url_mode(), ], ]); } function aismart_get_default_lang_url_mode() { $mode = sanitize_key((string) get_option('aismart_default_lang_url_mode', 'native')); if (!in_array($mode, ['native', 'prefixed'], true)) { $mode = 'native'; } return $mode; } function aismart_should_force_default_lang_prefix($provider = '') { if (aismart_get_default_lang_url_mode() !== 'prefixed') { return false; } $provider = sanitize_key((string) $provider); if ($provider === '') { $route = aismart_detect_translation_command(); $provider = isset($route['engine']) ? sanitize_key((string) $route['engine']) : ''; } $supported = ['bogo']; return in_array($provider, $supported, true); } function aismart_migration_parse_selected_ids($raw, $allowed_ids = []) { $allowed_lookup = []; foreach ((array) $allowed_ids as $id) { $id = (int) $id; if ($id > 0) { $allowed_lookup[$id] = true; } } $pieces = preg_split('/[^0-9]+/', (string) $raw); if (!is_array($pieces)) { return []; } $ids = []; foreach ($pieces as $piece) { $id = (int) $piece; if ($id <= 0) { continue; } if (!empty($allowed_lookup) && empty($allowed_lookup[$id])) { continue; } $ids[$id] = $id; } return array_values($ids); } function aismart_migration_preview_posts($post_ids, $limit = 60) { $limit = (int) $limit; if ($limit <= 0) { $limit = 60; } if ($limit > 200) { $limit = 200; } $preview = []; foreach ((array) $post_ids as $id_raw) { if (count($preview) >= $limit) { break; } $post_id = (int) $id_raw; if ($post_id <= 0) { continue; } $post = get_post($post_id); if (!$post || $post->post_type !== 'post') { continue; } $preview[] = [ 'id' => $post_id, 'title' => (string) $post->post_title, 'date' => mysql2date('Y-m-d', (string) $post->post_date), 'status' => (string) $post->post_status, 'provider' => aismart_detect_provider_from_post($post_id), ]; } return $preview; } function aismart_migration_collect_polylang_family($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return []; } $translations = []; $default_lang = function_exists('pll_default_language') ? (string) pll_default_language('slug') : 'en'; if (function_exists('pll_get_post_translations')) { $translations = (array) pll_get_post_translations($post_id); if (empty($translations)) { $lang = function_exists('pll_get_post_language') ? (string) pll_get_post_language($post_id, 'slug') : ''; if ($lang !== '') { $translations[$lang] = $post_id; } } } $family_ids = []; $nodes = []; if (!empty($translations)) { foreach ($translations as $lang_code => $translation_id_raw) { $translation_id = (int) $translation_id_raw; if ($translation_id <= 0) { continue; } $translation_post = get_post($translation_id); if (!$translation_post || $translation_post->post_type !== 'post') { continue; } $lang = strtolower(sanitize_text_field((string) $lang_code)); if ($lang === '') { $lang = function_exists('pll_get_post_language') ? (string) pll_get_post_language($translation_id, 'slug') : ''; $lang = strtolower(sanitize_text_field($lang)); } if ($lang === '') { continue; } $family_ids[] = $translation_id; $nodes[$lang] = [ 'post_id' => $translation_id, 'lang' => $lang, 'title' => (string) $translation_post->post_title, 'excerpt' => (string) $translation_post->post_excerpt, 'content' => (string) $translation_post->post_content, ]; } } if (empty($nodes)) { global $wpdb; $root_id = (int) get_post_meta($post_id, '_aismart_source_post_id', true); if ($root_id <= 0) { $root_id = $post_id; } $legacy_ids = [$post_id, $root_id]; $child_ids = $wpdb->get_col( $wpdb->prepare( "SELECT post_id FROM {$wpdb->postmeta} WHERE meta_key = %s AND meta_value = %d", '_aismart_source_post_id', $root_id ) ); if (is_array($child_ids)) { foreach ($child_ids as $child_id_raw) { $legacy_ids[] = (int) $child_id_raw; } } $legacy_ids = array_values(array_unique(array_filter(array_map('intval', $legacy_ids)))); foreach ($legacy_ids as $legacy_id) { $translation_post = get_post($legacy_id); if (!$translation_post || $translation_post->post_type !== 'post') { continue; } $text_for_detect = (string) $translation_post->post_title . "\n" . (string) $translation_post->post_content; $lang = 'en'; if (preg_match('/\p{Thai}/u', $text_for_detect)) { $lang = 'th'; } elseif (preg_match('/[\p{Hiragana}\p{Katakana}\p{Han}]/u', $text_for_detect)) { $lang = 'ja'; } if (isset($nodes[$lang])) { $existing_len = strlen((string) ($nodes[$lang]['content'] ?? '')); $candidate_len = strlen((string) $translation_post->post_content); if ($candidate_len <= $existing_len) { continue; } } $family_ids[] = $legacy_id; $nodes[$lang] = [ 'post_id' => $legacy_id, 'lang' => $lang, 'title' => (string) $translation_post->post_title, 'excerpt' => (string) $translation_post->post_excerpt, 'content' => (string) $translation_post->post_content, ]; } } if (empty($nodes) || empty($family_ids)) { return []; } sort($family_ids); $canonical_id = (int) $family_ids[0]; if ($default_lang !== '' && isset($translations[$default_lang])) { $candidate = (int) $translations[$default_lang]; if ($candidate > 0) { $canonical_id = $candidate; } } return [ 'canonical_id' => $canonical_id, 'default_lang' => $default_lang !== '' ? $default_lang : 'en', 'nodes' => $nodes, 'family_ids' => $family_ids, ]; } function aismart_migration_qtranslate_parity_mismatches($nodes, $wrapped_title, $wrapped_excerpt, $wrapped_content) { $mismatches = 0; foreach ((array) $nodes as $lang => $node) { $lang = strtolower(sanitize_text_field((string) $lang)); if ($lang === '') { continue; } $src_title = aismart_normalize_compare_text((string) ($node['title'] ?? '')); $dst_title = aismart_normalize_compare_text(aismart_qtranslate_extract_segment($wrapped_title, $lang)); if ($src_title !== '' && $dst_title !== '' && $src_title !== $dst_title) { $mismatches++; } $src_excerpt = aismart_normalize_compare_text((string) ($node['excerpt'] ?? '')); $dst_excerpt = aismart_normalize_compare_text(aismart_qtranslate_extract_segment($wrapped_excerpt, $lang)); if ($src_excerpt !== '' && $dst_excerpt !== '' && $src_excerpt !== $dst_excerpt) { $mismatches++; } $src_content = aismart_normalize_compare_text((string) ($node['content'] ?? '')); $dst_content = aismart_normalize_compare_text(aismart_qtranslate_extract_segment($wrapped_content, $lang)); if ($src_content !== '' && $dst_content !== '' && $src_content !== $dst_content) { $mismatches++; } } return $mismatches; } function aismart_migration_retire_legacy_posts($post_ids, $reason = '') { $retired = 0; foreach ((array) $post_ids as $id_raw) { $post_id = (int) $id_raw; if ($post_id <= 0) { continue; } $post = get_post($post_id); if (!$post || $post->post_type !== 'post') { continue; } if ((string) $post->post_status !== 'draft') { wp_update_post([ 'ID' => $post_id, 'post_status' => 'draft', ]); } update_post_meta($post_id, '_aismart_migration_legacy_retired', 1); update_post_meta($post_id, '_aismart_migration_legacy_retired_at', gmdate('c')); if ($reason !== '') { update_post_meta($post_id, '_aismart_migration_legacy_reason', sanitize_text_field($reason)); } $retired++; } return $retired; } function aismart_migration_execute_polylang_to_qtranslate($selected_ids, $mode = 'preserve', $retire_legacy = false) { $mode = sanitize_key((string) $mode); if (!in_array($mode, ['preserve', 'clean'], true)) { $mode = 'preserve'; } $selected_ids = array_values(array_unique(array_map('intval', (array) $selected_ids))); if (empty($selected_ids)) { return [ 'executed' => false, 'message' => 'No selected posts to execute.', 'families_total' => 0, 'families_processed' => 0, 'posts_updated' => 0, 'posts_created' => 0, 'legacy_retired' => 0, 'parity_failed' => 0, 'errors' => [], ]; } $family_keys = []; $families = []; foreach ($selected_ids as $post_id) { $family = aismart_migration_collect_polylang_family($post_id); if (empty($family) || empty($family['family_ids'])) { continue; } $family_ids = array_values(array_unique(array_map('intval', (array) $family['family_ids']))); sort($family_ids); $key = implode(',', $family_ids); if (isset($family_keys[$key])) { continue; } $family_keys[$key] = true; $families[] = $family; } $result = [ 'executed' => true, 'mode' => $mode, 'families_total' => count($families), 'families_processed' => 0, 'posts_updated' => 0, 'posts_created' => 0, 'legacy_retired' => 0, 'parity_failed' => 0, 'errors' => [], 'sample_targets' => [], ]; foreach ($families as $family) { $nodes = (array) ($family['nodes'] ?? []); if (empty($nodes)) { continue; } $canonical_id = (int) ($family['canonical_id'] ?? 0); $default_lang = strtolower(sanitize_text_field((string) ($family['default_lang'] ?? 'en'))); if ($default_lang === '' || !isset($nodes[$default_lang])) { $langs = array_keys($nodes); $default_lang = !empty($langs) ? (string) $langs[0] : 'en'; } $title_segments = []; $excerpt_segments = []; $content_segments = []; foreach ($nodes as $lang => $node) { $lang = strtolower(sanitize_text_field((string) $lang)); if ($lang === '') { continue; } $title_segments[$lang] = aismart_normalize_post_title((string) ($node['title'] ?? '')); $excerpt_segments[$lang] = (string) ($node['excerpt'] ?? ''); $content_segments[$lang] = aismart_cleanup_translated_post_content((string) ($node['content'] ?? '')); } $wrapped_title = aismart_qtranslate_wrap_segments($title_segments, $default_lang); $wrapped_excerpt = aismart_qtranslate_wrap_segments($excerpt_segments, $default_lang); $wrapped_content = aismart_qtranslate_wrap_segments($content_segments, $default_lang); $target_post_id = $canonical_id; if ($mode === 'clean') { $signature = md5(implode(',', (array) ($family['family_ids'] ?? [])) . '|polylang|qtranslate'); $rebuilt_id = (int) get_post_meta($canonical_id, '_aismart_migration_clean_qtranslate_post_id', true); if ($rebuilt_id > 0 && get_post($rebuilt_id)) { $target_post_id = $rebuilt_id; } else { $source_post = get_post($canonical_id); if (!$source_post || $source_post->post_type !== 'post') { $result['errors'][] = 'Skip family: canonical post missing #' . $canonical_id; continue; } $insert_id = wp_insert_post([ 'post_type' => 'post', 'post_status' => 'draft', 'post_author' => (int) $source_post->post_author, 'post_title' => (string) ($title_segments[$default_lang] ?? $source_post->post_title), 'post_excerpt' => (string) ($excerpt_segments[$default_lang] ?? $source_post->post_excerpt), 'post_content' => (string) ($content_segments[$default_lang] ?? $source_post->post_content), ], true); if (is_wp_error($insert_id) || (int) $insert_id <= 0) { $result['errors'][] = 'Create clean target failed family canonical #' . $canonical_id; continue; } $target_post_id = (int) $insert_id; $result['posts_created']++; $category_ids = wp_get_post_categories($canonical_id); if (!empty($category_ids)) { wp_set_post_categories($target_post_id, $category_ids, false); } $tag_ids = wp_get_post_tags($canonical_id, ['fields' => 'ids']); if (!empty($tag_ids)) { wp_set_post_tags($target_post_id, $tag_ids, false); } $thumbnail_id = (int) get_post_thumbnail_id($canonical_id); if ($thumbnail_id > 0) { set_post_thumbnail($target_post_id, $thumbnail_id); } update_post_meta($target_post_id, '_aismart_migration_clean_signature', $signature); update_post_meta($target_post_id, '_aismart_created_provider', 'qtranslate'); update_post_meta($canonical_id, '_aismart_migration_clean_qtranslate_post_id', $target_post_id); } } $update_result = wp_update_post([ 'ID' => $target_post_id, 'post_title' => $wrapped_title, 'post_excerpt' => $wrapped_excerpt, 'post_content' => $wrapped_content, ], true); if (is_wp_error($update_result)) { $result['errors'][] = 'Update failed target #' . $target_post_id . ' : ' . $update_result->get_error_message(); continue; } update_post_meta($target_post_id, '_aismart_created_provider', 'qtranslate'); update_post_meta($target_post_id, '_aismart_last_multilang_provider', 'qtranslate'); update_post_meta($target_post_id, '_aismart_migration_v21_mode', $mode); update_post_meta($target_post_id, '_aismart_migration_v21_source', 'polylang'); update_post_meta($target_post_id, '_aismart_migration_v21_at', gmdate('c')); $mismatch_count = aismart_migration_qtranslate_parity_mismatches($nodes, $wrapped_title, $wrapped_excerpt, $wrapped_content); if ($mismatch_count > 0) { $result['parity_failed']++; } if ($retire_legacy && $mismatch_count === 0) { $legacy_ids = array_values(array_unique(array_map('intval', (array) ($family['family_ids'] ?? [])))); if ($mode === 'preserve') { $legacy_ids = array_values(array_filter($legacy_ids, static function ($id) use ($target_post_id) { return (int) $id !== (int) $target_post_id; })); } $result['legacy_retired'] += (int) aismart_migration_retire_legacy_posts( $legacy_ids, 'V2.1 migration ' . $mode . ' polylang->qtranslate' ); } $result['families_processed']++; $result['posts_updated']++; if (count($result['sample_targets']) < 30) { $result['sample_targets'][] = $target_post_id; } } $result['message'] = sprintf( 'Executed %d/%d families. Updated %d post(s), created %d, parity failed %d, legacy retired %d.', (int) $result['families_processed'], (int) $result['families_total'], (int) $result['posts_updated'], (int) $result['posts_created'], (int) $result['parity_failed'], (int) $result['legacy_retired'] ); return $result; } add_action('wp_ajax_aismart_run_migration_bridge', 'aismart_run_migration_bridge'); function aismart_run_migration_bridge() { if (!current_user_can('manage_options')) { aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } $labels = aismart_supported_provider_labels(); $mode = sanitize_key((string) ($_POST['mode'] ?? 'preserve')); if (!in_array($mode, ['preserve', 'clean'], true)) { $mode = 'preserve'; } $target = aismart_normalize_provider_key($_POST['target_provider'] ?? ''); if ($target === '' || $target === 'auto') { $target = aismart_detect_active_provider_key(); } if ($target === '' || !isset($labels[$target])) { aismart_send_json_error_clean(['message' => 'Target plugin not resolved. Please choose target plugin.'], 422); } $source = aismart_normalize_provider_key($_POST['source_provider'] ?? ''); if ($source === '' || $source === 'auto') { $source = aismart_guess_previous_provider_key($target); } if ($source === '' || !isset($labels[$source])) { aismart_send_json_error_clean(['message' => 'Source plugin not resolved. Please choose source plugin.'], 422); } if ($source === $target) { aismart_send_json_error_clean(['message' => 'Source and target cannot be the same plugin.'], 422); } $execute = !empty($_POST['execute']) && filter_var($_POST['execute'], FILTER_VALIDATE_BOOLEAN); $retire_legacy = !empty($_POST['retire_legacy']) && filter_var($_POST['retire_legacy'], FILTER_VALIDATE_BOOLEAN); global $wpdb; $scan_limit = isset($_POST['scan_limit']) ? (int) $_POST['scan_limit'] : 250; if ($scan_limit < 50) { $scan_limit = 50; } if ($scan_limit > 1000) { $scan_limit = 1000; } $post_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type='post' AND post_status IN ('publish','draft','pending','future','private') ORDER BY ID DESC LIMIT %d", $scan_limit ) ); $candidate_ids = []; if (is_array($post_ids)) { foreach ($post_ids as $id_raw) { $post_id = (int) $id_raw; if ($post_id <= 0) { continue; } $provider = aismart_normalize_provider_key(aismart_detect_provider_from_post($post_id)); if ($provider === $source) { $candidate_ids[] = $post_id; } } } $selected_raw = isset($_POST['selected_post_ids']) ? (string) wp_unslash($_POST['selected_post_ids']) : ''; $selected_allowlist = $candidate_ids; if ($execute && trim($selected_raw) !== '') { $selected_allowlist = []; } $selected_ids = aismart_migration_parse_selected_ids($selected_raw, $selected_allowlist); if (empty($selected_ids)) { $selected_ids = array_values($candidate_ids); } $plan = [ 'ran_at' => gmdate('c'), 'mode' => $mode, 'source_provider' => $source, 'target_provider' => $target, 'source_label' => (string) $labels[$source], 'target_label' => (string) $labels[$target], 'scan_limit' => $scan_limit, 'scan_posts' => is_array($post_ids) ? count($post_ids) : 0, 'candidate_posts' => count($candidate_ids), 'selected_posts' => count($selected_ids), 'sample_candidate_post_ids' => array_slice(array_values($candidate_ids), 0, 30), 'sample_selected_post_ids' => array_slice(array_values($selected_ids), 0, 30), 'candidate_preview' => aismart_migration_preview_posts($candidate_ids, 60), 'selected_preview' => aismart_migration_preview_posts($selected_ids, 60), 'execution' => $execute ? 'phase2-execute' : 'planning-safe', 'retire_legacy' => $retire_legacy ? 1 : 0, 'next' => $mode === 'clean' ? 'Fallback rebuild mode selected: recreate target plugin entries from existing translated text, then retire legacy IDs after parity checks.' : 'Preserve mode selected: convert/re-link existing translation graph into target plugin format.', ]; $execution_result = null; if ($execute) { if ($source === 'polylang' && $target === 'qtranslate') { $execution_result = aismart_migration_execute_polylang_to_qtranslate($selected_ids, $mode, $retire_legacy); $plan['execution_result'] = $execution_result; } else { aismart_send_json_error_clean([ 'message' => 'Phase 2 execution currently supports Polylang -> qTranslate only. You can still run planning mode for other pairs.', 'plan' => $plan, ], 422); } } update_option('aismart_migration_last_source_provider', $source, false); update_option('aismart_migration_bridge_last_run', $plan, false); $message = $mode === 'clean' ? 'Clean Migration ' . ($execute ? 'executed.' : 'plan prepared.') : 'Migration ' . ($execute ? 'executed.' : 'plan prepared. If result is not correct, use Clean Migration fallback mode.'); aismart_send_json_success_clean([ 'message' => $message, 'plan' => $plan, 'execution_result' => $execution_result, ]); } // AJAX: Get translation status list add_action('wp_ajax_aismart_get_translation_status', 'aismart_get_translation_status'); add_action('wp_ajax_aismart_get_translation_status_fallback_info', 'aismart_get_translation_status_fallback_info'); function aismart_get_translation_status_fallback_info() { if (!current_user_can('manage_options')) { aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } $detected = (array) aismart_onboarding_detect_multilang_provider(); $active_provider = aismart_normalize_provider_key($detected['primary'] ?? ''); $state = aismart_onboarding_state_get(); $saved_provider = aismart_normalize_provider_key($state['multilang_provider'] ?? ''); $legacy_provider = aismart_normalize_provider_key(aismart_guess_previous_provider_key($active_provider)); if ($legacy_provider !== '' && $legacy_provider === $active_provider) { $legacy_provider = ''; } $active_label = aismart_provider_label($active_provider); if ($active_label === '' && $active_provider !== '') { $active_label = strtoupper($active_provider); } $legacy_label = aismart_provider_label($legacy_provider); if ($legacy_label === '' && $legacy_provider !== '') { $legacy_label = strtoupper($legacy_provider); } $reason = ''; if ($legacy_provider !== '' && $active_provider !== '' && $legacy_provider !== $active_provider) { $reason = 'No rows found under active provider ' . $active_label . '. Trying legacy provider ' . $legacy_label . ' based on existing queue/history.'; } elseif ($active_provider !== '') { $reason = 'No rows found under active provider ' . $active_label . '. No legacy provider was detected for fallback.'; } else { $reason = 'No active multilingual provider detected for strict status mode.'; } aismart_send_json_success_clean([ 'active_provider' => $active_provider, 'saved_provider' => $saved_provider, 'legacy_provider' => $legacy_provider, 'active_label' => $active_label, 'legacy_label' => $legacy_label, 'reason' => $reason, ]); } function aismart_normalize_compare_text($text) { $normalized = trim(wp_strip_all_tags((string) $text)); if (function_exists('mb_strtolower')) { $normalized = mb_strtolower($normalized); } else { $normalized = strtolower($normalized); } $normalized = preg_replace('/\s+/u', ' ', $normalized); return is_string($normalized) ? trim($normalized) : ''; } function aismart_normalize_post_title($title) { $title = trim((string) $title); if ($title === '') { return ''; } // Remove repeated trailing language-code markers e.g. "(TH) (TH)". $title = preg_replace('/(?:\s*\([A-Z]{2,5}(?:-[A-Z]{2,5})?\))+\s*$/u', '', $title); $title = preg_replace('/\s{2,}/u', ' ', (string) $title); return trim((string) $title); } function aismart_text_looks_english($text) { $sample = aismart_normalize_compare_text($text); if ($sample === '') { return false; } $ascii_only = preg_replace('/[^\x00-\x7F]/u', '', $sample); $ascii_ratio = strlen($sample) > 0 ? (strlen($ascii_only) / strlen($sample)) : 0; $hits = preg_match_all('/\b(the|and|for|with|from|that|your|you|how|marketing|plan|build|simple)\b/i', $sample); return $ascii_ratio > 0.85 && is_int($hits) && $hits >= 2; } function aismart_language_script_match($text, $lang_code) { $normalized = aismart_normalize_compare_text($text); if ($normalized === '') { return false; } $lang = strtolower((string) $lang_code); if ($lang === 'th') { return preg_match('/\p{Thai}/u', $normalized) === 1; } if ($lang === 'ja') { return preg_match('/[\p{Hiragana}\p{Katakana}\p{Han}]/u', $normalized) === 1; } if (in_array($lang, ['zh', 'zh-hk', 'zh-hans', 'zh-hant'], true)) { return preg_match('/\p{Han}/u', $normalized) === 1; } return true; } function aismart_detect_cloned_english_bundle($translated_texts) { if (!is_array($translated_texts) || count($translated_texts) < 3) { return false; } $hashes = []; $sample = ''; foreach ($translated_texts as $text) { $normalized = aismart_normalize_compare_text($text); if ($normalized === '') { continue; } $hashes[] = md5($normalized); if ($sample === '') { $sample = $normalized; } } if (count($hashes) < 3) { return false; } if (count(array_unique($hashes)) !== 1) { return false; } return aismart_text_looks_english($sample); } function aismart_is_managed_source_post($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return false; } $generated = get_post_meta($post_id, '_aismart_generated_post', true); if (!empty($generated)) { return true; } if (has_term('online-course', 'post_tag', $post_id) || has_term('online-course', 'category', $post_id)) { return true; } // Some translation sets were created with generated meta on a sibling language post. // Treat this post as managed when any translation sibling carries the generated marker. if (function_exists('pll_get_post_translations')) { $translations = pll_get_post_translations($post_id); if (is_array($translations) && !empty($translations)) { foreach ($translations as $translated_post_id) { $translated_post_id = (int) $translated_post_id; if ($translated_post_id <= 0) { continue; } if (!empty(get_post_meta($translated_post_id, '_aismart_generated_post', true))) { return true; } if (has_term('online-course', 'post_tag', $translated_post_id) || has_term('online-course', 'category', $translated_post_id)) { return true; } } } } return false; } function aismart_is_non_source_translation_post($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return false; } if ((int) get_post_meta($post_id, '_aismart_source_post_id', true) > 0) { return true; } global $wpdb; $selected_provider = aismart_normalize_provider_key(aismart_resolve_selected_provider_key()); $detected = (array) aismart_onboarding_detect_multilang_provider(); $active_primary = aismart_normalize_provider_key($detected['primary'] ?? ''); if ($selected_provider === '' || $selected_provider === 'none') { $selected_provider = $active_primary; } $is_child = false; switch ($selected_provider) { case 'polylang': if (function_exists('pll_get_post_language') && function_exists('pll_default_language')) { $pll_lang = sanitize_key((string) pll_get_post_language($post_id, 'slug')); $pll_default = sanitize_key((string) pll_default_language('slug')); if ($pll_lang !== '' && $pll_default !== '' && strcasecmp($pll_lang, $pll_default) !== 0) { $is_child = true; } } break; case 'wpml': if (has_filter('wpml_element_language_details')) { $lang_info = apply_filters('wpml_element_language_details', null, [ 'element_id' => $post_id, 'element_type' => 'post_post', ]); if (is_array($lang_info) && !empty($lang_info['source_language_code'])) { $is_child = true; } elseif (is_object($lang_info) && !empty($lang_info->source_language_code)) { $is_child = true; } } break; case 'bogo': $locale = sanitize_text_field((string) get_post_meta($post_id, '_locale', true)); if ($locale !== '' && strcasecmp($locale, 'all') !== 0) { $default_locale = function_exists('bogo_get_default_locale') ? sanitize_text_field((string) bogo_get_default_locale()) : ''; if ($default_locale !== '' && strcasecmp($locale, $default_locale) !== 0) { $is_child = true; } elseif ($default_locale === '') { $is_child = true; } } break; } if ($is_child) { return true; } // Checking for legacy DB plugin ghost child posts: static $wpml_table_ok = null; $wpml_table = $wpdb->prefix . 'icl_translations'; if ($wpml_table_ok === null) { $wpml_table_ok = ($wpdb->get_var("SHOW TABLES LIKE '{$wpml_table}'") === $wpml_table); } if ($wpml_table_ok) { $source_code = $wpdb->get_var($wpdb->prepare("SELECT source_language_code FROM {$wpml_table} WHERE element_id = %d AND element_type = %s", $post_id, 'post_post')); if (!empty($source_code)) { return true; } } return false; } function aismart_get_translation_status() { if (!current_user_can('manage_options')) { aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } global $wpdb; $search = isset($_POST['search']) ? sanitize_text_field(wp_unslash($_POST['search'])) : ''; $filter = isset($_POST['filter']) ? sanitize_text_field(wp_unslash($_POST['filter'])) : 'all'; $limit = isset($_POST['limit']) ? absint($_POST['limit']) : 15; $limit = $limit > 0 ? $limit : 15; $requested_provider = isset($_POST['provider']) ? aismart_normalize_provider_key(wp_unslash($_POST['provider'])) : ''; $detected = (array) aismart_onboarding_detect_multilang_provider(); $active_primary_provider = aismart_normalize_provider_key($detected['primary'] ?? ''); $selected_provider = $active_primary_provider; if ($selected_provider === '' || $selected_provider === 'none') { $selected_provider = aismart_normalize_provider_key(aismart_resolve_selected_provider_key()); } $allowed_override_providers = ['polylang', 'translatepress', 'qtranslate', 'wpglobus', 'bogo']; if ($requested_provider !== '' && in_array($requested_provider, $allowed_override_providers, true)) { $selected_provider = $requested_provider; } $provider_matches = static function ($provider_key) use ($selected_provider) { $provider_key = aismart_normalize_provider_key((string) $provider_key); if ($provider_key === '') { return true; } return $selected_provider === '' || $selected_provider === 'none' || $selected_provider === $provider_key; }; if ($provider_matches('polylang') && function_exists('pll_languages_list')) { $codes = pll_languages_list(); $names = pll_languages_list(['fields' => 'name']); $language_map = []; $active_codes = []; if (is_array($codes)) { foreach ($codes as $index => $code) { $active_codes[] = (string) $code; $language_map[(string) $code] = is_array($names) && isset($names[$index]) ? (string) $names[$index] : (string) $code; } } $base_language = function_exists('pll_default_language') ? (string) pll_default_language() : (count($active_codes) ? $active_codes[0] : 'en'); $missing_languages = array_values(array_diff($active_codes, [$base_language])); $candidate_limit = max($limit * 40, 600); $posts = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft'], 'posts_per_page' => $candidate_limit, 's' => $search, 'orderby' => 'date', 'order' => 'DESC', 'lang' => '', ]); if (!$posts) { aismart_send_json_success_clean(['posts' => [], 'languages' => $language_map, 'base_language' => $base_language]); } $output = []; foreach ($posts as $post) { $post_id = (int) $post->ID; $display_title = aismart_qtranslate_extract_segment((string) $post->post_title, $base_language); if ($display_title === '') { $display_title = (string) $post->post_title; } if (!aismart_is_managed_source_post($post_id)) { continue; } if (aismart_is_non_source_translation_post($post_id)) { continue; } $source_is_publish = ((string) $post->post_status === 'publish'); $base_content = trim(wp_strip_all_tags((string) $post->post_content)); $base_content = function_exists('mb_strtolower') ? mb_strtolower($base_content) : strtolower($base_content); $base_hash = $base_content !== '' ? md5($base_content) : ''; $source_lang = function_exists('pll_get_post_language') ? (string) pll_get_post_language($post_id, 'slug') : $base_language; if ($source_lang === '') { $source_lang = $base_language; } $post_missing_languages = array_values(array_diff($active_codes, [$source_lang])); $translations = function_exists('pll_get_post_translations') ? pll_get_post_translations($post_id) : []; $missing = []; $translated_texts = []; $quality_issue = ''; $quality_label = ''; $progress_percent = null; $target_locale_count = count($post_missing_languages); $english_fallback_count = 0; foreach ($post_missing_languages as $lang_code) { $translated_id = isset($translations[$lang_code]) ? (int) $translations[$lang_code] : 0; $translated_post = $translated_id > 0 ? get_post($translated_id) : null; $status_value = $translated_post ? sanitize_key((string) $translated_post->post_status) : ''; $status_ok = $translated_post && !in_array($status_value, ['trash', 'auto-draft'], true); $translated_content = $translated_post ? aismart_normalize_compare_text($translated_post->post_content) : ''; $translated_hash = $translated_content !== '' ? md5($translated_content) : ''; $content_ok = $translated_hash !== ''; $is_translated = $status_ok && $content_ok; if ($is_translated && !aismart_language_script_match($translated_content, $lang_code)) { $is_translated = false; $english_fallback_count++; } if ($status_ok && $content_ok) { $translated_texts[$lang_code] = $translated_content; } if (!$is_translated) { $missing[] = $language_map[$lang_code] ?? $lang_code; } } if ($quality_issue === '' && $target_locale_count > 0 && $english_fallback_count > 0 && $english_fallback_count === $target_locale_count) { $quality_issue = 'cloned_english'; } if ($quality_issue === '' && aismart_detect_cloned_english_bundle($translated_texts)) { $quality_issue = 'cloned_english'; } if ($quality_issue === 'cloned_english') { $quality_label = 'Cloned English from mother'; $progress_percent = 50; if (count($missing) === 0) { foreach ($post_missing_languages as $lang_code) { $missing[] = $language_map[$lang_code] ?? $lang_code; } } } $is_complete = $source_is_publish && count($missing) === 0; if ($filter === 'complete' && !$is_complete) { continue; } if ($filter === 'missing' && $is_complete) { continue; } $row_status = $source_is_publish ? ($is_complete ? 'complete' : 'missing') : 'draft'; $output[] = [ 'id' => $post_id, 'title' => (string) $display_title, 'date' => mysql2date('Y-m-d', $post->post_date), 'missing_languages' => $missing, 'missing_count' => count($missing), 'status' => $row_status, 'post_status' => (string) $post->post_status, 'can_translate' => $source_is_publish ? 1 : 0, 'quality_issue' => $quality_issue, 'quality_label' => $quality_label, 'progress_percent' => $progress_percent, ]; if (count($output) >= $limit) { break; } } aismart_send_json_success_clean([ 'posts' => $output, 'languages' => $language_map, 'base_language' => $base_language, ]); } if ($provider_matches('translatepress') && (function_exists('trp_get_languages') || defined('TRP_PLUGIN_VERSION'))) { $language_map = []; $active_codes = []; $trp_languages = function_exists('trp_get_languages') ? (array) trp_get_languages() : []; if (is_array($trp_languages)) { foreach ($trp_languages as $code => $name) { if (is_int($code)) { $code = (string) $name; } $code = sanitize_text_field((string) $code); if ($code === '') { continue; } $active_codes[] = $code; $language_map[$code] = is_scalar($name) ? (string) $name : $code; } } if (empty($active_codes)) { $trp_settings = get_option('trp_settings', []); $fallback_codes = []; if (is_array($trp_settings) && !empty($trp_settings['translation-languages']) && is_array($trp_settings['translation-languages'])) { $fallback_codes = $trp_settings['translation-languages']; } foreach ($fallback_codes as $code) { $code = sanitize_text_field((string) $code); if ($code === '') { continue; } $active_codes[] = $code; if (!isset($language_map[$code])) { $language_map[$code] = $code; } } } $trp_settings = get_option('trp_settings', []); $base_language = 'en_US'; if (is_array($trp_settings) && !empty($trp_settings['default-language'])) { $base_language = sanitize_text_field((string) $trp_settings['default-language']); } elseif (!empty($active_codes)) { $base_language = (string) $active_codes[0]; } $active_codes = array_values(array_unique(array_filter(array_map('strval', $active_codes)))); if (!in_array($base_language, $active_codes, true) && $base_language !== '') { $active_codes[] = $base_language; if (!isset($language_map[$base_language])) { $language_map[$base_language] = $base_language; } } $base_candidates = []; if ($base_language !== '') { $base_candidates[] = strtolower($base_language); $base_candidates[] = strtolower(str_replace('_', '-', $base_language)); if (strpos($base_language, '_') !== false) { $base_candidates[] = strtolower(substr($base_language, 0, strpos($base_language, '_'))); } elseif (strpos($base_language, '-') !== false) { $base_candidates[] = strtolower(substr($base_language, 0, strpos($base_language, '-'))); } } if (is_array($trp_settings) && !empty($trp_settings['url-slugs']) && is_array($trp_settings['url-slugs']) && !empty($trp_settings['url-slugs'][$base_language])) { $base_candidates[] = strtolower((string) $trp_settings['url-slugs'][$base_language]); } $base_candidates = array_values(array_unique(array_filter($base_candidates))); $missing_languages = array_values(array_diff($active_codes, [$base_language])); $candidate_limit = max($limit * 40, 600); $posts = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft'], 'posts_per_page' => $candidate_limit, 's' => $search, 'orderby' => 'date', 'order' => 'DESC', 'meta_query' => [ 'relation' => 'OR', [ 'key' => '_aismart_source_post_id', 'compare' => 'NOT EXISTS', ], [ 'key' => '_aismart_source_post_id', 'value' => '', 'compare' => '=', ], ], ]); if (!$posts) { aismart_send_json_success_clean(['posts' => [], 'languages' => $language_map, 'base_language' => $base_language]); } $legacy_lang_by_post = []; $post_ids_for_lang = array_values(array_unique(array_map(static function ($p) { return (int) $p->ID; }, $posts))); if (!empty($post_ids_for_lang)) { $placeholders = implode(',', array_fill(0, count($post_ids_for_lang), '%d')); $lang_rows = $wpdb->get_results($wpdb->prepare( "SELECT tr.object_id AS post_id, LOWER(t.slug) AS lang_slug FROM {$wpdb->term_relationships} tr INNER JOIN {$wpdb->term_taxonomy} tt ON tt.term_taxonomy_id = tr.term_taxonomy_id INNER JOIN {$wpdb->terms} t ON t.term_id = tt.term_id WHERE tt.taxonomy = 'language' AND tr.object_id IN ({$placeholders})", $post_ids_for_lang )); if (is_array($lang_rows)) { foreach ($lang_rows as $lang_row) { $rid = isset($lang_row->post_id) ? (int) $lang_row->post_id : 0; $slug = isset($lang_row->lang_slug) ? sanitize_text_field((string) $lang_row->lang_slug) : ''; if ($rid <= 0 || $slug === '') { continue; } if (!isset($legacy_lang_by_post[$rid])) { $legacy_lang_by_post[$rid] = []; } $legacy_lang_by_post[$rid][] = strtolower($slug); } } } $output = []; $seen_titles = []; foreach ($posts as $post) { $post_id = (int) $post->ID; if (!aismart_is_managed_source_post($post_id)) { continue; } if (aismart_is_non_source_translation_post($post_id)) { continue; } $source_post_id = (int) get_post_meta($post_id, '_aismart_source_post_id', true); if ($source_post_id > 0) { continue; } $target_lang_meta = sanitize_text_field((string) get_post_meta($post_id, '_aismart_target_lang', true)); if ($target_lang_meta !== '' && !empty($base_candidates) && !in_array(strtolower($target_lang_meta), $base_candidates, true)) { continue; } $term_langs = isset($legacy_lang_by_post[$post_id]) && is_array($legacy_lang_by_post[$post_id]) ? array_values(array_unique(array_map('strtolower', array_map('strval', $legacy_lang_by_post[$post_id])))) : []; if (!empty($term_langs) && !empty($base_candidates)) { if (count(array_intersect($term_langs, $base_candidates)) === 0) { continue; } } $normalized_title_key = aismart_normalize_compare_text(aismart_normalize_post_title((string) $post->post_title)); if ($normalized_title_key !== '') { if (isset($seen_titles[$normalized_title_key])) { continue; } $seen_titles[$normalized_title_key] = 1; } $queued = (bool) get_post_meta($post_id, '_aismart_translation_queued', true); $missing = []; if (!$queued) { foreach ($post_missing_languages as $lang_code) { $missing[] = $language_map[$lang_code] ?? $lang_code; } } $is_complete = count($missing) === 0; if ($filter === 'complete' && !$is_complete) { continue; } if ($filter === 'missing' && $is_complete) { continue; } $output[] = [ 'id' => $post_id, 'title' => $post->post_title, 'date' => mysql2date('Y-m-d', $post->post_date), 'missing_languages' => $missing, 'missing_count' => count($missing), 'status' => $is_complete ? 'complete' : 'missing', 'quality_issue' => '', 'quality_label' => '', 'progress_percent' => null, ]; if (count($output) >= $limit) { break; } } aismart_send_json_success_clean([ 'posts' => $output, 'languages' => $language_map, 'base_language' => $base_language, ]); } if ( $provider_matches('qtranslate') && ( defined('QTX_VERSION') || function_exists('qtranxf_getSortedLanguages') || function_exists('qtrans_getSortedLanguages') || function_exists('qtrans_getLanguage') ) ) { $language_map = []; $active_codes = []; $enabled = get_option('qtranslate_enabled_languages', []); if (!is_array($enabled)) { $enabled = []; } if (empty($enabled) && !empty($GLOBALS['q_config']) && is_array($GLOBALS['q_config']) && !empty($GLOBALS['q_config']['enabled_languages']) && is_array($GLOBALS['q_config']['enabled_languages'])) { $enabled = (array) $GLOBALS['q_config']['enabled_languages']; } $names = get_option('qtranslate_language_names', []); if (!is_array($names)) { $names = []; } foreach ((array) $enabled as $code_raw) { $code = strtolower(sanitize_text_field((string) $code_raw)); if ($code === '') { continue; } $active_codes[] = $code; $label = ''; if (isset($names[$code]) && is_scalar($names[$code])) { $label = (string) $names[$code]; } elseif (!empty($GLOBALS['q_config']) && is_array($GLOBALS['q_config']) && !empty($GLOBALS['q_config']['language_name'][$code])) { $label = (string) $GLOBALS['q_config']['language_name'][$code]; } if ($label === '') { $label = strtoupper(str_replace('-', '_', $code)); } $language_map[$code] = sanitize_text_field($label); } $active_codes = array_values(array_unique(array_filter(array_map('strval', $active_codes)))); $base_language = strtolower(sanitize_text_field((string) get_option('qtranslate_default_language', ''))); if ($base_language === '' && !empty($active_codes)) { $base_language = (string) $active_codes[0]; } if ($base_language === '') { $base_language = 'en'; } if (!in_array($base_language, $active_codes, true)) { $active_codes[] = $base_language; if (!isset($language_map[$base_language])) { $language_map[$base_language] = strtoupper($base_language); } } $missing_languages = array_values(array_diff($active_codes, [$base_language])); $candidate_limit = max($limit * 40, 600); if ($candidate_limit > 2000) { $candidate_limit = 2000; } $posts = get_posts([ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => $candidate_limit, 's' => $search, 'orderby' => 'date', 'order' => 'DESC', 'meta_query' => [ 'relation' => 'OR', [ 'key' => '_aismart_source_post_id', 'compare' => 'NOT EXISTS', ], [ 'key' => '_aismart_source_post_id', 'value' => '', 'compare' => '=', ], ], ]); if (!$posts) { aismart_send_json_success_clean(['posts' => [], 'languages' => $language_map, 'base_language' => $base_language]); } $output = []; $seen_titles = []; foreach ($posts as $post) { $post_id = (int) $post->ID; $raw_content = (string) $wpdb->get_var($wpdb->prepare( "SELECT post_content FROM {$wpdb->posts} WHERE ID = %d LIMIT 1", $post_id )); $content = $raw_content !== '' ? $raw_content : (string) $post->post_content; $is_managed = aismart_is_managed_source_post($post_id); $has_qtranslate_content = preg_match('/\[:[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\]/u', $content) === 1; if (!$is_managed && !$has_qtranslate_content) { continue; } if (aismart_is_non_source_translation_post($post_id)) { continue; } $source_post_id = (int) get_post_meta($post_id, '_aismart_source_post_id', true); if ($source_post_id > 0) { continue; } $normalized_title_key = aismart_normalize_compare_text(aismart_normalize_post_title((string) $post->post_title)); if ($normalized_title_key !== '') { if (isset($seen_titles[$normalized_title_key])) { continue; } $seen_titles[$normalized_title_key] = 1; } $missing = []; foreach ($post_missing_languages as $lang_code) { $lang_code = strtolower((string) $lang_code); $tag_pattern = '/\[:' . preg_quote($lang_code, '/') . '\](.*?)(?=(\[:[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\]|\[:\]))/su'; if (!preg_match($tag_pattern, $content, $matches)) { $missing[] = $language_map[$lang_code] ?? strtoupper($lang_code); continue; } $segment = isset($matches[1]) ? trim(wp_strip_all_tags((string) $matches[1])) : ''; if ($segment === '') { $missing[] = $language_map[$lang_code] ?? strtoupper($lang_code); } } $is_complete = count($missing) === 0; if ($filter === 'complete' && !$is_complete) { continue; } if ($filter === 'missing' && $is_complete) { continue; } $output[] = [ 'id' => $post_id, 'title' => $post->post_title, 'date' => mysql2date('Y-m-d', $post->post_date), 'missing_languages' => $missing, 'missing_count' => count($missing), 'status' => $is_complete ? 'complete' : 'missing', 'quality_issue' => '', 'quality_label' => '', 'progress_percent' => null, ]; if (count($output) >= $limit) { break; } } aismart_send_json_success_clean([ 'posts' => $output, 'languages' => $language_map, 'base_language' => $base_language, ]); } if ( $provider_matches('wpglobus') && ( class_exists('WPGlobus', false) || defined('WPGLOBUS_VERSION') || aismart_detect_active_provider_key() === 'wpglobus' ) ) { $language_map = []; $active_codes = aismart_get_wpglobus_enabled_languages(); $english_names = get_option('wpglobus_option_en_language_names', []); if (!is_array($english_names)) { $english_names = []; } foreach ((array) $active_codes as $code) { $code = strtolower(sanitize_text_field((string) $code)); if ($code === '') { continue; } $label = isset($english_names[$code]) && is_scalar($english_names[$code]) ? (string) $english_names[$code] : strtoupper(str_replace('-', '_', $code)); $language_map[$code] = sanitize_text_field($label); } $base_language = 'en'; $wpglobus_option = get_option('wpglobus_option', []); if (is_array($wpglobus_option) && isset($wpglobus_option['default_language']) && is_scalar($wpglobus_option['default_language'])) { $base_language = strtolower(sanitize_text_field((string) $wpglobus_option['default_language'])); } elseif (!empty($active_codes)) { $base_language = (string) $active_codes[0]; } if ($base_language === '') { $base_language = 'en'; } if (!in_array($base_language, $active_codes, true)) { $active_codes[] = $base_language; if (!isset($language_map[$base_language])) { $language_map[$base_language] = strtoupper($base_language); } } $missing_languages = array_values(array_diff($active_codes, [$base_language])); $candidate_limit = max($limit * 40, 600); if ($candidate_limit > 2000) { $candidate_limit = 2000; } $posts = get_posts([ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => $candidate_limit, 's' => $search, 'orderby' => 'date', 'order' => 'DESC', 'meta_query' => [ 'relation' => 'OR', [ 'key' => '_aismart_source_post_id', 'compare' => 'NOT EXISTS', ], [ 'key' => '_aismart_source_post_id', 'value' => '', 'compare' => '=', ], ], ]); if (!$posts) { aismart_send_json_success_clean(['posts' => [], 'languages' => $language_map, 'base_language' => $base_language]); } $output = []; foreach ($posts as $post) { $post_id = (int) $post->ID; $display_title = aismart_qtranslate_extract_segment((string) $post->post_title, $base_language); if ($display_title === '') { $display_title = (string) $post->post_title; } if (!aismart_is_managed_source_post($post_id)) { continue; } if (aismart_is_non_source_translation_post($post_id)) { continue; } if (aismart_is_non_source_translation_post($post_id)) { continue; } $source_post_id = (int) get_post_meta($post_id, '_aismart_source_post_id', true); if ($source_post_id > 0) { continue; } $content = (string) get_post_field('post_content', $post_id); $source_segment = aismart_normalize_compare_text(aismart_qtranslate_extract_segment($content, $base_language)); $missing = []; foreach ($post_missing_languages as $lang_code) { $segment = aismart_normalize_compare_text(aismart_qtranslate_extract_segment($content, $lang_code)); if ($segment === '' || ($source_segment !== '' && $segment === $source_segment)) { $missing[] = $language_map[$lang_code] ?? strtoupper($lang_code); } } $source_is_publish = ((string) $post->post_status === 'publish'); $is_complete = $source_is_publish && count($missing) === 0; if ($filter === 'complete' && !$is_complete) { continue; } if ($filter === 'missing' && $is_complete) { continue; } $row_status = $source_is_publish ? ($is_complete ? 'complete' : 'missing') : 'draft'; $output[] = [ 'id' => $post_id, 'title' => (string) $display_title, 'date' => mysql2date('Y-m-d', (string) $post->post_date), 'missing_languages' => $missing, 'missing_count' => count($missing), 'status' => $row_status, 'post_status' => (string) $post->post_status, 'can_translate' => $source_is_publish ? 1 : 0, 'quality_issue' => '', 'quality_label' => '', 'progress_percent' => null, ]; if (count($output) >= $limit) { break; } } aismart_send_json_success_clean([ 'posts' => $output, 'languages' => $language_map, 'base_language' => $base_language, ]); } if ( $provider_matches('bogo') && ( function_exists('bogo_available_locales') || class_exists('Bogo', false) || aismart_detect_active_provider_key() === 'bogo' ) ) { $language_map = []; $active_locales = function_exists('bogo_available_locales') ? (array) bogo_available_locales(['exclude_enus_if_inactive' => true]) : []; foreach ($active_locales as $locale) { $locale = sanitize_text_field((string) $locale); if ($locale === '') { continue; } $code = aismart_bogo_locale_to_lang_code($locale); if ($code === '') { continue; } $name = function_exists('bogo_get_language') ? (string) bogo_get_language($locale) : strtoupper($code); if ($name === '') { $name = strtoupper($code); } $language_map[$code] = sanitize_text_field($name); } $base_locale = function_exists('bogo_get_default_locale') ? (string) bogo_get_default_locale() : 'en_US'; $base_language = aismart_bogo_locale_to_lang_code($base_locale); if ($base_language === '') { $base_language = 'en'; } $active_codes = array_values(array_unique(array_filter(array_map('strval', array_keys($language_map))))); if (!in_array($base_language, $active_codes, true)) { $active_codes[] = $base_language; if (!isset($language_map[$base_language])) { $language_map[$base_language] = strtoupper($base_language); } } $missing_languages = array_values(array_diff($active_codes, [$base_language])); $candidate_limit = max($limit * 40, 600); if ($candidate_limit > 2000) { $candidate_limit = 2000; } $posts = get_posts([ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => $candidate_limit, 's' => $search, 'orderby' => 'date', 'order' => 'DESC', 'meta_query' => [ 'relation' => 'OR', [ 'key' => '_aismart_source_post_id', 'compare' => 'NOT EXISTS', ], [ 'key' => '_aismart_source_post_id', 'value' => '', 'compare' => '=', ], ], ]); if (!$posts) { aismart_send_json_success_clean(['posts' => [], 'languages' => $language_map, 'base_language' => $base_language]); } $output = []; foreach ($posts as $post) { $post_id = (int) $post->ID; if (!aismart_is_managed_source_post($post_id)) { continue; } if (aismart_is_non_source_translation_post($post_id)) { continue; } $source_post_id = (int) get_post_meta($post_id, '_aismart_source_post_id', true); if ($source_post_id > 0) { continue; } // Legacy Bogo rows may have linked children without locale metadata/taxonomy. // Keep a fallback pool so panel status still reflects completed translated children. $fallback_child_pool = []; $fallback_children = get_posts([ 'post_type' => 'post', 'post_status' => 'publish', 'posts_per_page' => -1, 'meta_key' => '_aismart_source_post_id', 'meta_value' => (string) $post_id, 'orderby' => 'ID', 'order' => 'ASC', ]); if (is_array($fallback_children) && !empty($fallback_children)) { foreach ($fallback_children as $fallback_child) { if (!$fallback_child instanceof WP_Post) { continue; } $fallback_content = aismart_normalize_compare_text((string) $fallback_child->post_content); if ($fallback_content !== '') { $fallback_child_pool[] = (int) $fallback_child->ID; } } } $missing = []; foreach ($post_missing_languages as $lang_code) { $target_locale = ''; foreach ($active_locales as $locale) { $locale = sanitize_text_field((string) $locale); if (aismart_bogo_locale_to_lang_code($locale) === $lang_code) { $target_locale = $locale; break; } } if ($target_locale === '') { $missing[] = $language_map[$lang_code] ?? strtoupper($lang_code); continue; } $translated_post = function_exists('bogo_get_post_translation') ? bogo_get_post_translation($post_id, $target_locale) : false; $status_value = $translated_post instanceof WP_Post ? sanitize_key((string) $translated_post->post_status) : ''; $status_ok = $translated_post instanceof WP_Post && !in_array($status_value, ['trash', 'auto-draft'], true); $content = $translated_post instanceof WP_Post ? aismart_normalize_compare_text((string) $translated_post->post_content) : ''; if (!$status_ok || $content === '') { if (!empty($fallback_child_pool)) { array_shift($fallback_child_pool); continue; } $missing[] = $language_map[$lang_code] ?? strtoupper($lang_code); } } $source_is_publish = ((string) $post->post_status === 'publish'); $is_complete = $source_is_publish && count($missing) === 0; if ($filter === 'complete' && !$is_complete) { continue; } if ($filter === 'missing' && $is_complete) { continue; } $row_status = $source_is_publish ? ($is_complete ? 'complete' : 'missing') : 'draft'; $output[] = [ 'id' => $post_id, 'title' => (string) $post->post_title, 'date' => mysql2date('Y-m-d', (string) $post->post_date), 'missing_languages' => $missing, 'missing_count' => count($missing), 'status' => $row_status, 'post_status' => (string) $post->post_status, 'can_translate' => $source_is_publish ? 1 : 0, 'quality_issue' => '', 'quality_label' => '', 'progress_percent' => null, ]; if (count($output) >= $limit) { break; } } aismart_send_json_success_clean([ 'posts' => $output, 'languages' => $language_map, 'base_language' => $base_language, ]); } if ( $provider_matches('falang') && ( function_exists('falang_languages_list') || class_exists('Falang', false) || defined('FALANG_VERSION') || aismart_detect_active_provider_key() === 'falang' ) ) { $language_pairs = function_exists('aismart_get_falang_language_pairs') ? (array) aismart_get_falang_language_pairs() : []; $language_map = []; $active_pairs = []; foreach ($language_pairs as $pair) { if (!is_array($pair)) { continue; } $locale = sanitize_text_field((string) ($pair['locale'] ?? '')); $slug = sanitize_title((string) ($pair['slug'] ?? '')); if ($locale === '' && $slug === '') { continue; } if ($locale === '') { $locale = $slug; } if ($slug === '') { $slug = sanitize_title($locale); } $label = strtoupper(str_replace('_', '-', $locale)); if (taxonomy_exists('language')) { $term = get_term_by('slug', $slug, 'language'); if ($term instanceof WP_Term && $term->name !== '') { $label = sanitize_text_field((string) $term->name); } } $active_pairs[] = [ 'locale' => $locale, 'slug' => $slug, ]; $language_map[$slug !== '' ? $slug : strtolower($locale)] = $label; } if (empty($active_pairs) && taxonomy_exists('language')) { $terms = get_terms([ 'taxonomy' => 'language', 'hide_empty' => false, ]); if (!is_wp_error($terms) && is_array($terms)) { foreach ($terms as $term) { if (!($term instanceof WP_Term)) { continue; } $slug = sanitize_title((string) $term->slug); if ($slug === '') { continue; } $active_pairs[] = [ 'locale' => $slug, 'slug' => $slug, ]; $language_map[$slug] = sanitize_text_field((string) $term->name); } } } $base_locale = function_exists('falang_default_language') ? sanitize_text_field((string) falang_default_language('locale')) : ''; if ($base_locale === '' && !empty($active_pairs)) { $base_locale = sanitize_text_field((string) ($active_pairs[0]['locale'] ?? '')); } if ($base_locale === '') { $base_locale = 'en_US'; } $base_slug = sanitize_title($base_locale); if ($base_slug === '') { $base_slug = strtolower($base_locale); } $target_pairs = []; foreach ($active_pairs as $pair) { $locale = sanitize_text_field((string) ($pair['locale'] ?? '')); $slug = sanitize_title((string) ($pair['slug'] ?? '')); if ($locale === '' && $slug === '') { continue; } if ($slug === '') { $slug = sanitize_title($locale); } if (strcasecmp($locale, $base_locale) === 0 || $slug === $base_slug) { continue; } $target_pairs[] = [ 'locale' => $locale, 'slug' => $slug, ]; if (!isset($language_map[$slug])) { $language_map[$slug] = strtoupper($slug); } } if (!isset($language_map[$base_slug])) { $language_map[$base_slug] = strtoupper($base_slug); } $base_candidates = array_values(array_unique(array_filter([ strtolower($base_slug), strtolower(sanitize_title($base_locale)), strtolower(substr((string) $base_slug, 0, 2)), strtolower(substr((string) $base_locale, 0, 2)), ]))); $candidate_limit = max($limit * 40, 600); $posts = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft'], 'posts_per_page' => $candidate_limit, 's' => $search, 'orderby' => 'date', 'order' => 'DESC', 'meta_query' => [ 'relation' => 'OR', [ 'key' => '_aismart_source_post_id', 'compare' => 'NOT EXISTS', ], [ 'key' => '_aismart_source_post_id', 'value' => '', 'compare' => '=', ], ], ]); if (!$posts) { aismart_send_json_success_clean(['posts' => [], 'languages' => $language_map, 'base_language' => $base_slug]); } $output = []; foreach ($posts as $post) { $post_id = (int) $post->ID; if (!aismart_is_managed_source_post($post_id)) { continue; } $source_post_id = (int) get_post_meta($post_id, '_aismart_source_post_id', true); if ($source_post_id > 0) { continue; } $post_lang_slugs = []; if (taxonomy_exists('language')) { $raw_lang_slugs = wp_get_post_terms($post_id, 'language', ['fields' => 'slugs']); if (!is_wp_error($raw_lang_slugs) && is_array($raw_lang_slugs)) { foreach ($raw_lang_slugs as $lang_slug_raw) { $lang_slug = strtolower(sanitize_title((string) $lang_slug_raw)); if ($lang_slug !== '') { $post_lang_slugs[] = $lang_slug; } } $post_lang_slugs = array_values(array_unique($post_lang_slugs)); } } if (!empty($post_lang_slugs) && count(array_intersect($post_lang_slugs, $base_candidates)) === 0) { continue; } $source_is_publish = ((string) $post->post_status === 'publish'); $missing = []; foreach ($target_pairs as $pair) { $target_locale = sanitize_text_field((string) ($pair['locale'] ?? '')); $target_slug = sanitize_title((string) ($pair['slug'] ?? '')); if ($target_locale === '' && $target_slug === '') { continue; } $prefix = function_exists('aismart_get_falang_meta_prefix') ? (string) aismart_get_falang_meta_prefix($target_locale !== '' ? $target_locale : $target_slug) : ('_' . ($target_locale !== '' ? $target_locale : $target_slug) . '_'); $translated_content = aismart_normalize_compare_text((string) get_post_meta($post_id, $prefix . 'post_content', true)); $translated_title = aismart_normalize_compare_text((string) get_post_meta($post_id, $prefix . 'post_title', true)); $translated_published = (string) get_post_meta($post_id, $prefix . 'published', true); $has_translation = ($translated_content !== '' || $translated_title !== ''); $is_published_translation = ($translated_published === '' || $translated_published === '1'); if (!($has_translation && $is_published_translation)) { $missing[] = $language_map[$target_slug] ?? strtoupper($target_slug !== '' ? $target_slug : $target_locale); } } $is_complete = $source_is_publish && count($missing) === 0; if ($filter === 'complete' && !$is_complete) { continue; } if ($filter === 'missing' && $is_complete) { continue; } $row_status = $source_is_publish ? ($is_complete ? 'complete' : 'missing') : 'draft'; $output[] = [ 'id' => $post_id, 'title' => (string) $post->post_title, 'date' => mysql2date('Y-m-d', (string) $post->post_date), 'missing_languages' => $missing, 'missing_count' => count($missing), 'status' => $row_status, 'post_status' => (string) $post->post_status, 'can_translate' => $source_is_publish ? 1 : 0, 'quality_issue' => '', 'quality_label' => '', 'progress_percent' => null, ]; if (count($output) >= $limit) { break; } } aismart_send_json_success_clean([ 'posts' => $output, 'languages' => $language_map, 'base_language' => $base_slug, ]); } if (!$provider_matches('wpml')) { aismart_send_json_success_clean(['posts' => [], 'languages' => []]); } $translations_table = $wpdb->prefix . 'icl_translations'; $languages_table = $wpdb->prefix . 'icl_languages'; $translation_status_table = $wpdb->prefix . 'icl_translation_status'; $translations_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $translations_table)); $languages_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $languages_table)); $translation_status_exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $translation_status_table)); $wpml_strict_job_state = (bool) apply_filters('aismart_wpml_strict_translation_status', true); if (!$translation_status_exists) { $wpml_strict_job_state = false; } if (!$translations_exists || !$languages_exists) { aismart_send_json_success_clean(['posts' => [], 'languages' => []]); } $active_languages = $wpdb->get_results("SELECT code, english_name FROM {$languages_table} WHERE active = 1"); $language_map = []; $active_codes = []; foreach ($active_languages as $lang) { $language_map[$lang->code] = $lang->english_name ?: $lang->code; $active_codes[] = $lang->code; } $base_language = in_array('en', $active_codes, true) ? 'en' : (count($active_codes) ? $active_codes[0] : 'en'); $missing_languages = array_values(array_diff($active_codes, [$base_language])); $posts_table = $wpdb->posts; $limit = max(1, (int) $limit); if ($search !== '') { $search_like = '%' . $wpdb->esc_like($search) . '%'; $posts = $wpdb->get_results($wpdb->prepare( "SELECT p.ID, p.post_title, p.post_date, p.post_content, t.trid FROM {$posts_table} p LEFT JOIN {$translations_table} t ON t.element_id = p.ID AND t.element_type = 'post_post' WHERE p.post_status = 'publish' AND p.post_type = 'post' AND p.post_title LIKE %s AND (t.language_code = %s OR t.language_code IS NULL) ORDER BY p.post_date DESC LIMIT %d", $search_like, $base_language, $limit )); } else { $posts = $wpdb->get_results($wpdb->prepare( "SELECT p.ID, p.post_title, p.post_date, p.post_content, t.trid FROM {$posts_table} p LEFT JOIN {$translations_table} t ON t.element_id = p.ID AND t.element_type = 'post_post' WHERE p.post_status = 'publish' AND p.post_type = 'post' AND (t.language_code = %s OR t.language_code IS NULL) ORDER BY p.post_date DESC LIMIT %d", $base_language, $limit )); } if (!$posts) { aismart_send_json_success_clean(['posts' => [], 'languages' => $language_map]); } $trids = array_values(array_unique(array_filter(array_map(fn($row) => (int) $row->trid, $posts)))); $base_hash_by_trid = []; $base_hash_by_id = []; foreach ($posts as $post) { $normalized = trim(wp_strip_all_tags((string) $post->post_content)); if (function_exists('mb_strtolower')) { $normalized = mb_strtolower($normalized); } else { $normalized = strtolower($normalized); } $hash = $normalized !== '' ? md5($normalized) : ''; $post_id = (int) $post->ID; $base_hash_by_id[$post_id] = $hash; if (!empty($post->trid)) { $base_hash_by_trid[(int) $post->trid] = $hash; } } $translation_map = []; if ($trids) { $placeholders = implode(',', array_fill(0, count($trids), '%d')); if ($translation_status_exists) { $translation_rows = $wpdb->get_results($wpdb->prepare( "SELECT t.trid, t.language_code, t.Translated, p.post_status, p.post_content, ts.status AS wpml_status, ts.needs_update AS wpml_needs_update FROM {$translations_table} t LEFT JOIN {$posts_table} p ON p.ID = t.element_id LEFT JOIN {$translation_status_table} ts ON ts.translation_id = t.translation_id WHERE t.element_type = 'post_post' AND t.trid IN ({$placeholders})", $trids )); } else { $translation_rows = $wpdb->get_results($wpdb->prepare( "SELECT t.trid, t.language_code, t.Translated, p.post_status, p.post_content, NULL AS wpml_status, NULL AS wpml_needs_update FROM {$translations_table} t LEFT JOIN {$posts_table} p ON p.ID = t.element_id WHERE t.element_type = 'post_post' AND t.trid IN ({$placeholders})", $trids )); } foreach ($translation_rows as $row) { $content = trim(wp_strip_all_tags((string) $row->post_content)); if (function_exists('mb_strtolower')) { $content = mb_strtolower($content); } else { $content = strtolower($content); } $content_hash = $content !== '' ? md5($content) : ''; $translation_map[(int) $row->trid][$row->language_code] = [ 'translated' => (int) $row->Translated, 'status' => $row->post_status, 'content_hash' => $content_hash, 'wpml_status' => isset($row->wpml_status) && $row->wpml_status !== null ? (int) $row->wpml_status : null, 'wpml_needs_update' => isset($row->wpml_needs_update) && $row->wpml_needs_update !== null ? (int) $row->wpml_needs_update : null, ]; } } $output = []; foreach ($posts as $post) { $post_id = (int) $post->ID; if (!aismart_is_managed_source_post($post_id)) { continue; } $trid = (int) $post->trid; $translated = $trid ? ($translation_map[$trid] ?? []) : []; $base_hash = $trid ? ($base_hash_by_trid[$trid] ?? '') : ($base_hash_by_id[$post_id] ?? ''); $missing = []; $translated_texts = []; $quality_issue = ''; $quality_label = ''; $progress_percent = null; foreach ($missing_languages as $lang_code) { $lang_state = $translated[$lang_code] ?? null; $translated_flag = is_array($lang_state) && (int) ($lang_state['translated'] ?? 0) === 1; $status_value = is_array($lang_state) ? sanitize_key((string) ($lang_state['status'] ?? '')) : ''; $status_ok = in_array($status_value, ['publish', 'future', 'draft', 'pending', 'private'], true); $content_ok = is_array($lang_state) && !empty($lang_state['content_hash']); $diff_ok = $content_ok && ($base_hash === '' || $lang_state['content_hash'] !== $base_hash); $is_translated = $content_ok && ($translated_flag || ($status_ok && $diff_ok)); if ($wpml_strict_job_state) { $wpml_status = is_array($lang_state) ? ($lang_state['wpml_status'] ?? null) : null; $wpml_needs_update = is_array($lang_state) ? ($lang_state['wpml_needs_update'] ?? null) : null; $job_finalized = in_array((int) $wpml_status, [9, 10], true) && (int) $wpml_needs_update === 0; if (!$job_finalized) { $is_translated = false; } } if ($is_translated) { $translated_texts[$lang_code] = (string) $lang_state['content_hash']; } if (!$is_translated) { $missing[] = $language_map[$lang_code] ?? $lang_code; } } if (count($translated_texts) >= 3 && count(array_unique(array_values($translated_texts))) === 1) { $quality_issue = 'cloned_english'; $quality_label = 'Cloned English from mother'; $progress_percent = 50; if (count($missing) === 0) { foreach ($missing_languages as $lang_code) { $missing[] = $language_map[$lang_code] ?? $lang_code; } } } $is_complete = count($missing) === 0; if ($filter === 'complete' && !$is_complete) { continue; } if ($filter === 'missing' && $is_complete) { continue; } $output[] = [ 'id' => $post_id, 'title' => $post->post_title, 'date' => mysql2date('Y-m-d', $post->post_date), 'missing_languages' => $missing, 'missing_count' => count($missing), 'status' => $is_complete ? 'complete' : 'missing', 'post_status' => 'publish', 'can_translate' => 1, 'quality_issue' => $quality_issue, 'quality_label' => $quality_label, 'progress_percent' => $progress_percent, ]; } foreach ($output as &$item) { $item['is_queued'] = (int) get_post_meta($item['id'], '_aismart_translation_queued', true) === 1; } unset($item); aismart_send_json_success_clean([ 'posts' => $output, 'languages' => $language_map, 'base_language' => $base_language, ]); } // AJAX: Trigger translation for a post (background) add_action('wp_ajax_aismart_translate_now', 'aismart_translate_now'); function aismart_translate_now() { if (!current_user_can('manage_options')) { aismart_send_json_error_clean(['message' => 'Unauthorized'], 403); } if (!aismart_require_positive_credit_or_json_error('Translation')) { return; } $post_id = isset($_POST['post_id']) ? absint($_POST['post_id']) : 0; if ($post_id <= 0) { aismart_send_json_error_clean(['message' => 'Invalid post ID'], 400); } error_log('AISmart Translate Now: request received for post_id=' . $post_id); aismart_queue_log('Translate now requested post_id=' . $post_id); $route = aismart_detect_translation_command(); $engine = isset($route['engine']) ? sanitize_key((string) $route['engine']) : 'unknown'; if (in_array($engine, ['bogo', 'wpml', 'polylang'], true)) { $args = [(int) $post_id, 'bogo']; if ($engine !== 'bogo') { $args = [(int) $post_id, $engine]; } if (!wp_next_scheduled('aismart_process_translation_event', $args)) { wp_schedule_single_event(time() + 1, 'aismart_process_translation_event', $args); } if (function_exists('spawn_cron')) { @spawn_cron(time()); } aismart_defer_background_translation((int) $post_id, $engine); delete_post_meta($post_id, '_aismart_translation_last_error'); update_post_meta($post_id, '_aismart_translation_queued', 1); update_post_meta($post_id, '_aismart_translation_queue_last_at', gmdate('c')); aismart_queue_log('Translate now queued in background post_id=' . $post_id . ' engine=' . $engine); aismart_send_json_success_clean([ 'message' => 'Translation queued in background', 'post_id' => $post_id, 'translation_queued' => 1, 'translation_queued_background' => 1, ]); } $queue_runtime_noise = ''; $buffer_started = false; try { if (!headers_sent()) { ob_start(); $buffer_started = true; } $queued = aismart_queue_translation_for_post($post_id); } catch (Throwable $e) { $queued = false; update_post_meta($post_id, '_aismart_translation_last_error', sanitize_text_field($e->getMessage())); error_log('AISmart Translate Now Exception: post_id=' . (int) $post_id . ' message=' . $e->getMessage()); } finally { if ($buffer_started) { $queue_runtime_noise = trim((string) ob_get_clean()); } } if ($queue_runtime_noise !== '') { $noise_preview = substr(preg_replace('/\s+/u', ' ', $queue_runtime_noise), 0, 500); aismart_queue_log('Translate now runtime output post_id=' . $post_id . ' output=' . $noise_preview); } if (!$queued) { delete_post_meta($post_id, '_aismart_translation_queued'); delete_post_meta($post_id, '_aismart_translation_queue_last_at'); aismart_queue_log('Translate now queue failed post_id=' . $post_id); $message = 'Translation queue failed (check error log)'; $last_error = (string) get_post_meta($post_id, '_aismart_translation_last_error', true); if ($last_error !== '') { $message = $last_error; } if ($engine === 'translatepress') { $message = 'Laravel Backend Error: ' . $message; } aismart_send_json_error_clean(['message' => $message], 500); } update_post_meta($post_id, '_aismart_translation_queued', 1); update_post_meta($post_id, '_aismart_translation_queue_last_at', gmdate('c')); aismart_queue_log('Translate now queued post_id=' . $post_id . ' meta_marked=1'); aismart_send_json_success_clean([ 'message' => 'Translation queued', 'post_id' => $post_id, 'translation_queued' => 1, ]); } // Auto-translate on publish when enabled add_action('transition_post_status', 'aismart_auto_translate_on_publish', 10, 3); function aismart_auto_translate_on_publish($new_status, $old_status, $post) { if ($new_status !== 'publish' || $old_status === 'publish') { return; } if (!$post || $post->post_type !== 'post') { return; } $forced_on_publish = (bool) get_post_meta($post->ID, '_aismart_force_translate_on_publish', true); if (!get_option('aismart_auto_translate', 0) && !$forced_on_publish) { return; } if (wp_is_post_revision($post->ID)) { return; } if (!aismart_is_managed_source_post((int) $post->ID)) { return; } $already = get_post_meta($post->ID, '_aismart_translation_queued', true); if ($already) { return; } // Safety: Do not auto-translate posts that are themselves translations. // For Bogo, a translated child can be published before _aismart_source_post_id // is written, so also use locale/target-lang heuristics. $is_translation = get_post_meta($post->ID, '_aismart_source_post_id', true); if ($is_translation || aismart_is_non_source_translation_post((int) $post->ID)) { return; } $queued = aismart_queue_translation_for_post($post->ID); if ($queued) { update_post_meta($post->ID, '_aismart_translation_queued', 1); if ($forced_on_publish) { delete_post_meta($post->ID, '_aismart_force_translate_on_publish'); } } } function aismart_detect_translation_command() { $active_plugins = (array) get_option('active_plugins', []); if (is_multisite()) { $sitewide = array_keys((array) get_site_option('active_sitewide_plugins', [])); $active_plugins = array_values(array_unique(array_merge($active_plugins, $sitewide))); } $has_plugin = static function ($slug) use ($active_plugins) { return in_array($slug, $active_plugins, true); }; $provider_available = static function ($provider) use ($has_plugin) { $provider = sanitize_key((string) $provider); if ($provider === '') { return false; } if ($provider === 'wpml') { return defined('ICL_SITEPRESS_VERSION') || $has_plugin('sitepress-multilingual-cms/sitepress.php'); } if ($provider === 'polylang') { return function_exists('pll_languages_list') || $has_plugin('polylang/polylang.php'); } if ($provider === 'translatepress') { return defined('TRP_PLUGIN_VERSION') || $has_plugin('translatepress-multilingual/index.php'); } if ($provider === 'multilingualpress') { return $has_plugin('multilingual-press/multilingual-press.php') || $has_plugin('multilingual-press/multilingualpress.php') || $has_plugin('multilingualpress/multilingualpress.php'); } if ($provider === 'falang') { return $has_plugin('falang/falang.php'); } if ($provider === 'qtranslate') { return $has_plugin('qtranslate-x/qtranslate.php') || $has_plugin('qtranslate-xt/qtranslate.php') || $has_plugin('qtranslate/qtranslate.php'); } if ($provider === 'sublanguage') { return $has_plugin('sublanguage/sublanguage.php'); } if ($provider === 'wpglobus') { return $has_plugin('wpglobus/wpglobus.php'); } if ($provider === 'bogo') { return $has_plugin('bogo/bogo.php'); } return false; }; // Respect explicit provider selected in onboarding when that provider is available. // This prevents fallback-order mismatches when multiple multilingual plugins coexist. $selected_provider = aismart_normalize_provider_key(aismart_resolve_selected_provider_key()); if ($selected_provider !== '' && $provider_available($selected_provider)) { $selected_engine = $selected_provider === 'qtranslate-xt' ? 'qtranslate' : $selected_provider; $selected_command = $selected_engine . ':translate-universal'; if ($selected_engine === 'translatepress') { $selected_command = 'trp:translate'; } return ['engine' => $selected_engine, 'command' => $selected_command]; } if (function_exists('pll_languages_list') || $has_plugin('polylang/polylang.php')) { return ['engine' => 'polylang', 'command' => 'polylang:translate-universal']; } if (defined('TRP_PLUGIN_VERSION') || $has_plugin('translatepress-multilingual/index.php')) { return ['engine' => 'translatepress', 'command' => 'trp:translate']; } if ($has_plugin('multilingual-press/multilingual-press.php') || $has_plugin('multilingual-press/multilingualpress.php') || $has_plugin('multilingualpress/multilingualpress.php')) { return ['engine' => 'multilingualpress', 'command' => 'multilingualpress:translate-universal']; } if ($has_plugin('falang/falang.php')) { return ['engine' => 'falang', 'command' => 'falang:translate-universal']; } if ($has_plugin('qtranslate-x/qtranslate.php') || $has_plugin('qtranslate-xt/qtranslate.php') || $has_plugin('qtranslate/qtranslate.php')) { return ['engine' => 'qtranslate', 'command' => 'qtranslate:translate-universal']; } if ($has_plugin('sublanguage/sublanguage.php')) { return ['engine' => 'sublanguage', 'command' => 'sublanguage:translate-universal']; } if ($has_plugin('wpglobus/wpglobus.php')) { return ['engine' => 'wpglobus', 'command' => 'wpglobus:translate-universal']; } if ($has_plugin('bogo/bogo.php')) { return ['engine' => 'bogo', 'command' => 'bogo:translate-universal']; } if (defined('ICL_SITEPRESS_VERSION') || $has_plugin('sitepress-multilingual-cms/sitepress.php')) { return ['engine' => 'wpml', 'command' => 'wpml:translate-universal']; } $state = aismart_onboarding_state_get(); $saved_provider = isset($state['multilang_provider']) ? sanitize_key((string) $state['multilang_provider']) : ''; $provider_to_engine = [ 'wpml' => 'wpml', 'polylang' => 'polylang', 'translatepress' => 'translatepress', 'multilingualpress' => 'multilingualpress', 'falang' => 'falang', 'qtranslate' => 'qtranslate', 'qtranslate-xt' => 'qtranslate', 'sublanguage' => 'sublanguage', 'wpglobus' => 'wpglobus', 'bogo' => 'bogo', ]; if ($saved_provider !== '' && isset($provider_to_engine[$saved_provider])) { $engine = (string) $provider_to_engine[$saved_provider]; return ['engine' => $engine, 'command' => $engine . ':translate-universal']; } return ['engine' => 'unknown', 'command' => '']; } function aismart_engine_to_universal_command($engine) { $engine = sanitize_key((string) $engine); $allowed = [ 'wpml', 'polylang', 'translatepress', 'multilingualpress', 'falang', 'qtranslate', 'sublanguage', 'wpglobus', 'bogo', ]; if ($engine === 'qtranslate-xt') { $engine = 'qtranslate'; } if (!in_array($engine, $allowed, true)) { return ''; } return $engine . ':translate-universal'; } function aismart_defer_background_translation($post_id, $engine = 'unknown') { static $registered = false; static $jobs = []; $post_id = (int) $post_id; $engine = sanitize_key((string) $engine); if ($post_id <= 0 || $engine === '') { return; } $jobs[$engine . ':' . $post_id] = [$post_id, $engine]; if ($registered) { return; } $registered = true; register_shutdown_function(static function () use (&$jobs) { if (empty($jobs)) { return; } if (function_exists('ignore_user_abort')) { @ignore_user_abort(true); } // Release the client response first when supported, then continue translation work. if (function_exists('fastcgi_finish_request')) { @fastcgi_finish_request(); } if (function_exists('set_time_limit')) { @set_time_limit(120); } foreach ($jobs as $job) { $pid = isset($job[0]) ? (int) $job[0] : 0; $eng = isset($job[1]) ? sanitize_key((string) $job[1]) : ''; if ($pid <= 0 || $eng === '') { continue; } aismart_queue_log('Deferred translation worker start post_id=' . $pid . ' engine=' . $eng); aismart_process_translation_event_handler($pid, $eng); } }); } add_action('aismart_process_translation_event', 'aismart_process_translation_event_handler', 10, 2); function aismart_process_translation_event_handler($post_id, $engine = 'unknown') { $post_id = (int) $post_id; $engine = sanitize_key((string) $engine); if ($post_id <= 0) { return; } $lock_key = 'aismart_translation_worker_lock_' . $post_id; if (function_exists('get_transient') && get_transient($lock_key)) { aismart_queue_log('Background translation worker locked/skip post_id=' . $post_id . ' engine=' . $engine); return; } if (function_exists('set_transient')) { set_transient($lock_key, 1, 90); } try { aismart_queue_log('Background translation worker start post_id=' . $post_id . ' engine=' . $engine); if ($engine === 'polylang') { $ok = aismart_translate_polylang_native($post_id); aismart_queue_log('Background translation worker result post_id=' . $post_id . ' engine=' . $engine . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache($post_id); aismart_schedule_featured_image_sync($post_id); } return; } if ($engine === 'bogo') { $ok = aismart_translate_bogo_native($post_id); aismart_queue_log('Background translation worker result post_id=' . $post_id . ' engine=' . $engine . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache($post_id); aismart_schedule_featured_image_sync($post_id); } return; } if ($engine === 'wpglobus') { $ok = aismart_translate_wpglobus_native($post_id); aismart_queue_log('Background translation worker result post_id=' . $post_id . ' engine=' . $engine . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache($post_id); aismart_schedule_featured_image_sync($post_id); } return; } if ($engine === 'wpml') { $ok = aismart_translate_wpml_native($post_id); aismart_queue_log('Background translation worker result post_id=' . $post_id . ' engine=' . $engine . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache($post_id); aismart_schedule_featured_image_sync($post_id); } return; } aismart_queue_log('Background translation worker skip unsupported engine=' . $engine . ' post_id=' . $post_id); } finally { if (function_exists('delete_transient')) { delete_transient($lock_key); } } } add_action('aismart_retry_translatepress_event', 'aismart_retry_translatepress_event_handler', 10, 1); function aismart_retry_translatepress_event_handler($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return; } $route = aismart_detect_translation_command(); $engine = isset($route['engine']) ? sanitize_key((string) $route['engine']) : 'unknown'; $command = isset($route['command']) ? (string) $route['command'] : ''; if ($engine !== 'translatepress' || $command === '') { aismart_queue_log('TranslatePress retry skipped post_id=' . $post_id . ' engine=' . $engine . ' command=' . $command); return; } $php_binary = PHP_BINARY; if ( !$php_binary || !is_file($php_binary) || !is_executable($php_binary) || stripos((string) $php_binary, 'php-fpm') !== false ) { $php_binary = '/usr/bin/php'; } $artisan = aismart_get_artisan_path(); if (!file_exists($artisan)) { aismart_queue_log('TranslatePress retry skipped: artisan missing post_id=' . $post_id); return; } $cmd = sprintf( '%s %s %s auto blog 0 site2 --id=%d 2>&1', escapeshellcmd($php_binary), escapeshellarg($artisan), escapeshellarg($command), (int) $post_id ); $output = []; $result = 1; $ran = aismart_shell_run_capture($cmd, $output, $result); $preview = substr(preg_replace('/\s+/u', ' ', implode("\n", (array) $output)), 0, 700); aismart_queue_log('TranslatePress retry run post_id=' . $post_id . ' ran=' . ($ran ? '1' : '0') . ' result=' . (int) $result . ' cmd=' . $cmd . ' output=' . $preview); if ($ran && (int) $result === 0) { aismart_clear_translation_runtime_cache($post_id); aismart_schedule_featured_image_sync($post_id); } } function aismart_schedule_translatepress_retry($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return; } $args = [$post_id]; if (!wp_next_scheduled('aismart_retry_translatepress_event', $args)) { wp_schedule_single_event(time() + 25, 'aismart_retry_translatepress_event', $args); aismart_queue_log('Scheduled TranslatePress retry post_id=' . $post_id . ' delay=25s'); } if (function_exists('spawn_cron')) { @spawn_cron(time()); } } add_action('aismart_sync_featured_image_event', 'aismart_sync_featured_image_event_handler', 10, 2); function aismart_sync_featured_image_event_handler($post_id, $attempt = 0) { $post_id = (int) $post_id; if ($post_id <= 0) { return; } aismart_sync_featured_image_to_translations($post_id); } function aismart_find_falang_translation_targets($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return []; } if (!function_exists('falang_languages_list') && !class_exists('Falang', false) && !taxonomy_exists('language')) { return []; } global $wpdb; $source_post = get_post($post_id); if (!$source_post || $source_post->post_type !== 'post') { return []; } $targets = []; $meta_children = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft', 'pending', 'future', 'private'], 'numberposts' => -1, 'fields' => 'ids', 'meta_query' => [ [ 'key' => '_aismart_source_post_id', 'value' => $post_id, 'compare' => '=', 'type' => 'NUMERIC', ], ], ]); if (is_array($meta_children)) { foreach ($meta_children as $child_id) { $child_id = (int) $child_id; if ($child_id > 0 && $child_id !== $post_id) { $targets[] = $child_id; } } } $source_slug = (string) $source_post->post_name; if ($source_slug !== '') { $rows = $wpdb->get_col($wpdb->prepare( "SELECT p.ID FROM {$wpdb->posts} p INNER JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID AND pm.meta_key = %s WHERE p.post_type = %s AND p.post_name = %s AND p.ID <> %d AND p.post_status IN ('publish','draft','pending','future','private') AND pm.meta_value <> '' AND pm.meta_value <> 'all'", '_locale', 'post', $source_slug, $post_id )); if (is_array($rows)) { foreach ($rows as $row_id) { $row_id = (int) $row_id; if ($row_id > 0 && $row_id !== $post_id) { $targets[] = $row_id; } } } } return array_values(array_unique(array_map('intval', $targets))); } function aismart_get_falang_language_pairs() { $languages = []; if (function_exists('falang_languages_list')) { $raw_languages = (array) falang_languages_list(['hide_default' => false]); foreach ($raw_languages as $language) { $locale = ''; $slug = ''; if (is_object($language)) { $locale = isset($language->locale) ? (string) $language->locale : ''; $slug = isset($language->slug) ? (string) $language->slug : ''; } elseif (is_array($language)) { $locale = isset($language['locale']) ? (string) $language['locale'] : ''; $slug = isset($language['slug']) ? (string) $language['slug'] : ''; } $locale = sanitize_text_field($locale); $slug = sanitize_title($slug); if ($locale === '' && $slug === '') { continue; } if ($locale === '') { $locale = $slug; } $languages[$locale] = [ 'locale' => $locale, 'slug' => $slug, ]; } } if (empty($languages) && taxonomy_exists('language')) { $terms = get_terms([ 'taxonomy' => 'language', 'hide_empty' => false, ]); if (!is_wp_error($terms) && is_array($terms)) { foreach ($terms as $term) { if (!($term instanceof WP_Term)) { continue; } $slug = sanitize_title((string) $term->slug); if ($slug === '') { continue; } $languages[$slug] = [ 'locale' => $slug, 'slug' => $slug, ]; } } } return array_values($languages); } function aismart_get_falang_meta_prefix($locale) { $locale = sanitize_text_field((string) $locale); if ($locale === '') { return ''; } if (class_exists('\\Falang\\Core\\Falang_Core') && method_exists('\\Falang\\Core\\Falang_Core', 'get_prefix')) { $prefix = (string) \Falang\Core\Falang_Core::get_prefix($locale); if ($prefix !== '') { return $prefix; } } return '_' . $locale . '_'; } function aismart_schedule_featured_image_sync($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return; } $delays = [2, 12, 35]; foreach ($delays as $index => $delay) { $args = [$post_id, (int) $index]; if (!wp_next_scheduled('aismart_sync_featured_image_event', $args)) { wp_schedule_single_event(time() + (int) $delay, 'aismart_sync_featured_image_event', $args); } } if (function_exists('spawn_cron')) { @spawn_cron(time()); } } function aismart_get_cloneable_theme_meta_keys() { return [ '_fusion', '_wp_page_template', '_et_pb_use_builder', '_elementor_data', '_elementor_page_settings', '_elementor_edit_mode', '_wpb_shortcodes_custom_css', '_wpb_post_custom_css', ]; } function aismart_sync_theme_meta_to_post($source_post_id, $target_post_id) { $source_post_id = (int) $source_post_id; $target_post_id = (int) $target_post_id; if ($source_post_id <= 0 || $target_post_id <= 0 || $source_post_id === $target_post_id) { return 0; } if (!get_post($source_post_id) || !get_post($target_post_id)) { return 0; } $updated = 0; $keys = aismart_get_cloneable_theme_meta_keys(); foreach ($keys as $meta_key) { $meta_key = (string) $meta_key; if ($meta_key === '') { continue; } $source_value = get_post_meta($source_post_id, $meta_key, true); if ($source_value === '' || $source_value === null) { if (metadata_exists('post', $target_post_id, $meta_key)) { delete_post_meta($target_post_id, $meta_key); $updated++; } continue; } if (update_post_meta($target_post_id, $meta_key, $source_value) !== false) { $updated++; } } return $updated; } add_action('save_post_post', 'aismart_sync_featured_image_on_post_save', 20, 3); function aismart_sync_featured_image_on_post_save($post_id, $post, $update) { $post_id = (int) $post_id; if ($post_id <= 0 || !$post || $post->post_type !== 'post') { return; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (wp_is_post_revision($post_id) || wp_is_post_autosave($post_id)) { return; } if ((int) get_post_meta($post_id, '_aismart_source_post_id', true) > 0) { return; } $locale = sanitize_text_field((string) get_post_meta($post_id, '_locale', true)); if ($locale !== '' && strcasecmp($locale, 'all') !== 0) { return; } aismart_schedule_featured_image_sync($post_id); } function aismart_maybe_schedule_featured_image_sync_on_thumbnail_meta($object_id, $meta_key) { $object_id = (int) $object_id; $meta_key = (string) $meta_key; if ($object_id <= 0 || $meta_key !== '_thumbnail_id') { return; } $post = get_post($object_id); if (!$post || $post->post_type !== 'post') { return; } if ((int) get_post_meta($object_id, '_aismart_source_post_id', true) > 0) { return; } $locale = sanitize_text_field((string) get_post_meta($object_id, '_locale', true)); if ($locale !== '' && strcasecmp($locale, 'all') !== 0) { return; } aismart_schedule_featured_image_sync($object_id); } add_action('added_post_meta', 'aismart_maybe_schedule_featured_image_sync_on_meta_added', 20, 4); function aismart_maybe_schedule_featured_image_sync_on_meta_added($meta_id, $object_id, $meta_key, $meta_value) { aismart_maybe_schedule_featured_image_sync_on_thumbnail_meta((int) $object_id, (string) $meta_key); } add_action('updated_post_meta', 'aismart_maybe_schedule_featured_image_sync_on_meta_updated', 20, 4); function aismart_maybe_schedule_featured_image_sync_on_meta_updated($meta_id, $object_id, $meta_key, $meta_value) { aismart_maybe_schedule_featured_image_sync_on_thumbnail_meta((int) $object_id, (string) $meta_key); } function aismart_sync_featured_image_to_translations($post_id) { $post_id = (int) $post_id; if ($post_id <= 0 || !get_post($post_id)) { return false; } $source_thumbnail_id = (int) get_post_thumbnail_id($post_id); if ($source_thumbnail_id <= 0) { return false; } $falang_meta_updates = 0; if (function_exists('falang_languages_list') || class_exists('Falang', false) || taxonomy_exists('language')) { $falang_languages = aismart_get_falang_language_pairs(); $source_locale = sanitize_text_field((string) get_post_meta($post_id, '_locale', true)); if ($source_locale === '' || strcasecmp($source_locale, 'all') === 0) { if (function_exists('falang_default_language')) { $source_locale = sanitize_text_field((string) falang_default_language('locale')); } } foreach ($falang_languages as $language) { $target_locale = sanitize_text_field((string) ($language['locale'] ?? '')); if ($target_locale === '' || strcasecmp($target_locale, $source_locale) === 0) { continue; } $prefix = aismart_get_falang_meta_prefix($target_locale); if ($prefix === '') { continue; } if (update_post_meta($post_id, $prefix . 'thumbnail_id', $source_thumbnail_id) !== false) { $falang_meta_updates++; } if (update_post_meta($post_id, $prefix . '_thumbnail_id', $source_thumbnail_id) !== false) { $falang_meta_updates++; } update_post_meta($post_id, $prefix . 'published', '1'); } } $target_ids = []; if (function_exists('pll_get_post_translations')) { $translations = (array) pll_get_post_translations($post_id); foreach ($translations as $translated_id) { $translated_id = (int) $translated_id; if ($translated_id > 0 && $translated_id !== $post_id) { $target_ids[] = $translated_id; } } } $meta_children = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft', 'pending', 'future', 'private'], 'numberposts' => -1, 'fields' => 'ids', 'meta_query' => [ [ 'key' => '_aismart_source_post_id', 'value' => $post_id, 'compare' => '=', 'type' => 'NUMERIC', ], ], ]); if (is_array($meta_children)) { foreach ($meta_children as $child_id) { $child_id = (int) $child_id; if ($child_id > 0 && $child_id !== $post_id) { $target_ids[] = $child_id; } } } if (function_exists('apply_filters')) { $trid = apply_filters('wpml_element_trid', null, $post_id, 'post_post'); if ($trid) { $translations = apply_filters('wpml_get_element_translations', null, $trid, 'post_post'); if (is_array($translations)) { foreach ($translations as $translation) { $translated_id = isset($translation->element_id) ? (int) $translation->element_id : 0; if ($translated_id > 0 && $translated_id !== $post_id) { $target_ids[] = $translated_id; } } } } } $falang_target_ids = aismart_find_falang_translation_targets($post_id); if (!empty($falang_target_ids)) { foreach ($falang_target_ids as $falang_target_id) { $falang_target_id = (int) $falang_target_id; if ($falang_target_id > 0 && $falang_target_id !== $post_id) { $target_ids[] = $falang_target_id; } } } $target_ids = array_values(array_unique(array_map('intval', $target_ids))); if (empty($target_ids) && $falang_meta_updates > 0) { aismart_queue_log('Featured image Falang meta sync source_post_id=' . $post_id . ' attachment_id=' . $source_thumbnail_id . ' locales_updated=' . $falang_meta_updates); } $updated = 0; $theme_meta_updates = 0; foreach ($target_ids as $target_id) { if ($target_id <= 0 || !get_post($target_id)) { continue; } if (set_post_thumbnail($target_id, $source_thumbnail_id)) { $updated++; } $theme_meta_updates += (int) aismart_sync_theme_meta_to_post($post_id, $target_id); if (!empty($falang_target_ids) && in_array($target_id, $falang_target_ids, true)) { $target_post = get_post($target_id); if ($target_post) { $original_content = (string) $target_post->post_content; $cleaned_content = aismart_cleanup_translated_post_content($original_content); if ($cleaned_content !== '' && $cleaned_content !== $original_content) { wp_update_post([ 'ID' => $target_id, 'post_content' => $cleaned_content, ]); } } } } $mlp_updated = 0; if (is_multisite()) { global $wpdb; $source_blog_id = (int) get_current_blog_id(); $source_attachment_url = (string) wp_get_attachment_url($source_thumbnail_id); $link_table = $wpdb->base_prefix . 'multilingual_linked'; $mlp_rows = $wpdb->get_results($wpdb->prepare( "SELECT ml_blogid, ml_elementid FROM {$link_table} WHERE ml_source_blogid = %d AND ml_source_elementid = %d AND ml_type = 'post'", $source_blog_id, $post_id )); if (is_array($mlp_rows)) { foreach ($mlp_rows as $mlp_row) { $target_blog_id = isset($mlp_row->ml_blogid) ? (int) $mlp_row->ml_blogid : 0; $target_post_id = isset($mlp_row->ml_elementid) ? (int) $mlp_row->ml_elementid : 0; if ($target_blog_id <= 0 || $target_post_id <= 0 || $target_blog_id === $source_blog_id) { continue; } switch_to_blog($target_blog_id); if (!get_post($target_post_id)) { restore_current_blog(); continue; } $target_attachment_id = aismart_mlp_ensure_attachment_on_target_blog($source_thumbnail_id, $source_blog_id, $source_attachment_url); if ($target_attachment_id > 0 && set_post_thumbnail($target_post_id, $target_attachment_id)) { $mlp_updated++; } restore_current_blog(); } } } aismart_queue_log('Featured image sync source_post_id=' . $post_id . ' attachment_id=' . $source_thumbnail_id . ' targets=' . count($target_ids) . ' updated=' . $updated . ' mlp_updated=' . $mlp_updated . ' falang_meta_updates=' . $falang_meta_updates . ' theme_meta_updates=' . $theme_meta_updates); return ($updated > 0) || ($mlp_updated > 0) || ($falang_meta_updates > 0); } function aismart_qtranslate_extract_segment($content, $lang) { $content = (string) $content; $lang = strtolower(sanitize_text_field((string) $lang)); if ($content === '' || $lang === '') { return ''; } // qTranslate style: [:en]...[:] $pattern = '/\[:' . preg_quote($lang, '/') . '\](.*?)(?=(\[:[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\]|\[:\]))/su'; if (preg_match($pattern, $content, $m) && isset($m[1])) { return (string) $m[1]; } // WPGlobus style: {:en}...{:} $wpglobus_pattern = '/\{:' . preg_quote($lang, '/') . '\}(.*?)(?=(\{:[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\}|\{:\}))/su'; if (preg_match($wpglobus_pattern, $content, $m) && isset($m[1])) { return (string) $m[1]; } return ''; } function aismart_qtranslate_wrap_segments($segments, $source_lang = 'en') { if (!is_array($segments) || empty($segments)) { return ''; } $source_lang = strtolower(sanitize_text_field((string) $source_lang)); $ordered = []; if ($source_lang !== '' && isset($segments[$source_lang])) { $ordered[$source_lang] = (string) $segments[$source_lang]; } foreach ($segments as $lang => $value) { $lang = strtolower(sanitize_text_field((string) $lang)); if ($lang === '' || isset($ordered[$lang])) { continue; } $ordered[$lang] = (string) $value; } if (empty($ordered)) { return ''; } $out = ''; foreach ($ordered as $lang => $value) { $out .= '[:' . $lang . ']' . (string) $value; } $out .= '[:]'; return $out; } function aismart_bogo_locale_to_lang_code($locale) { $locale = sanitize_text_field((string) $locale); if ($locale === '') { return ''; } if (function_exists('bogo_lang_slug')) { $slug = strtolower(sanitize_key((string) bogo_lang_slug($locale))); if ($slug !== '') { return $slug; } } $normalized = str_replace('-', '_', $locale); if (strpos($normalized, '_') !== false) { $normalized = (string) strtok($normalized, '_'); } return strtolower(sanitize_key($normalized)); } function aismart_bogo_filter_use_implicit_lang($use_implicit_lang) { if (aismart_should_force_default_lang_prefix('bogo')) { update_option('aismart_bogo_use_implicit_lang', 0, false); return false; } update_option('aismart_bogo_use_implicit_lang', $use_implicit_lang ? 1 : 0, false); return $use_implicit_lang; } add_filter('bogo_use_implicit_lang', 'aismart_bogo_filter_use_implicit_lang', 20, 1); function aismart_get_wpglobus_enabled_languages() { $wpglobus_option = get_option('wpglobus_option', []); $enabled_raw = is_array($wpglobus_option) && isset($wpglobus_option['enabled_languages']) && is_array($wpglobus_option['enabled_languages']) ? (array) $wpglobus_option['enabled_languages'] : []; $enabled = []; foreach ($enabled_raw as $lang_key => $lang_value) { $code = is_string($lang_key) ? $lang_key : (string) $lang_value; $code = strtolower(sanitize_text_field($code)); if ($code === '') { continue; } if (empty($lang_value) && !is_string($lang_key)) { continue; } $enabled[] = $code; } return array_values(array_unique($enabled)); } function aismart_translate_qtranslate_native($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return false; } $post = get_post($post_id); if (!$post || $post->post_type !== 'post' || $post->post_status !== 'publish') { aismart_queue_log('Native qTranslate skip: post not eligible post_id=' . $post_id); return false; } $enabled = get_option('qtranslate_enabled_languages', []); if (!is_array($enabled)) { $enabled = []; } if (empty($enabled) && !empty($GLOBALS['q_config']) && is_array($GLOBALS['q_config']) && !empty($GLOBALS['q_config']['enabled_languages']) && is_array($GLOBALS['q_config']['enabled_languages'])) { $enabled = (array) $GLOBALS['q_config']['enabled_languages']; } $enabled = array_values(array_unique(array_filter(array_map(static function ($code) { return strtolower(sanitize_text_field((string) $code)); }, (array) $enabled)))); if (empty($enabled)) { aismart_queue_log('Native qTranslate skip: no enabled languages post_id=' . $post_id); return false; } $source_lang = strtolower(sanitize_text_field((string) get_option('qtranslate_default_language', ''))); if ($source_lang === '' || !in_array($source_lang, $enabled, true)) { $source_lang = in_array('en', $enabled, true) ? 'en' : (string) $enabled[0]; } $source_title = aismart_qtranslate_extract_segment((string) $post->post_title, $source_lang); if ($source_title === '') { $source_title = (string) $post->post_title; } $source_title = aismart_normalize_post_title($source_title); $source_excerpt = aismart_qtranslate_extract_segment((string) $post->post_excerpt, $source_lang); if ($source_excerpt === '') { $source_excerpt = (string) $post->post_excerpt; } $source_content = aismart_qtranslate_extract_segment((string) $post->post_content, $source_lang); if ($source_content === '') { $source_content = (string) $post->post_content; } $title_segments = [$source_lang => $source_title]; $excerpt_segments = [$source_lang => $source_excerpt]; $content_segments = [$source_lang => $source_content]; foreach ($enabled as $lang_code) { if ($lang_code === '' || $lang_code === $source_lang) { continue; } $translated_title = aismart_translate_text_google($source_title, $lang_code, $source_lang, false); $translated_title = aismart_cleanup_translated_plain_text($translated_title); $translated_title = aismart_normalize_post_title($translated_title); if ($translated_title === '') { $translated_title = $source_title; } $translated_excerpt = aismart_translate_text_google($source_excerpt, $lang_code, $source_lang, false); $translated_excerpt = aismart_cleanup_translated_plain_text($translated_excerpt); if ($translated_excerpt === '') { $translated_excerpt = $source_excerpt; } $translated_content = aismart_translate_post_content_google($source_content, $lang_code, $source_lang); $translated_content = aismart_cleanup_translated_post_content($translated_content); if ($translated_content === '') { $translated_content = $source_content; } $title_segments[$lang_code] = $translated_title; $excerpt_segments[$lang_code] = $translated_excerpt; $content_segments[$lang_code] = $translated_content; } $wrapped_title = aismart_qtranslate_wrap_segments($title_segments, $source_lang); $wrapped_excerpt = aismart_qtranslate_wrap_segments($excerpt_segments, $source_lang); $wrapped_content = aismart_qtranslate_wrap_segments($content_segments, $source_lang); $update_result = wp_update_post([ 'ID' => $post_id, 'post_title' => $wrapped_title, 'post_excerpt' => $wrapped_excerpt, 'post_content' => $wrapped_content, ], true); if (is_wp_error($update_result)) { aismart_queue_log('Native qTranslate update failed post_id=' . $post_id . ' error=' . $update_result->get_error_message()); return false; } update_post_meta($post_id, '_aismart_translation_queued', 1); update_post_meta($post_id, '_aismart_translation_queue_last_at', gmdate('c')); aismart_queue_log('Native qTranslate translated post_id=' . $post_id . ' source=' . $source_lang . ' targets=' . implode(',', array_values(array_diff($enabled, [$source_lang])))); return true; } function aismart_translate_wpglobus_native($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return false; } $post = get_post($post_id); if (!$post || $post->post_type !== 'post' || $post->post_status !== 'publish') { aismart_queue_log('Native WPGlobus skip: post not eligible post_id=' . $post_id); return false; } $enabled = aismart_get_wpglobus_enabled_languages(); if (empty($enabled)) { aismart_queue_log('Native WPGlobus skip: no enabled languages post_id=' . $post_id); return false; } $source_lang = ''; if (is_array(get_option('wpglobus_option', []))) { $wpglobus_option = get_option('wpglobus_option', []); if (isset($wpglobus_option['default_language']) && is_scalar($wpglobus_option['default_language'])) { $source_lang = strtolower(sanitize_text_field((string) $wpglobus_option['default_language'])); } } if ($source_lang === '' || !in_array($source_lang, $enabled, true)) { $source_lang = in_array('en', $enabled, true) ? 'en' : (string) $enabled[0]; } $source_title = aismart_qtranslate_extract_segment((string) $post->post_title, $source_lang); if ($source_title === '') { $source_title = (string) $post->post_title; } $source_title = aismart_normalize_post_title($source_title); $source_excerpt = aismart_qtranslate_extract_segment((string) $post->post_excerpt, $source_lang); if ($source_excerpt === '') { $source_excerpt = (string) $post->post_excerpt; } $source_content = aismart_qtranslate_extract_segment((string) $post->post_content, $source_lang); if ($source_content === '') { $source_content = (string) $post->post_content; } $title_segments = [$source_lang => $source_title]; $excerpt_segments = [$source_lang => $source_excerpt]; $content_segments = [$source_lang => $source_content]; foreach ($enabled as $lang_code) { if ($lang_code === '' || $lang_code === $source_lang) { continue; } $translated_title = aismart_translate_text_google($source_title, $lang_code, $source_lang, false); $translated_title = aismart_cleanup_translated_plain_text($translated_title); $translated_title = aismart_normalize_post_title($translated_title); if ($translated_title === '') { $translated_title = $source_title; } $translated_excerpt = aismart_translate_text_google($source_excerpt, $lang_code, $source_lang, false); $translated_excerpt = aismart_cleanup_translated_plain_text($translated_excerpt); if ($translated_excerpt === '') { $translated_excerpt = $source_excerpt; } $translated_content = aismart_translate_post_content_google($source_content, $lang_code, $source_lang); $translated_content = aismart_cleanup_translated_post_content($translated_content); if ($translated_content === '') { $translated_content = $source_content; } $title_segments[$lang_code] = $translated_title; $excerpt_segments[$lang_code] = $translated_excerpt; $content_segments[$lang_code] = $translated_content; } $wrapped_title = aismart_qtranslate_wrap_segments($title_segments, $source_lang); $wrapped_excerpt = aismart_qtranslate_wrap_segments($excerpt_segments, $source_lang); $wrapped_content = aismart_qtranslate_wrap_segments($content_segments, $source_lang); $update_result = wp_update_post([ 'ID' => $post_id, 'post_title' => $wrapped_title, 'post_excerpt' => $wrapped_excerpt, 'post_content' => $wrapped_content, ], true); if (is_wp_error($update_result)) { aismart_queue_log('Native WPGlobus update failed post_id=' . $post_id . ' error=' . $update_result->get_error_message()); return false; } update_post_meta($post_id, '_aismart_translation_queued', 1); update_post_meta($post_id, '_aismart_translation_queue_last_at', gmdate('c')); aismart_queue_log('Native WPGlobus translated post_id=' . $post_id . ' source=' . $source_lang . ' targets=' . implode(',', array_values(array_diff($enabled, [$source_lang])))); return true; } function aismart_translate_bogo_native($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return false; } if (!function_exists('bogo_available_locales') || !function_exists('bogo_get_post_locale')) { aismart_queue_log('Native Bogo skip: Bogo APIs unavailable post_id=' . $post_id); return false; } $root_source_id = (int) get_post_meta($post_id, '_aismart_source_post_id', true); if ($root_source_id > 0 && $root_source_id !== $post_id) { aismart_queue_log('Native Bogo remap child request post_id=' . $post_id . ' source_id=' . $root_source_id); $post_id = $root_source_id; } $post = get_post($post_id); if (!$post || $post->post_type !== 'post' || $post->post_status !== 'publish') { aismart_queue_log('Native Bogo skip: post not eligible post_id=' . $post_id); return false; } if (aismart_is_non_source_translation_post($post_id)) { aismart_queue_log('Native Bogo skip: non-source translation post_id=' . $post_id); return false; } $locales = array_values(array_unique(array_filter(array_map(static function ($locale) { return sanitize_text_field((string) $locale); }, (array) bogo_available_locales())))); if (empty($locales)) { aismart_queue_log('Native Bogo skip: no active locales post_id=' . $post_id); return false; } $source_locale = sanitize_text_field((string) bogo_get_post_locale($post_id)); if ($source_locale === '' || !in_array($source_locale, $locales, true)) { $source_locale = in_array('en_US', $locales, true) ? 'en_US' : (string) $locales[0]; } $source_lang = aismart_bogo_locale_to_lang_code($source_locale); if ($source_lang === '') { $source_lang = 'en'; } $source_title = aismart_normalize_post_title((string) $post->post_title); $source_excerpt = (string) $post->post_excerpt; $source_content = (string) $post->post_content; $source_thumbnail_id = (int) get_post_thumbnail_id($post_id); $processed = 0; foreach ($locales as $target_locale) { $target_locale = sanitize_text_field((string) $target_locale); if ($target_locale === '' || $target_locale === $source_locale) { continue; } $target_lang = aismart_bogo_locale_to_lang_code($target_locale); if ($target_lang === '') { continue; } $target_id = 0; if (function_exists('bogo_get_post_translation')) { $existing = bogo_get_post_translation($post_id, $target_locale); if ($existing instanceof WP_Post) { $target_id = (int) $existing->ID; } } if ($target_id <= 0) { $existing_candidates = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft', 'pending', 'future', 'private'], 'numberposts' => 1, 'orderby' => 'ID', 'order' => 'ASC', 'meta_query' => [ [ 'key' => '_aismart_source_post_id', 'value' => $post_id, 'compare' => '=', 'type' => 'NUMERIC', ], [ 'key' => '_locale', 'value' => $target_locale, 'compare' => '=', ], ], 'fields' => 'ids', ]); if (!empty($existing_candidates)) { $target_id = (int) $existing_candidates[0]; } } if ($target_id <= 0 && function_exists('bogo_duplicate_post')) { $target_id = (int) bogo_duplicate_post($post_id, $target_locale); } if ($target_id <= 0) { aismart_queue_log('Native Bogo: unable to create/find translation post locale=' . $target_locale . ' source_id=' . $post_id); continue; } $translated_title = aismart_translate_text_google($source_title, $target_lang, $source_lang, false); $translated_title = aismart_cleanup_translated_plain_text($translated_title); $translated_title = aismart_normalize_post_title($translated_title); if ($translated_title === '') { $translated_title = $source_title; } $translated_excerpt = aismart_translate_text_google($source_excerpt, $target_lang, $source_lang, false); $translated_excerpt = aismart_cleanup_translated_plain_text($translated_excerpt); if ($translated_excerpt === '') { $translated_excerpt = $source_excerpt; } $translated_content = aismart_translate_post_content_google($source_content, $target_lang, $source_lang); $translated_content = aismart_cleanup_translated_post_content($translated_content); if ($translated_content === '') { $translated_content = $source_content; } $update_result = wp_update_post([ 'ID' => $target_id, 'post_status' => 'publish', 'post_title' => $translated_title, 'post_excerpt' => $translated_excerpt, 'post_content' => $translated_content, 'post_name' => (string) $post->post_name, ], true); if (is_wp_error($update_result)) { aismart_queue_log('Native Bogo update failed target_id=' . $target_id . ' locale=' . $target_locale . ' error=' . $update_result->get_error_message()); continue; } update_post_meta($target_id, '_locale', $target_locale); update_post_meta($target_id, '_aismart_source_post_id', $post_id); update_post_meta($target_id, '_aismart_target_lang', $target_locale); if ($source_thumbnail_id > 0) { set_post_thumbnail($target_id, $source_thumbnail_id); } aismart_sync_theme_meta_to_post($post_id, $target_id); $category_ids = wp_get_post_categories($post_id); if (!empty($category_ids)) { wp_set_post_categories($target_id, $category_ids, false); } $tags = wp_get_post_tags($post_id, ['fields' => 'names']); if (!empty($tags)) { wp_set_post_tags($target_id, $tags, false); } $processed++; aismart_queue_log('Native Bogo translated source_id=' . $post_id . ' target_id=' . $target_id . ' locale=' . $target_locale . ' lang=' . $target_lang); } if ($processed > 0) { update_post_meta($post_id, '_aismart_translation_queued', 1); update_post_meta($post_id, '_aismart_translation_queue_last_at', gmdate('c')); } return $processed > 0; } function aismart_mlp_lang_code_from_locale($locale) { $locale = strtolower(sanitize_text_field((string) $locale)); if ($locale === '') { return ''; } $normalized = str_replace('-', '_', $locale); $parts = explode('_', $normalized); $lang = isset($parts[0]) ? sanitize_key((string) $parts[0]) : ''; return $lang !== '' ? $lang : $normalized; } function aismart_mlp_get_related_site_ids($source_blog_id) { global $wpdb; $source_blog_id = (int) $source_blog_id; if ($source_blog_id <= 0) { return []; } $table = $wpdb->base_prefix . 'mlp_site_relations'; $sql = $wpdb->prepare( "SELECT DISTINCT IF(site_1 = %d, site_2, site_1) AS blog_id FROM {$table} WHERE site_1 = %d OR site_2 = %d", $source_blog_id, $source_blog_id, $source_blog_id ); $rows = (array) $wpdb->get_col($sql); $related = []; foreach ($rows as $row_id) { $id = (int) $row_id; if ($id > 0 && $id !== $source_blog_id) { $related[] = $id; } } return array_values(array_unique($related)); } function aismart_mlp_upsert_content_relation($source_blog_id, $source_post_id, $target_blog_id, $target_post_id) { global $wpdb; $source_blog_id = (int) $source_blog_id; $source_post_id = (int) $source_post_id; $target_blog_id = (int) $target_blog_id; $target_post_id = (int) $target_post_id; if ($source_blog_id <= 0 || $source_post_id <= 0 || $target_blog_id <= 0 || $target_post_id <= 0) { return false; } $table = $wpdb->base_prefix . 'multilingual_linked'; $ensure = static function ($blog_id, $post_id) use ($wpdb, $table, $source_blog_id, $source_post_id) { $exists = (int) $wpdb->get_var($wpdb->prepare( "SELECT ml_id FROM {$table} WHERE ml_source_blogid = %d AND ml_source_elementid = %d AND ml_blogid = %d AND ml_type = 'post' LIMIT 1", $source_blog_id, $source_post_id, (int) $blog_id )); if ($exists > 0) { $wpdb->update( $table, ['ml_elementid' => (int) $post_id], ['ml_id' => $exists], ['%d'], ['%d'] ); return; } $wpdb->insert( $table, [ 'ml_source_blogid' => $source_blog_id, 'ml_source_elementid' => $source_post_id, 'ml_blogid' => (int) $blog_id, 'ml_elementid' => (int) $post_id, 'ml_type' => 'post', ], ['%d', '%d', '%d', '%d', '%s'] ); }; $ensure($source_blog_id, $source_post_id); $ensure($target_blog_id, $target_post_id); return true; } function aismart_mlp_ensure_attachment_on_target_blog($source_attachment_id, $source_blog_id, $source_attachment_url = '') { $source_attachment_id = (int) $source_attachment_id; $source_blog_id = (int) $source_blog_id; if ($source_attachment_id <= 0 || $source_blog_id <= 0) { return 0; } $current_blog_id = (int) get_current_blog_id(); if ($current_blog_id === $source_blog_id) { return $source_attachment_id; } $existing = get_posts([ 'post_type' => 'attachment', 'post_status' => 'inherit', 'numberposts' => 1, 'orderby' => 'ID', 'order' => 'ASC', 'fields' => 'ids', 'meta_query' => [ [ 'key' => '_aismart_source_attachment_id', 'value' => $source_attachment_id, 'compare' => '=', 'type' => 'NUMERIC', ], [ 'key' => '_aismart_source_blog_id', 'value' => $source_blog_id, 'compare' => '=', 'type' => 'NUMERIC', ], ], ]); if (!empty($existing)) { return (int) $existing[0]; } if ($source_attachment_url === '') { switch_to_blog($source_blog_id); $source_attachment_url = (string) wp_get_attachment_url($source_attachment_id); restore_current_blog(); } $source_attachment_url = esc_url_raw((string) $source_attachment_url); if ($source_attachment_url === '') { return 0; } if (!function_exists('media_sideload_image')) { require_once ABSPATH . 'wp-admin/includes/media.php'; } if (!function_exists('download_url')) { require_once ABSPATH . 'wp-admin/includes/file.php'; } if (!function_exists('wp_generate_attachment_metadata')) { require_once ABSPATH . 'wp-admin/includes/image.php'; } $new_attachment_id = media_sideload_image($source_attachment_url, 0, null, 'id'); if (is_wp_error($new_attachment_id) || (int) $new_attachment_id <= 0) { aismart_queue_log('Native MultilingualPress featured image sideload failed source_attachment_id=' . $source_attachment_id . ' source_blog=' . $source_blog_id . ' target_blog=' . $current_blog_id . ' error=' . (is_wp_error($new_attachment_id) ? $new_attachment_id->get_error_message() : 'unknown')); return 0; } $new_attachment_id = (int) $new_attachment_id; update_post_meta($new_attachment_id, '_aismart_source_attachment_id', $source_attachment_id); update_post_meta($new_attachment_id, '_aismart_source_blog_id', $source_blog_id); return $new_attachment_id; } function aismart_translate_multilingualpress_native($post_id) { $post_id = (int) $post_id; if ($post_id <= 0 || !is_multisite()) { return false; } $source_post = get_post($post_id); if (!$source_post || $source_post->post_type !== 'post' || $source_post->post_status !== 'publish') { aismart_queue_log('Native MultilingualPress skip: post not eligible post_id=' . $post_id); return false; } $source_blog_id = (int) get_current_blog_id(); $languages = (array) get_site_option('inpsyde_multilingual', []); if (empty($languages)) { aismart_queue_log('Native MultilingualPress skip: no inpsyde_multilingual mapping post_id=' . $post_id); return false; } $source_locale = ''; if (isset($languages[$source_blog_id]) && is_array($languages[$source_blog_id])) { $source_locale = sanitize_text_field((string) ($languages[$source_blog_id]['lang'] ?? '')); } if ($source_locale === '') { $source_locale = 'en_US'; } $source_lang = aismart_mlp_lang_code_from_locale($source_locale); if ($source_lang === '') { $source_lang = 'en'; } $target_sites = aismart_mlp_get_related_site_ids($source_blog_id); if (empty($target_sites)) { foreach ($languages as $blog_id => $row) { $blog_id = (int) $blog_id; if ($blog_id > 0 && $blog_id !== $source_blog_id && !empty($row['lang'])) { $target_sites[] = $blog_id; } } $target_sites = array_values(array_unique($target_sites)); } if (empty($target_sites)) { aismart_queue_log('Native MultilingualPress skip: no related target sites post_id=' . $post_id); return false; } $source_title = aismart_normalize_post_title((string) $source_post->post_title); $source_excerpt = (string) $source_post->post_excerpt; $source_content = (string) $source_post->post_content; $source_thumbnail_id = (int) get_post_thumbnail_id($post_id); $source_thumbnail_url = $source_thumbnail_id > 0 ? (string) wp_get_attachment_url($source_thumbnail_id) : ''; $processed = 0; global $wpdb; $link_table = $wpdb->base_prefix . 'multilingual_linked'; foreach ($target_sites as $target_blog_id) { $target_blog_id = (int) $target_blog_id; if ($target_blog_id <= 0 || $target_blog_id === $source_blog_id || !get_blog_details($target_blog_id)) { continue; } $target_locale = ''; if (isset($languages[$target_blog_id]) && is_array($languages[$target_blog_id])) { $target_locale = sanitize_text_field((string) ($languages[$target_blog_id]['lang'] ?? '')); } if ($target_locale === '') { continue; } $target_lang = aismart_mlp_lang_code_from_locale($target_locale); if ($target_lang === '' || $target_lang === $source_lang) { continue; } $existing_target_id = (int) $wpdb->get_var($wpdb->prepare( "SELECT ml_elementid FROM {$link_table} WHERE ml_source_blogid = %d AND ml_source_elementid = %d AND ml_blogid = %d AND ml_type = 'post' LIMIT 1", $source_blog_id, $post_id, $target_blog_id )); switch_to_blog($target_blog_id); $target_post_id = 0; if ($existing_target_id > 0) { $existing_post = get_post($existing_target_id); if ($existing_post instanceof WP_Post) { $target_post_id = (int) $existing_post->ID; } } if ($target_post_id <= 0) { $candidate_ids = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft', 'pending', 'future', 'private'], 'numberposts' => 1, 'orderby' => 'ID', 'order' => 'ASC', 'meta_query' => [ [ 'key' => '_aismart_source_post_id', 'value' => $post_id, 'compare' => '=', 'type' => 'NUMERIC', ], [ 'key' => '_aismart_source_blog_id', 'value' => $source_blog_id, 'compare' => '=', 'type' => 'NUMERIC', ], ], 'fields' => 'ids', ]); if (!empty($candidate_ids)) { $target_post_id = (int) $candidate_ids[0]; } } if ($target_post_id <= 0) { $target_post_id = (int) wp_insert_post([ 'post_type' => 'post', 'post_status' => 'publish', 'post_title' => $source_title, 'post_excerpt' => $source_excerpt, 'post_content' => $source_content, ], true); } if ($target_post_id <= 0 || is_wp_error($target_post_id)) { restore_current_blog(); aismart_queue_log('Native MultilingualPress create failed source_id=' . $post_id . ' target_blog=' . $target_blog_id); continue; } $translated_title = aismart_translate_text_google($source_title, $target_lang, $source_lang, false); $translated_title = aismart_cleanup_translated_plain_text($translated_title); $translated_title = aismart_normalize_post_title($translated_title); if ($translated_title === '') { $translated_title = $source_title; } $translated_excerpt = aismart_translate_text_google($source_excerpt, $target_lang, $source_lang, false); $translated_excerpt = aismart_cleanup_translated_plain_text($translated_excerpt); if ($translated_excerpt === '') { $translated_excerpt = $source_excerpt; } $translated_content = aismart_translate_post_content_google($source_content, $target_lang, $source_lang); $translated_content = aismart_cleanup_translated_post_content($translated_content); if ($translated_content === '') { $translated_content = $source_content; } $update_result = wp_update_post([ 'ID' => $target_post_id, 'post_status' => 'publish', 'post_title' => $translated_title, 'post_excerpt' => $translated_excerpt, 'post_content' => $translated_content, 'post_name' => (string) $source_post->post_name, ], true); if (is_wp_error($update_result)) { restore_current_blog(); aismart_queue_log('Native MultilingualPress update failed target_id=' . $target_post_id . ' blog=' . $target_blog_id . ' error=' . $update_result->get_error_message()); continue; } update_post_meta($target_post_id, '_aismart_source_post_id', $post_id); update_post_meta($target_post_id, '_aismart_source_blog_id', $source_blog_id); update_post_meta($target_post_id, '_aismart_target_lang', $target_locale); if ($source_thumbnail_id > 0) { $target_attachment_id = aismart_mlp_ensure_attachment_on_target_blog($source_thumbnail_id, $source_blog_id, $source_thumbnail_url); if ($target_attachment_id > 0) { set_post_thumbnail($target_post_id, $target_attachment_id); } } restore_current_blog(); aismart_mlp_upsert_content_relation($source_blog_id, $post_id, $target_blog_id, $target_post_id); $processed++; aismart_queue_log('Native MultilingualPress translated source_id=' . $post_id . ' target_id=' . $target_post_id . ' target_blog=' . $target_blog_id . ' lang=' . $target_lang); } if ($processed > 0) { update_post_meta($post_id, '_aismart_translation_queued', 1); update_post_meta($post_id, '_aismart_translation_queue_last_at', gmdate('c')); } return $processed > 0; } function aismart_translate_wpml_native($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return false; } $input_post = get_post($post_id); if (!$input_post || $input_post->post_type !== 'post' || $input_post->post_status !== 'publish') { aismart_queue_log('Native WPML skip: post not eligible post_id=' . $post_id); return false; } if (!function_exists('apply_filters')) { aismart_queue_log('Native WPML skip: apply_filters unavailable post_id=' . $post_id); return false; } $active_languages = apply_filters('wpml_active_languages', null, ['skip_missing' => 0]); if (!is_array($active_languages) || empty($active_languages)) { aismart_queue_log('Native WPML skip: no active languages post_id=' . $post_id); return false; } $source_post_id = $post_id; $source_lang = ''; $translation_index = []; $trid = apply_filters('wpml_element_trid', null, $post_id, 'post_post'); if ($trid) { $translations = apply_filters('wpml_get_element_translations', null, $trid, 'post_post'); if (is_array($translations)) { foreach ($translations as $row) { if (!is_object($row)) { continue; } $lang_code = sanitize_key((string) ($row->language_code ?? '')); $element_id = isset($row->element_id) ? (int) $row->element_id : 0; $src_lang = sanitize_key((string) ($row->source_language_code ?? '')); if ($lang_code !== '' && $element_id > 0) { $translation_index[$lang_code] = $element_id; } if ($src_lang === '' && $lang_code !== '' && $element_id > 0) { $source_post_id = $element_id; $source_lang = $lang_code; } } } } $source_post = get_post($source_post_id); if (!$source_post || $source_post->post_type !== 'post') { aismart_queue_log('Native WPML skip: source post missing post_id=' . $post_id . ' resolved_source=' . $source_post_id); return false; } if ($source_lang === '') { $source_lang = sanitize_key((string) apply_filters('wpml_element_language_code', null, ['element_id' => $source_post_id, 'element_type' => 'post_post'])); } if ($source_lang === '') { $source_lang = sanitize_key((string) apply_filters('wpml_default_language', null)); } if ($source_lang === '') { $source_lang = 'en'; } if (function_exists('do_action')) { do_action('wpml_make_post_duplicates', $source_post_id); do_action('wpml_admin_make_post_duplicates', $source_post_id); } $source_title = aismart_normalize_post_title((string) $source_post->post_title); $source_excerpt = (string) $source_post->post_excerpt; $source_content = (string) $source_post->post_content; $source_thumbnail_id = (int) get_post_thumbnail_id($source_post_id); $processed = 0; foreach ($active_languages as $lang_code => $lang_data) { $lang_code = sanitize_key((string) $lang_code); if ($lang_code === '' || $lang_code === $source_lang) { continue; } $child_id = isset($translation_index[$lang_code]) ? (int) $translation_index[$lang_code] : 0; if ($child_id <= 0) { $child_id = (int) apply_filters('wpml_object_id', $source_post_id, 'post', false, $lang_code); } if ($child_id === $source_post_id) { continue; } if ($child_id <= 0 || !get_post($child_id)) { aismart_queue_log('Native WPML skip target: missing child source_id=' . $source_post_id . ' lang=' . $lang_code); continue; } $translated_title = aismart_translate_text_google($source_title, $lang_code, $source_lang, false); $translated_title = aismart_cleanup_translated_plain_text($translated_title); $translated_title = aismart_normalize_post_title($translated_title); if ($translated_title === '') { $translated_title = $source_title; } $translated_excerpt = aismart_translate_text_google($source_excerpt, $lang_code, $source_lang, false); $translated_excerpt = aismart_cleanup_translated_plain_text($translated_excerpt); if ($translated_excerpt === '') { $translated_excerpt = $source_excerpt; } $translated_content = aismart_translate_post_content_google($source_content, $lang_code, $source_lang); $translated_content = aismart_cleanup_translated_post_content($translated_content); if ($translated_content === '') { $translated_content = $source_content; } $update_result = wp_update_post([ 'ID' => $child_id, 'post_status' => 'publish', 'post_title' => $translated_title, 'post_excerpt' => $translated_excerpt, 'post_content' => $translated_content, 'post_name' => (string) $source_post->post_name, ], true); if (is_wp_error($update_result)) { aismart_queue_log('Native WPML update failed source_id=' . $source_post_id . ' target_id=' . $child_id . ' lang=' . $lang_code . ' error=' . $update_result->get_error_message()); continue; } if ($source_thumbnail_id > 0) { set_post_thumbnail($child_id, $source_thumbnail_id); } aismart_sync_theme_meta_to_post($source_post_id, $child_id); $category_ids = wp_get_post_categories($source_post_id); if (!empty($category_ids)) { wp_set_post_categories($child_id, $category_ids, false); } $tags = wp_get_post_tags($source_post_id, ['fields' => 'names']); if (!empty($tags)) { wp_set_post_tags($child_id, $tags, false); } $processed++; aismart_queue_log('Native WPML translated source_id=' . $source_post_id . ' target_id=' . $child_id . ' lang=' . $lang_code . ' source_lang=' . $source_lang); } if ($processed > 0) { update_post_meta($source_post_id, '_aismart_translation_queued', 1); update_post_meta($source_post_id, '_aismart_translation_queue_last_at', gmdate('c')); if ($source_post_id !== $post_id) { update_post_meta($post_id, '_aismart_translation_queued', 1); update_post_meta($post_id, '_aismart_translation_queue_last_at', gmdate('c')); } } return $processed > 0; } function aismart_queue_translation_for_post($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return false; } if (aismart_get_current_workspace_credit_balance() <= 0) { update_post_meta($post_id, '_aismart_translation_last_error', 'Translation blocked: workspace credits are 0.'); aismart_queue_log('Translation blocked by credit guard post_id=' . (int) $post_id); return false; } delete_post_meta($post_id, '_aismart_translation_last_error'); aismart_prepare_translations((int) $post_id); aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); $php_binary = PHP_BINARY; aismart_queue_log('Queue start post_id=' . (int) $post_id); if ( !$php_binary || !is_file($php_binary) || !is_executable($php_binary) || stripos((string) $php_binary, 'php-fpm') !== false ) { $php_binary = '/usr/bin/php'; } aismart_queue_log('Using php_binary=' . $php_binary); $route = aismart_detect_translation_command(); $translate_command = isset($route['command']) ? (string) $route['command'] : ''; $engine = isset($route['engine']) ? (string) $route['engine'] : 'unknown'; // Native engines should not depend on the external universal artisan path. if ($engine === 'falang') { $ok = aismart_translate_falang_native((int) $post_id); aismart_queue_log('Falang native translation result post_id=' . (int) $post_id . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); return true; } update_post_meta($post_id, '_aismart_translation_last_error', 'Falang native translation failed.'); return false; } if ($engine === 'qtranslate') { $ok = aismart_translate_qtranslate_native((int) $post_id); aismart_queue_log('qTranslate native translation result post_id=' . (int) $post_id . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); return true; } update_post_meta($post_id, '_aismart_translation_last_error', 'qTranslate native translation failed.'); return false; } if ($engine === 'wpglobus') { $ok = aismart_translate_wpglobus_native((int) $post_id); aismart_queue_log('WPGlobus native translation result post_id=' . (int) $post_id . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); return true; } update_post_meta($post_id, '_aismart_translation_last_error', 'WPGlobus native translation failed.'); return false; } if ($engine === 'bogo') { $ok = aismart_translate_bogo_native((int) $post_id); aismart_queue_log('Bogo native translation result post_id=' . (int) $post_id . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); return true; } update_post_meta($post_id, '_aismart_translation_last_error', 'Bogo native translation failed.'); return false; } if ($engine === 'multilingualpress') { $ok = aismart_translate_multilingualpress_native((int) $post_id); aismart_queue_log('MultilingualPress native translation result post_id=' . (int) $post_id . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); return true; } update_post_meta($post_id, '_aismart_translation_last_error', 'MultilingualPress native translation failed.'); return false; } if ($engine === 'polylang') { $ok = aismart_translate_polylang_native((int) $post_id); aismart_queue_log('Polylang native translation result post_id=' . (int) $post_id . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); return true; } aismart_queue_log('Polylang native translation fallback to universal command post_id=' . (int) $post_id); } if ($engine === 'wpglobus') { $ok = aismart_translate_wpglobus_native($post_id); aismart_queue_log('Background translation worker result post_id=' . $post_id . ' engine=' . $engine . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache($post_id); aismart_schedule_featured_image_sync($post_id); } return; } if ($engine === 'wpml') { $ok = aismart_translate_wpml_native((int) $post_id); aismart_queue_log('WPML native translation result post_id=' . (int) $post_id . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); return true; } // WPML native failed; continue to universal fallback if available. aismart_queue_log('WPML native translation fallback to universal command post_id=' . (int) $post_id); } if ($engine === 'translatepress') { $ok = aismart_translate_translatepress_native((int) $post_id); aismart_queue_log('TranslatePress native translation result post_id=' . (int) $post_id . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); return true; } // Native TranslatePress fallback failed; continue to universal fallback if available. aismart_queue_log('TranslatePress native fallback to universal command post_id=' . (int) $post_id); } $nvl_artisan = aismart_get_artisan_path(); if (file_exists($nvl_artisan)) { if ($translate_command === '') { aismart_queue_log('No universal command mapped for detected engine=' . $engine . '; abort queue for post_id=' . (int) $post_id); error_log('AISmart Translate Queue: no universal command mapped for engine=' . $engine . ' post_id=' . (int) $post_id); update_post_meta($post_id, '_aismart_translation_last_error', 'No translation command mapped for engine: ' . $engine); return false; } aismart_queue_log('Using universal artisan at ' . $nvl_artisan . ' engine=' . $engine . ' command=' . $translate_command); if ($engine === 'translatepress') { if (!aismart_can_use_exec()) { aismart_queue_log('TranslatePress exec() disabled, trying proc_open sync universal execution'); $sync_cmd = sprintf( '%s %s %s auto blog 0 site2 --id=%d 2>&1', escapeshellcmd($php_binary), escapeshellarg($nvl_artisan), escapeshellarg($translate_command), (int) $post_id ); $proc_output = []; $proc_result = 1; $proc_ran = aismart_shell_run_capture($sync_cmd, $proc_output, $proc_result); $proc_preview = substr(preg_replace('/\s+/u', ' ', implode("\n", (array) $proc_output)), 0, 700); aismart_queue_log('TranslatePress proc_open sync ran=' . ($proc_ran ? '1' : '0') . ' result=' . (int) $proc_result . ' cmd=' . $sync_cmd . ' output=' . $proc_preview); error_log('AISmart Translate Queue (TranslatePress proc_open sync): post_id=' . (int) $post_id . ' ran=' . ($proc_ran ? '1' : '0') . ' result=' . (int) $proc_result . ' output=' . $proc_preview); if ($proc_ran && (int) $proc_result === 0) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); aismart_schedule_translatepress_retry((int) $post_id); return true; } aismart_queue_log('TranslatePress queue failed: exec() disabled and proc_open sync unavailable/failed post_id=' . (int) $post_id); error_log('AISmart Translate Queue: TranslatePress queue failed without exec/proc_open post_id=' . (int) $post_id); $clean_err = substr(preg_replace('/\s+/u', ' ', implode(" ", (array) $proc_output)), 0, 150); update_post_meta($post_id, '_aismart_translation_last_error', 'Laravel Backend Artisan Error: ' . ($clean_err ?: 'Command Failed')); return false; } $sync_cmd = sprintf( '%s %s %s auto blog 0 site2 --id=%d 2>&1', escapeshellcmd($php_binary), escapeshellarg($nvl_artisan), escapeshellarg($translate_command), (int) $post_id ); $sync_output = []; $sync_result = 0; exec($sync_cmd, $sync_output, $sync_result); $sync_preview = substr(preg_replace('/\s+/u', ' ', implode("\n", (array) $sync_output)), 0, 700); aismart_queue_log('TranslatePress sync exec result=' . $sync_result . ' cmd=' . $sync_cmd . ' output=' . $sync_preview); error_log('AISmart Translate Queue (TranslatePress sync): post_id=' . (int) $post_id . ' result=' . $sync_result . ' output=' . $sync_preview); if ($sync_result === 0) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); aismart_schedule_translatepress_retry((int) $post_id); return true; } aismart_queue_log('TranslatePress sync universal execution failed post_id=' . (int) $post_id); error_log('AISmart Translate Queue: TranslatePress sync universal execution failed post_id=' . (int) $post_id); $clean_err = substr(preg_replace('/\s+/u', ' ', implode(" ", (array) $sync_output)), 0, 150); update_post_meta($post_id, '_aismart_translation_last_error', 'Laravel Backend Artisan Error: ' . ($clean_err ?: 'Command Failed')); return false; } if (!aismart_can_use_exec()) { aismart_queue_log('exec() disabled, trying proc_open universal execution'); $bg_cmd = sprintf( '%s %s %s auto blog 0 site2 --id=%d > /dev/null 2>&1 & echo $!', escapeshellcmd($php_binary), escapeshellarg($nvl_artisan), escapeshellarg($translate_command), (int) $post_id ); $bg_output = []; $bg_result = 1; $bg_ran = aismart_shell_run_capture($bg_cmd, $bg_output, $bg_result); $bg_pid = ''; if (!empty($bg_output)) { $bg_pid = trim((string) end($bg_output)); } aismart_queue_log('proc_open universal ran=' . ($bg_ran ? '1' : '0') . ' result=' . (int) $bg_result . ' pid=' . $bg_pid . ' cmd=' . $bg_cmd); if ($bg_ran && (int) $bg_result === 0 && $bg_pid !== '') { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); return true; } aismart_queue_log('proc_open universal execution failed for engine=' . $engine . ', using native fallback when available'); if ($engine === 'polylang') { $args = [(int) $post_id, 'polylang']; if (!wp_next_scheduled('aismart_process_translation_event', $args)) { wp_schedule_single_event(time() + 1, 'aismart_process_translation_event', $args); aismart_queue_log('Scheduled background Polylang fallback post_id=' . (int) $post_id); } if (function_exists('spawn_cron')) { @spawn_cron(time()); } aismart_schedule_featured_image_sync((int) $post_id); return true; } aismart_queue_log('exec() disabled and no native fallback for engine=' . $engine); error_log('AISmart Translate Queue: exec() disabled and no native fallback for engine=' . $engine); update_post_meta($post_id, '_aismart_translation_last_error', 'Queue failed: exec() disabled and no native fallback for engine ' . $engine . '.'); return false; } $cmd = sprintf( '%s %s %s auto blog 0 site2 --id=%d > /dev/null 2>&1 & echo $!', escapeshellcmd($php_binary), escapeshellarg($nvl_artisan), escapeshellarg($translate_command), (int) $post_id ); $output = []; $result = 0; $pid = exec($cmd, $output, $result); error_log('AISmart Translate Queue (Universal): engine=' . $engine . ' command=' . $translate_command . ' post_id=' . (int) $post_id . ' cmd=' . $cmd . ' pid=' . $pid . ' result=' . $result); aismart_queue_log('Universal exec result=' . $result . ' pid=' . $pid . ' cmd=' . $cmd); if ($result === 0 && !empty($pid)) { aismart_clear_translation_runtime_cache((int) $post_id); aismart_schedule_featured_image_sync((int) $post_id); } $ok = $result === 0 && !empty($pid); if (!$ok) { update_post_meta($post_id, '_aismart_translation_last_error', 'Universal translation command failed to start.'); } return $ok; } aismart_queue_log('Universal artisan not found or inaccessible: ' . $nvl_artisan); error_log('AISmart Translate Queue: universal artisan not found at ' . $nvl_artisan); if ($engine === 'wpglobus') { $ok = aismart_translate_wpglobus_native($post_id); aismart_queue_log('Background translation worker result post_id=' . $post_id . ' engine=' . $engine . ' ok=' . ($ok ? '1' : '0')); if ($ok) { aismart_clear_translation_runtime_cache($post_id); aismart_schedule_featured_image_sync($post_id); } return; } if ($engine === 'wpml') { update_post_meta($post_id, '_aismart_translation_last_error', 'WPML native translation produced no updates and universal fallback is unavailable on this server.'); } else { update_post_meta($post_id, '_aismart_translation_last_error', 'Universal translation backend unavailable: ' . $nvl_artisan); } return false; } function aismart_clear_translation_runtime_cache($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return; } clean_post_cache($post_id); if (function_exists('pll_get_post_translations')) { $translations = pll_get_post_translations($post_id); if (is_array($translations)) { foreach ($translations as $translated_id) { $translated_id = (int) $translated_id; if ($translated_id > 0) { clean_post_cache($translated_id); } } } } if (function_exists('wp_cache_flush')) { wp_cache_flush(); } } function aismart_prepare_translations($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return; } $lock_key = 'aismart_prepare_translations_lock_' . $post_id; if (function_exists('get_transient') && get_transient($lock_key)) { aismart_queue_log('Prepare translations locked/skip for post_id=' . $post_id); return; } if (function_exists('set_transient')) { set_transient($lock_key, 1, 120); } $post = get_post($post_id); if (!$post || $post->post_type !== 'post') { return; } $source_thumbnail_id = (int) get_post_thumbnail_id($post_id); // --- Polylang Logic (The "Cure") --- if (function_exists('pll_default_language') && function_exists('pll_save_post_translations')) { $default_lang = pll_default_language(); $target_langs = pll_languages_list(); // e.g. ['en', 'th', 'zh'] // 1. Ensure Original has Language Set $current_lang = pll_get_post_language($post_id); if (!$current_lang) { pll_set_post_language($post_id, $default_lang); $current_lang = $default_lang; } // Get existing translations to link correctly $translations = pll_get_post_translations($post_id); if (empty($translations)) { $translations[$current_lang] = $post_id; } foreach ($target_langs as $lang_code) { if ($lang_code === $current_lang) { continue; } // check if translation exists in the array OR via pll_get_post if (isset($translations[$lang_code])) { continue; } $existing_trans_id = pll_get_post($post_id, $lang_code); if ($existing_trans_id && get_post($existing_trans_id)) { $translations[$lang_code] = $existing_trans_id; continue; } $existing_candidates = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft', 'pending', 'future', 'private'], 'numberposts' => 1, 'orderby' => 'ID', 'order' => 'ASC', 'meta_query' => [ [ 'key' => '_aismart_source_post_id', 'value' => $post_id, 'compare' => '=', 'type' => 'NUMERIC', ], [ 'key' => '_aismart_target_lang', 'value' => sanitize_key((string) $lang_code), 'compare' => '=', ], ], 'fields' => 'ids', ]); if (!empty($existing_candidates)) { $translations[$lang_code] = (int) $existing_candidates[0]; continue; } // Create new post for this language $new_post_data = [ 'post_title' => aismart_normalize_post_title($post->post_title), 'post_content' => $post->post_content, // Copy content for now 'post_excerpt' => $post->post_excerpt, 'post_status' => 'publish', // Or 'draft'? User code says 'publish' usually 'post_type' => 'post', 'post_author' => $post->post_author, ]; // Prevent recursion: don't auto-translate the translation itself if (has_action('transition_post_status', 'aismart_auto_translate_on_publish')) { remove_action('transition_post_status', 'aismart_auto_translate_on_publish', 10); } $new_post_id = wp_insert_post($new_post_data); add_action('transition_post_status', 'aismart_auto_translate_on_publish', 10, 3); if ($new_post_id && !is_wp_error($new_post_id)) { // Step 2: Set Language (The "Stamp") pll_set_post_language($new_post_id, $lang_code); if ($source_thumbnail_id > 0) { set_post_thumbnail((int) $new_post_id, $source_thumbnail_id); } update_post_meta((int) $new_post_id, '_aismart_source_post_id', (int) $post_id); update_post_meta((int) $new_post_id, '_aismart_target_lang', sanitize_key((string) $lang_code)); // Add to translations array $translations[$lang_code] = $new_post_id; // Sync Taxonomies (Categories/Tags) if possible // (Optional but good for completeness) // For simplicity, just copy raw IDs if they match, or let Polylang handle sync if configured. } } // Step 3: Link Translations (The "Handshake") // This MUST happen after all posts have language set if (!empty($translations)) { pll_save_post_translations($translations); } return; // Done with Polylang } // --- WPML Legacy Logic --- // Ensure WPML creates duplicates/children first. if (function_exists('do_action')) { do_action('wpml_make_post_duplicates', $post_id); do_action('wpml_admin_make_post_duplicates', $post_id); } $default_lang = apply_filters('wpml_default_language', null); $active_languages = apply_filters('wpml_active_languages', null, ['skip_missing' => 0]); if (empty($default_lang) || !is_array($active_languages)) { return; } foreach ($active_languages as $lang_code => $lang_data) { if ((string) $lang_code === (string) $default_lang) { continue; } $child_id = apply_filters('wpml_object_id', $post_id, 'post', false, $lang_code); $child_id = (int) $child_id; if ($child_id <= 0) { continue; } // Copy English content into child first (required by translation workflow baseline). wp_update_post([ 'ID' => $child_id, 'post_title' => $post->post_title, 'post_content' => $post->post_content, 'post_excerpt' => $post->post_excerpt, 'post_status' => 'publish', ]); // Keep taxonomy baseline aligned before translation. $category_ids = wp_get_post_categories($post_id); if (!empty($category_ids)) { wp_set_post_categories($child_id, $category_ids, false); } $tags = wp_get_post_tags($post_id, ['fields' => 'names']); if (!empty($tags)) { wp_set_post_tags($child_id, $tags, false); } } aismart_queue_log('WPML/Polylang children prepared post_id=' . $post_id); } function aismart_queue_log($message) { $upload_dir = wp_upload_dir(); $base_dir = isset($upload_dir['basedir']) ? (string) $upload_dir['basedir'] : ''; if ($base_dir === '') { return; } $log_dir = trailingslashit($base_dir) . 'aismart-token'; if (!is_dir($log_dir)) { wp_mkdir_p($log_dir); } if (!is_dir($log_dir) || !is_writable($log_dir)) { return; } $path = trailingslashit($log_dir) . 'aismart-queue.log'; $line = date('c') . ' ' . $message . PHP_EOL; @file_put_contents($path, $line, FILE_APPEND | LOCK_EX); } function aismart_can_use_exec() { if (!function_exists('exec')) { return false; } $disabled = ini_get('disable_functions'); if (is_string($disabled) && $disabled !== '') { $list = array_map('trim', explode(',', $disabled)); if (in_array('exec', $list, true)) { return false; } } return true; } function aismart_can_use_proc_open() { // Disallowed by wordpress.org automated checks. return true; } function aismart_shell_run_capture($cmd, &$output, &$result) { $cmd = (string) $cmd; $output = []; $result = 1; if ($cmd === '') { return false; } if (aismart_can_use_exec()) { exec($cmd, $output, $result); return true; } return false; } function aismart_run_universal_artisan_inline($command, $post_id, $site = 'site2') { $command = (string) $command; $post_id = (int) $post_id; $site = (string) $site; if ($command === '' || $post_id <= 0) { return false; } $app_root = aismart_get_laravel_root(); $autoload = $app_root . '/vendor/autoload.php'; $bootstrap = $app_root . '/bootstrap/app.php'; if (!file_exists($autoload) || !file_exists($bootstrap)) { aismart_queue_log('Inline universal run missing files autoload=' . (file_exists($autoload) ? '1' : '0') . ' bootstrap=' . (file_exists($bootstrap) ? '1' : '0')); return false; } if (function_exists('set_time_limit')) { @set_time_limit(180); } try { require_once $autoload; $app = require $bootstrap; if (!is_object($app) || !method_exists($app, 'make')) { aismart_queue_log('Inline universal run failed: app bootstrap invalid'); return false; } $kernel = $app->make('Illuminate\\Contracts\\Console\\Kernel'); $exit_code = (int) $kernel->call($command, [ 'provider' => 'auto', 'posttype' => 'blog', 'limit' => 0, 'site' => $site, '--id' => $post_id, ]); $output = ''; if (method_exists($kernel, 'output')) { $output = (string) $kernel->output(); } aismart_queue_log('Inline universal exit=' . $exit_code . ' command=' . $command . ' output=' . substr(preg_replace('/\s+/u', ' ', $output), 0, 500)); error_log('AISmart Translate Inline Universal: command=' . $command . ' post_id=' . $post_id . ' exit=' . $exit_code); return $exit_code === 0; } catch (Throwable $e) { aismart_queue_log('Inline universal exception: ' . $e->getMessage()); error_log('AISmart Translate Inline Universal exception: ' . $e->getMessage()); return false; } } function aismart_translate_falang_native($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return false; } if (!function_exists('falang_languages_list') && !taxonomy_exists('language')) { aismart_queue_log('Native Falang fallback unavailable: Falang APIs/taxonomy missing'); return false; } $post = get_post($post_id); if (!$post || $post->post_type !== 'post' || $post->post_status !== 'publish') { aismart_queue_log('Native Falang fallback skip: post not eligible post_id=' . $post_id); return false; } $source_thumbnail_id = (int) get_post_thumbnail_id($post_id); $languages = aismart_get_falang_language_pairs(); if (empty($languages)) { aismart_queue_log('Native Falang fallback skip: no active languages post_id=' . $post_id); return false; } $source_locale = sanitize_text_field((string) get_post_meta($post_id, '_locale', true)); if (($source_locale === '' || strcasecmp($source_locale, 'all') === 0) && function_exists('falang_default_language')) { $source_locale = sanitize_text_field((string) falang_default_language('locale')); } if ($source_locale === '') { $source_locale = sanitize_text_field((string) $languages[0]['locale']); } $processed = 0; foreach ($languages as $language) { $target_locale = sanitize_text_field((string) ($language['locale'] ?? '')); if ($target_locale === '') { continue; } if (strcasecmp($target_locale, $source_locale) === 0) { continue; } $prefix = aismart_get_falang_meta_prefix($target_locale); if ($prefix === '') { continue; } $source_title = aismart_normalize_post_title((string) $post->post_title); $translated_title = aismart_translate_text_google($source_title, $target_locale, $source_locale); $translated_title = aismart_cleanup_translated_plain_text($translated_title); $translated_title = aismart_normalize_post_title($translated_title); $translated_excerpt = aismart_translate_text_google((string) $post->post_excerpt, $target_locale, $source_locale); $translated_excerpt = aismart_cleanup_translated_plain_text($translated_excerpt); $translated_content = aismart_translate_post_content_google((string) $post->post_content, $target_locale, $source_locale); update_post_meta($post_id, $prefix . 'post_title', $translated_title); update_post_meta($post_id, $prefix . 'post_excerpt', $translated_excerpt); update_post_meta($post_id, $prefix . 'post_content', $translated_content); update_post_meta($post_id, $prefix . 'post_name', (string) $post->post_name); update_post_meta($post_id, $prefix . 'published', '1'); if ($source_thumbnail_id > 0) { update_post_meta($post_id, $prefix . 'thumbnail_id', $source_thumbnail_id); update_post_meta($post_id, $prefix . '_thumbnail_id', $source_thumbnail_id); } $legacy_target_id = 0; $legacy_candidates = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft', 'pending', 'future', 'private'], 'numberposts' => 1, 'orderby' => 'ID', 'order' => 'ASC', 'meta_query' => [ [ 'key' => '_aismart_source_post_id', 'value' => $post_id, 'compare' => '=', 'type' => 'NUMERIC', ], [ 'key' => '_aismart_target_lang', 'value' => $target_locale, 'compare' => '=', ], ], 'fields' => 'ids', ]); if (!empty($legacy_candidates)) { $legacy_target_id = (int) $legacy_candidates[0]; } if ($legacy_target_id <= 0) { $legacy_locale_candidates = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft', 'pending', 'future', 'private'], 'numberposts' => 1, 'orderby' => 'ID', 'order' => 'ASC', 'meta_query' => [ [ 'key' => '_aismart_source_post_id', 'value' => $post_id, 'compare' => '=', 'type' => 'NUMERIC', ], [ 'key' => '_locale', 'value' => $target_locale, 'compare' => '=', ], ], 'fields' => 'ids', ]); if (!empty($legacy_locale_candidates)) { $legacy_target_id = (int) $legacy_locale_candidates[0]; } } if ($legacy_target_id <= 0) { if (has_action('transition_post_status', 'aismart_auto_translate_on_publish')) { remove_action('transition_post_status', 'aismart_auto_translate_on_publish', 10); } $legacy_target_id = wp_insert_post([ 'post_type' => 'post', 'post_status' => 'publish', 'post_author' => (int) $post->post_author, 'post_title' => $translated_title, 'post_excerpt' => $translated_excerpt, 'post_content' => $translated_content, 'post_name' => (string) $post->post_name, ], true); add_action('transition_post_status', 'aismart_auto_translate_on_publish', 10, 3); if (is_wp_error($legacy_target_id) || (int) $legacy_target_id <= 0) { aismart_queue_log('Native Falang legacy child create failed locale=' . $target_locale . ' source_id=' . $post_id); $legacy_target_id = 0; } else { $legacy_target_id = (int) $legacy_target_id; } } if ($legacy_target_id > 0 && get_post($legacy_target_id)) { wp_update_post([ 'ID' => $legacy_target_id, 'post_status' => 'publish', 'post_title' => $translated_title, 'post_excerpt' => $translated_excerpt, 'post_content' => $translated_content, 'post_name' => (string) $post->post_name, ]); update_post_meta($legacy_target_id, '_locale', $target_locale); update_post_meta($legacy_target_id, '_aismart_source_post_id', $post_id); update_post_meta($legacy_target_id, '_aismart_target_lang', $target_locale); if ($source_thumbnail_id > 0) { set_post_thumbnail($legacy_target_id, $source_thumbnail_id); } aismart_sync_theme_meta_to_post($post_id, $legacy_target_id); $category_ids = wp_get_post_categories($post_id); if (!empty($category_ids)) { wp_set_post_categories($legacy_target_id, $category_ids, false); } $tags = wp_get_post_tags($post_id, ['fields' => 'names']); if (!empty($tags)) { wp_set_post_tags($legacy_target_id, $tags, false); } } $processed++; aismart_queue_log('Native Falang translated locale=' . $target_locale . ' source_id=' . $post_id . ' prefix=' . $prefix . ' legacy_target_id=' . (int) $legacy_target_id); } if ($processed > 0) { update_post_meta($post_id, '_aismart_translation_queued', 1); } return $processed > 0; } function aismart_translate_translatepress_native($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return false; } if (!defined('TRP_PLUGIN_VERSION')) { aismart_queue_log('Native TranslatePress fallback unavailable: plugin inactive'); return false; } $post = get_post($post_id); if (!$post || $post->post_type !== 'post' || $post->post_status !== 'publish') { aismart_queue_log('Native TranslatePress fallback skip: post not eligible post_id=' . $post_id); return false; } $settings = get_option('trp_settings', []); $langs = isset($settings['translation-languages']) && is_array($settings['translation-languages']) ? array_values(array_filter(array_map('strval', $settings['translation-languages']))) : []; if (empty($langs)) { aismart_queue_log('Native TranslatePress fallback skip: no languages configured post_id=' . $post_id); return false; } $default_lang = isset($settings['default-language']) ? (string) $settings['default-language'] : (string) get_locale(); if ($default_lang === '') { $default_lang = (string) $langs[0]; } $translation_store = []; $processed = 0; foreach ($langs as $lang) { $lang = trim((string) $lang); if ($lang === '' || $lang === $default_lang) { continue; } $source_title = aismart_normalize_post_title((string) $post->post_title); $translated_title = aismart_translate_text_google($source_title, $lang, $default_lang, false); $translated_title = aismart_cleanup_translated_plain_text($translated_title); $translated_title = aismart_normalize_post_title($translated_title); $translated_excerpt = aismart_translate_text_google((string) $post->post_excerpt, $lang, $default_lang, false); $translated_excerpt = aismart_cleanup_translated_plain_text($translated_excerpt); $translated_content = aismart_translate_post_content_google((string) $post->post_content, $lang, $default_lang); update_post_meta($post_id, '_aismart_trp_translation_' . $lang . '_title', $translated_title); update_post_meta($post_id, '_aismart_trp_translation_' . $lang . '_excerpt', $translated_excerpt); update_post_meta($post_id, '_aismart_trp_translation_' . $lang . '_content', $translated_content); $translation_store[$lang] = [ 'title' => $translated_title, 'excerpt' => $translated_excerpt, 'content' => $translated_content, 'updated_at' => gmdate('c'), ]; $processed++; } if ($processed <= 0) { aismart_queue_log('Native TranslatePress fallback produced no target languages post_id=' . $post_id); return false; } update_post_meta($post_id, '_aismart_trp_translations', $translation_store); update_post_meta($post_id, '_aismart_translation_queued', 1); update_post_meta($post_id, '_aismart_translation_queue_last_at', gmdate('c')); aismart_queue_log('Native TranslatePress fallback translated post_id=' . $post_id . ' languages=' . $processed); return true; } function aismart_translate_polylang_native($post_id) { $post_id = (int) $post_id; if ($post_id <= 0) { return false; } if (!function_exists('pll_languages_list') || !function_exists('pll_get_post_language') || !function_exists('pll_get_post_translations')) { aismart_queue_log('Native Polylang fallback unavailable: Polylang APIs missing'); return false; } $post = get_post($post_id); if (!$post || $post->post_type !== 'post' || $post->post_status !== 'publish') { aismart_queue_log('Native Polylang fallback skip: post not eligible post_id=' . $post_id); return false; } $source_thumbnail_id = (int) get_post_thumbnail_id($post_id); $langs = (array) pll_languages_list(['fields' => 'slug']); if (empty($langs)) { aismart_queue_log('Native Polylang fallback skip: no languages'); return false; } $source_lang = (string) pll_get_post_language($post_id, 'slug'); if ($source_lang === '' && function_exists('pll_default_language')) { $source_lang = (string) pll_default_language('slug'); } if ($source_lang === '') { $source_lang = (string) $langs[0]; } if (function_exists('pll_set_post_language')) { pll_set_post_language($post_id, $source_lang); } $translations = (array) pll_get_post_translations($post_id); if (empty($translations[$source_lang])) { $translations[$source_lang] = $post_id; } foreach ($langs as $lang) { $lang = sanitize_key((string) $lang); if ($lang === '' || $lang === $source_lang) { continue; } $target_id = isset($translations[$lang]) ? (int) $translations[$lang] : 0; if ($target_id <= 0) { $existing_candidates = get_posts([ 'post_type' => 'post', 'post_status' => ['publish', 'draft', 'pending', 'future', 'private'], 'numberposts' => 1, 'orderby' => 'ID', 'order' => 'ASC', 'meta_query' => [ [ 'key' => '_aismart_source_post_id', 'value' => $post_id, 'compare' => '=', 'type' => 'NUMERIC', ], [ 'key' => '_aismart_target_lang', 'value' => $lang, 'compare' => '=', ], ], 'fields' => 'ids', ]); if (!empty($existing_candidates)) { $target_id = (int) $existing_candidates[0]; } } if ($target_id <= 0) { if (has_action('transition_post_status', 'aismart_auto_translate_on_publish')) { remove_action('transition_post_status', 'aismart_auto_translate_on_publish', 10); } $target_id = wp_insert_post([ 'post_type' => 'post', 'post_status' => 'publish', 'post_author' => (int) $post->post_author, 'post_title' => (string) $post->post_title, 'post_excerpt' => (string) $post->post_excerpt, 'post_content' => (string) $post->post_content, ], true); add_action('transition_post_status', 'aismart_auto_translate_on_publish', 10, 3); if (is_wp_error($target_id) || (int) $target_id <= 0) { aismart_queue_log('Native Polylang fallback: failed to create translation post for lang=' . $lang); continue; } update_post_meta((int) $target_id, '_aismart_source_post_id', (int) $post_id); update_post_meta((int) $target_id, '_aismart_target_lang', $lang); } if (function_exists('pll_set_post_language')) { pll_set_post_language((int) $target_id, $lang); } $source_title = aismart_normalize_post_title((string) $post->post_title); $translated_title = aismart_translate_text_google($source_title, $lang, $source_lang); $translated_title = aismart_cleanup_translated_plain_text($translated_title); $translated_title = aismart_normalize_post_title($translated_title); $translated_excerpt = aismart_translate_text_google((string) $post->post_excerpt, $lang, $source_lang); $translated_excerpt = aismart_cleanup_translated_plain_text($translated_excerpt); $translated_content = aismart_translate_post_content_google((string) $post->post_content, $lang, $source_lang); wp_update_post([ 'ID' => (int) $target_id, 'post_status' => 'publish', 'post_title' => $translated_title, 'post_excerpt' => $translated_excerpt, 'post_content' => $translated_content, ]); $category_ids = wp_get_post_categories($post_id); if (!empty($category_ids)) { wp_set_post_categories((int) $target_id, $category_ids, false); } $tags = wp_get_post_tags($post_id, ['fields' => 'names']); if (!empty($tags)) { wp_set_post_tags((int) $target_id, $tags, false); } if ($source_thumbnail_id > 0) { set_post_thumbnail((int) $target_id, $source_thumbnail_id); } $translations[$lang] = (int) $target_id; aismart_queue_log('Native Polylang fallback translated lang=' . $lang . ' target_id=' . (int) $target_id); } if (function_exists('pll_save_post_translations')) { pll_save_post_translations($translations); } update_post_meta($post_id, '_aismart_translation_queued', 1); return true; } function aismart_translate_text_google($text, $target_lang, $source_lang, $preserve_html = false) { $text = trim((string) $text); if ($text === "") { return ""; } $tl = aismart_normalize_translate_lang($target_lang); $sl = aismart_normalize_translate_lang($source_lang); $payload = [ "text" => $text, "target_lang" => $tl, "source_lang" => $sl, ]; if ($preserve_html) { $payload["format"] = "html"; } $extract_translation = static function ($result) { if (!is_array($result)) { return ""; } foreach (["translated_text", "translation", "text", "result"] as $key) { if (!empty($result[$key]) && is_string($result[$key])) { return (string) $result[$key]; } } if (isset($result["data"]) && is_array($result["data"])) { foreach (["translated_text", "translation", "text", "result"] as $key) { if (!empty($result["data"][$key]) && is_string($result["data"][$key])) { return (string) $result["data"][$key]; } } } return ""; }; $is_translated_value = static function ($translated, $original, $target, $source) { $translated = trim((string) $translated); $original = trim((string) $original); if ($translated === "") { return false; } $target = strtolower(trim((string) $target)); $source = strtolower(trim((string) $source)); if ($target !== "" && $source !== "" && $target !== $source) { if (aismart_normalize_compare_text($translated) === aismart_normalize_compare_text($original)) { return false; } } return true; }; $try_public_google = static function ($original, $target, $source) { $query = [ "client" => "gtx", "sl" => $source !== "" ? $source : "auto", "tl" => $target !== "" ? $target : "en", "dt" => "t", "q" => $original, ]; $url = add_query_arg($query, "https://translate.googleapis.com/translate_a/single"); $resp = wp_remote_get($url, ["timeout" => 20]); if (is_wp_error($resp)) { return ""; } $json = json_decode((string) wp_remote_retrieve_body($resp), true); if (!is_array($json) || empty($json[0]) || !is_array($json[0])) { return ""; } $parts = []; foreach ($json[0] as $row) { if (is_array($row) && isset($row[0]) && is_string($row[0])) { $parts[] = $row[0]; } } return trim(implode("", $parts)); }; $public_first = $try_public_google($text, $tl, $sl); if ($is_translated_value($public_first, $text, $tl, $sl)) { return $public_first; } if (function_exists("aismart_plugin_api_request")) { $api_result = aismart_plugin_api_request("POST", "api/plugin/v1/translation/translate", $payload, true); if (!is_wp_error($api_result)) { $translated = $extract_translation($api_result); if ($is_translated_value($translated, $text, $tl, $sl)) { return $translated; } } } $api_base = untrailingslashit((string) aismart_plugin_get_api_base()); $response = wp_remote_post($api_base . "/api/plugin/v1/translation/translate", [ "timeout" => 30, "headers" => [ "Content-Type" => "application/json", ], "body" => wp_json_encode($payload), ]); if (!is_wp_error($response)) { $json = json_decode(wp_remote_retrieve_body($response), true); $translated = $extract_translation($json); if ($is_translated_value($translated, $text, $tl, $sl)) { return $translated; } } $fallback = $try_public_google($text, $tl, $sl); if ($is_translated_value($fallback, $text, $tl, $sl)) { return $fallback; } return $text; } function aismart_translate_post_content_google($content, $target_lang, $source_lang) { $content = (string) $content; if (trim($content) === '') { return ''; } if (strpos($content, '<') !== false && strpos($content, '>') !== false) { $parts = preg_split('/(<[^>]+>)/u', $content, -1, PREG_SPLIT_DELIM_CAPTURE); if (is_array($parts) && !empty($parts)) { $translated = ''; foreach ($parts as $part) { if ($part === '') { continue; } if (preg_match('/^<[^>]+>$/u', $part)) { $translated .= $part; continue; } if (trim($part) === '') { $translated .= $part; continue; } $leading = ''; $trailing = ''; if (preg_match('/^\s+/u', $part, $leadingMatch)) { $leading = (string) ($leadingMatch[0] ?? ''); } if (preg_match('/\s+$/u', $part, $trailingMatch)) { $trailing = (string) ($trailingMatch[0] ?? ''); } $core = trim((string) $part); if ($core === '') { $translated .= $part; continue; } $translated_core = aismart_translate_text_google($core, $target_lang, $source_lang, false); $translated .= $leading . ($translated_core !== '' ? $translated_core : $core) . $trailing; } } else { $translated = aismart_translate_text_google($content, $target_lang, $source_lang, true); } } else { $translated = aismart_translate_text_google($content, $target_lang, $source_lang, false); } $translated = aismart_cleanup_translated_post_content($translated); return $translated !== '' ? $translated : $content; } function aismart_cleanup_translated_plain_text($text) { $text = trim((string) $text); if ($text === '') { return ''; } $text = preg_replace('/^\s*```(?:html|markdown|md|text)?\s*/iu', '', $text, 1); $text = preg_replace('/\s*```\s*$/u', '', (string) $text, 1); $text = preg_replace('/^\s*`?html`?\s*$/iu', '', (string) $text); $text = preg_replace('/\R{3,}/u', "\n\n", (string) $text); $lines = preg_split('/\R/u', (string) $text); if (is_array($lines) && count($lines) >= 2) { $first = trim((string) $lines[0]); $second = trim((string) $lines[1]); if ($first !== '' && $second !== '' && $first === $second) { array_shift($lines); $text = implode("\n", $lines); } } return trim((string) $text); } function aismart_cleanup_translated_post_content($content) { $original = (string) $content; $content = trim($original); if ($content === '') { return ''; } if (preg_match('/]*>(.*?)<\/body>/is', $content, $bodyMatch) && isset($bodyMatch[1])) { $content = trim((string) $bodyMatch[1]); } $content = preg_replace('/]*>.*?<\/head>/is', '', (string) $content); $content = preg_replace('/