'AI Smart Content',
'description' => 'Enterprise text generation, bulk SEO articles, and advanced translations.',
'type' => 'ai_provider',
'plugin' => [
'file' => plugin_basename(__FILE__),
'is_active' => '__return_true',
],
'authentication' => [
'method' => 'api_key',
'credentials_url' => aismart_token_wp7_connector_credentials_url(),
'setting_name' => aismart_token_wp7_connector_setting_name(),
'constant_name' => aismart_token_wp7_connector_key_constant_name(),
'env_var_name' => aismart_token_wp7_connector_key_constant_name(),
],
];
if ($logo_url !== '') {
$args['logo_url'] = $logo_url;
}
if ($registry->is_registered($id)) {
$existing = $registry->unregister($id);
if (is_array($existing) && empty($args['logo_url']) && !empty($existing['logo_url'])) {
$args['logo_url'] = (string) $existing['logo_url'];
}
}
$registry->register($id, $args);
}
add_action('wp_connectors_init', 'aismart_token_wp7_register_connector', 30, 1);
function aismart_token_wp7_model_capabilities_from_id($model_id) {
$model_id = strtolower(trim((string) $model_id));
$caps = [];
$contains_any = static function ($haystack, $needles) {
foreach ($needles as $needle) {
if ($needle !== '' && strpos($haystack, $needle) !== false) {
return true;
}
}
return false;
};
if ($contains_any($model_id, ['embed', 'embedding'])) {
$caps[] = \WordPress\AiClient\Providers\Models\Enums\CapabilityEnum::embeddingGeneration();
} elseif ($contains_any($model_id, ['image', 'dall', 'sdxl', 'stable-diffusion', 'flux', 'vision-image'])) {
$caps[] = \WordPress\AiClient\Providers\Models\Enums\CapabilityEnum::imageGeneration();
} elseif ($contains_any($model_id, ['video', 'sora'])) {
$caps[] = \WordPress\AiClient\Providers\Models\Enums\CapabilityEnum::videoGeneration();
} elseif ($contains_any($model_id, ['tts', 'text-to-speech'])) {
$caps[] = \WordPress\AiClient\Providers\Models\Enums\CapabilityEnum::textToSpeechConversion();
} elseif ($contains_any($model_id, ['speech', 'audio'])) {
$caps[] = \WordPress\AiClient\Providers\Models\Enums\CapabilityEnum::speechGeneration();
} else {
$caps[] = \WordPress\AiClient\Providers\Models\Enums\CapabilityEnum::textGeneration();
$caps[] = \WordPress\AiClient\Providers\Models\Enums\CapabilityEnum::chatHistory();
}
return array_values($caps);
}
function aismart_token_wp7_resolve_api_key() {
$preferred = aismart_token_wp7_connector_key_constant_name();
$legacy = 'AISMARTCONTENT_API_KEY';
$env = getenv($preferred);
if ($env !== false && trim((string) $env) !== '') {
return trim((string) $env);
}
if (defined($preferred)) {
$const = constant($preferred);
if (is_string($const) && trim($const) !== '') {
return trim($const);
}
}
$legacy_env = getenv($legacy);
if ($legacy_env !== false && trim((string) $legacy_env) !== '') {
return trim((string) $legacy_env);
}
if (defined($legacy)) {
$legacy_const = constant($legacy);
if (is_string($legacy_const) && trim($legacy_const) !== '') {
return trim($legacy_const);
}
}
$db_key = get_option(aismart_token_wp7_connector_setting_name(), '');
if (is_string($db_key) && trim($db_key) !== '') {
return trim($db_key);
}
$fallback_key = get_option('connectors_ai_aismartcontent_api_key', '');
if (is_string($fallback_key) && trim($fallback_key) !== '') {
return trim($fallback_key);
}
return '';
}
function aismart_token_wp7_register_ai_provider() {
if (!aismart_token_wp7_connectors_available()) {
return;
}
if (!class_exists('AISmart_Token_WP7_OpenAI_Provider')) {
return;
}
try {
$registry = \WordPress\AiClient\AiClient::defaultRegistry();
$provider_id = aismart_token_wp7_connector_id();
if (!$registry->hasProvider($provider_id)) {
$registry->registerProvider('AISmart_Token_WP7_OpenAI_Provider');
}
} catch (\Throwable $e) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('[AISmart] Token WP7 provider registration failed: ' . $e->getMessage());
}
}
}
add_action('init', 'aismart_token_wp7_register_ai_provider', 5);
function aismart_token_wp7_apply_provider_authentication() {
if (!aismart_token_wp7_connectors_available()) {
return;
}
$api_key = aismart_token_wp7_resolve_api_key();
if ($api_key === '') {
return;
}
if (!class_exists('\\WordPress\\AiClient\\Providers\\Http\\DTO\\ApiKeyRequestAuthentication')) {
return;
}
try {
$registry = \WordPress\AiClient\AiClient::defaultRegistry();
$provider_id = aismart_token_wp7_connector_id();
if (!$registry->hasProvider($provider_id)) {
return;
}
$registry->setProviderRequestAuthentication(
$provider_id,
new \WordPress\AiClient\Providers\Http\DTO\ApiKeyRequestAuthentication($api_key)
);
} catch (\Throwable $e) {
if (defined('WP_DEBUG') && WP_DEBUG) {
error_log('[AISmart] Token WP7 provider auth sync failed: ' . $e->getMessage());
}
}
}
add_action('init', 'aismart_token_wp7_apply_provider_authentication', 21);
if (
!class_exists('AISmart_Token_WP7_OpenAI_Provider')
&& class_exists('\\WordPress\\AiClient\\Providers\\ApiBasedImplementation\\AbstractApiProvider')
&& class_exists('\\WordPress\\AiClient\\Providers\\OpenAiCompatibleImplementation\\AbstractOpenAiCompatibleTextGenerationModel')
&& class_exists('\\WordPress\\AiClient\\Providers\\OpenAiCompatibleImplementation\\AbstractOpenAiCompatibleModelMetadataDirectory')
) {
class AISmart_Token_WP7_OpenAI_Text_Model extends \WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleTextGenerationModel {
protected function createRequest(\WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum $method, string $path, array $headers = [], $data = null): \WordPress\AiClient\Providers\Http\DTO\Request {
return new \WordPress\AiClient\Providers\Http\DTO\Request(
$method,
AISmart_Token_WP7_OpenAI_Provider::url($path),
$headers,
$data,
$this->getRequestOptions()
);
}
}
class AISmart_Token_WP7_OpenAI_Model_Metadata_Directory extends \WordPress\AiClient\Providers\OpenAiCompatibleImplementation\AbstractOpenAiCompatibleModelMetadataDirectory {
protected function createRequest(\WordPress\AiClient\Providers\Http\Enums\HttpMethodEnum $method, string $path, array $headers = [], $data = null): \WordPress\AiClient\Providers\Http\DTO\Request {
return new \WordPress\AiClient\Providers\Http\DTO\Request(
$method,
AISmart_Token_WP7_OpenAI_Provider::url($path),
$headers,
$data
);
}
protected function parseResponseToModelMetadataList(\WordPress\AiClient\Providers\Http\DTO\Response $response): array {
$response_data = $response->getData();
if (!is_array($response_data) || !isset($response_data['data']) || !is_array($response_data['data'])) {
throw \WordPress\AiClient\Providers\Http\Exception\ResponseException::fromMissingData('AISmart', 'data');
}
$models = [];
foreach ($response_data['data'] as $item) {
if (!is_array($item) || !isset($item['id']) || !is_string($item['id'])) {
continue;
}
$model_id = sanitize_text_field((string) $item['id']);
if ($model_id === '') {
continue;
}
$model_name = isset($item['name']) && is_string($item['name'])
? sanitize_text_field((string) $item['name'])
: $model_id;
$models[] = new \WordPress\AiClient\Providers\Models\DTO\ModelMetadata(
$model_id,
$model_name,
aismart_token_wp7_model_capabilities_from_id($model_id),
[]
);
}
if (empty($models)) {
$fallback_id = aismart_token_wp7_default_text_model_id();
$models[] = new \WordPress\AiClient\Providers\Models\DTO\ModelMetadata(
$fallback_id,
$fallback_id,
[
\WordPress\AiClient\Providers\Models\Enums\CapabilityEnum::textGeneration(),
\WordPress\AiClient\Providers\Models\Enums\CapabilityEnum::chatHistory(),
],
[]
);
}
return $models;
}
}
class AISmart_Token_WP7_OpenAI_Provider extends \WordPress\AiClient\Providers\ApiBasedImplementation\AbstractApiProvider {
protected static function baseUrl(): string {
return aismart_token_wp7_ai_client_base_url();
}
protected static function createModel(\WordPress\AiClient\Providers\Models\DTO\ModelMetadata $modelMetadata, \WordPress\AiClient\Providers\DTO\ProviderMetadata $providerMetadata): \WordPress\AiClient\Providers\Models\Contracts\ModelInterface {
return new AISmart_Token_WP7_OpenAI_Text_Model($modelMetadata, $providerMetadata);
}
protected static function createProviderMetadata(): \WordPress\AiClient\Providers\DTO\ProviderMetadata {
$logo_path = aismart_token_wp7_connector_logo_path();
return new \WordPress\AiClient\Providers\DTO\ProviderMetadata(
aismart_token_wp7_connector_id(),
'AI Smart Content',
\WordPress\AiClient\Providers\Enums\ProviderTypeEnum::cloud(),
aismart_token_wp7_connector_credentials_url(),
\WordPress\AiClient\Providers\Http\Enums\RequestAuthenticationMethod::apiKey(),
'Enterprise text generation, bulk SEO articles, and advanced translations.',
$logo_path !== '' ? $logo_path : null
);
}
protected static function createProviderAvailability(): \WordPress\AiClient\Providers\Contracts\ProviderAvailabilityInterface {
return new \WordPress\AiClient\Providers\ApiBasedImplementation\ListModelsApiBasedProviderAvailability(
static::modelMetadataDirectory()
);
}
protected static function createModelMetadataDirectory(): \WordPress\AiClient\Providers\Contracts\ModelMetadataDirectoryInterface {
return new AISmart_Token_WP7_OpenAI_Model_Metadata_Directory();
}
}
}
function aismart_dependency_paths() {
return [
'autoload' => __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 = 'Manual command (SSH/Terminal):
'
. '' . esc_html($data['manual_command']) . '
'
. 'Beginner step-by-step (run one line at a time):
'
. '' . $steps_html . ' ';
}
$missing = $data['missing_files_html'] !== '' ? 'Missing files:
' . $data['missing_files_html'] . ' ' : '';
$output = $data['output_html'];
$no_ssh_help = '';
if (!empty($data['show_no_ssh_help']) && $data['manual_command'] !== '') {
$support_text = "Hello Hosting Support,\n\nPlease run this command on my WordPress server to install plugin dependencies:\n" . $data['manual_command'] . "\n\nThen please confirm when complete.\nThank you.";
$no_ssh_help = ''
. '
No SSH access? Use this beginner path:
'
. '
'
. 'Open your hosting control panel (usually cPanel or Plesk). '
. 'Try Terminal and run the manual command above. '
. 'If Terminal is not available, send this message to hosting support: '
. ' '
. '
'
. '
';
}
return ''
. '
' . esc_html($data['title']) . ' '
. '
' . esc_html($data['intro']) . '
'
. '
Quick steps for admin:
'
. '
'
. 'Click the button below to run the automatic fix. '
. 'Wait until the page finishes loading. '
. 'Activate AISmart Content Studio again if needed. '
. ' '
. $button_url
. $missing
. $manual
. $no_ssh_help
. $output
. '
';
}
function aismart_flash_key() {
$user_id = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0;
return 'aismart_recovery_flash_' . $user_id;
}
function aismart_activation_redirect_flag_key() {
$user_id = function_exists('get_current_user_id') ? (int) get_current_user_id() : 0;
return 'aismart_activation_redirect_' . $user_id;
}
function aismart_mark_activation_redirect() {
set_transient(aismart_activation_redirect_flag_key(), '1', 180);
}
function aismart_consume_activation_redirect_flag() {
$key = aismart_activation_redirect_flag_key();
$value = get_transient($key);
if ($value === false) {
return false;
}
delete_transient($key);
return true;
}
function aismart_set_recovery_flash($payload) {
set_transient(aismart_flash_key(), $payload, 180);
}
function aismart_get_recovery_flash() {
$payload = get_transient(aismart_flash_key());
if ($payload !== false) {
delete_transient(aismart_flash_key());
}
return is_array($payload) ? $payload : null;
}
function aismart_shell_function_available($name) {
if (!function_exists($name)) {
return false;
}
$disabled = ini_get('disable_functions');
if (!is_string($disabled) || $disabled === '') {
return true;
}
$disabled_list = array_map('trim', explode(',', $disabled));
return !in_array($name, $disabled_list, true);
}
function aismart_detect_composer_binary() {
$candidates = [];
$path = getenv('PATH');
if (is_string($path) && $path !== '') {
foreach (explode(PATH_SEPARATOR, $path) as $dir) {
$dir = trim((string) $dir);
if ($dir !== '') {
$candidates[] = rtrim($dir, '/\\') . '/composer';
}
}
}
$candidates[] = '/opt/cpanel/composer/bin/composer';
$candidates[] = '/usr/local/bin/composer';
$candidates[] = '/usr/bin/composer';
$candidates[] = '/bin/composer';
$candidates[] = __DIR__ . '/aismart-core/composer.phar';
$candidates = array_values(array_unique($candidates));
foreach ($candidates as $candidate) {
if (is_string($candidate) && $candidate !== '' && file_exists($candidate) && is_executable($candidate)) {
$is_phar = substr($candidate, -5) === '.phar';
return [
'found' => true,
'binary' => $candidate,
'is_phar' => $is_phar,
'checked' => $candidates,
];
}
}
return [
'found' => false,
'binary' => '',
'is_phar' => false,
'checked' => $candidates,
];
}
function aismart_build_composer_missing_help_html($core_dir) {
$core_dir = rtrim((string) $core_dir, '/');
$manual = aismart_get_recovery_command();
$commands = [
'cd ' . $core_dir,
'/opt/cpanel/composer/bin/composer --version',
'/opt/cpanel/composer/bin/composer install --no-dev --optimize-autoloader --no-interaction',
'',
'# If the cPanel Composer path above is unavailable, ask hosting/dev to install Composer and run:',
$manual,
];
$text = implode("\n", $commands);
return ''
. '
Composer is missing in server shell.
'
. '
Use these copy-ready commands in cPanel Terminal:
'
. '
'
. '
';
}
function aismart_build_shell_disabled_help_html($core_dir) {
$core_dir = rtrim((string) $core_dir, '/');
$manual = aismart_get_recovery_command();
$disabled = (string) ini_get('disable_functions');
$disabled_display = $disabled !== '' ? $disabled : '(none listed by php.ini)';
$commands = [
'cd ' . $core_dir,
'/opt/cpanel/composer/bin/composer install --no-dev --optimize-autoloader --no-interaction',
'',
'# If cPanel Terminal is unavailable, send this exact command to hosting/developer:',
$manual,
];
$text = implode("\n", $commands);
return ''
. '
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:
'
. '
'
. '
';
}
function aismart_run_composer_install() {
$core_dir = __DIR__ . '/aismart-core';
if (!is_dir($core_dir)) {
return [
'success' => false,
'message' => 'aismart-core directory is missing.',
'output' => '',
];
}
@set_time_limit(300);
$composer = aismart_detect_composer_binary();
if (empty($composer['found'])) {
return [
'success' => false,
'error_type' => 'composer_missing',
'message' => 'Composer binary was not found on this server.',
'output' => '',
'composer_help_html' => aismart_build_composer_missing_help_html($core_dir),
];
}
$composer_bin = (string) $composer['binary'];
if (!empty($composer['is_phar'])) {
$php_bin = (defined('PHP_BINARY') && PHP_BINARY) ? escapeshellarg(PHP_BINARY) : 'php';
$composer_cmd = $php_bin . ' ' . escapeshellarg($composer_bin);
} else {
$composer_cmd = escapeshellarg($composer_bin);
}
$command = 'cd ' . escapeshellarg($core_dir) . ' && ' . $composer_cmd . ' install --no-dev --optimize-autoloader --no-interaction 2>&1';
$output = '';
$exit_code = null;
if (aismart_shell_function_available('exec')) {
$lines = [];
exec($command, $lines, $exit_code);
$output = implode("\n", $lines);
} elseif (aismart_shell_function_available('system')) {
ob_start();
system($command, $exit_code);
$output = (string) ob_get_clean();
} elseif (aismart_shell_function_available('shell_exec')) {
$result = shell_exec($command);
$output = is_string($result) ? $result : '';
} else {
return [
'success' => false,
'error_type' => 'shell_disabled',
'message' => 'Shell functions are disabled on this server. Run composer manually via SSH/Terminal.',
'output' => '',
'shell_help_html' => aismart_build_shell_disabled_help_html($core_dir),
];
}
$missing_after = aismart_missing_dependencies();
$success = empty($missing_after);
if ($success) {
return [
'success' => true,
'message' => 'Composer install completed. Dependencies are ready.',
'output' => trim($output),
'exit_code' => $exit_code,
];
}
return [
'success' => false,
'message' => 'Composer command finished but dependencies are still missing.',
'output' => trim($output),
'exit_code' => $exit_code,
];
}
function aismart_recovery_failure_intro($result) {
$base = 'Automatic fix could not complete on this server. Please run the manual command below and then activate AISmart Content Studio again.';
$message = isset($result['message']) ? strtolower((string) $result['message']) : '';
$output = isset($result['output']) ? strtolower((string) $result['output']) : '';
$full = trim($message . "\n" . $output);
if (strpos($full, 'shell functions are disabled') !== false || strpos($full, 'disable_functions') !== false) {
return 'Automatic fix cannot run because shell execution is disabled on this server. Please use the manual command below. If you are a beginner, copy the support message box and send it to your hosting provider or developer.';
}
if (strpos($full, 'composer: not found') !== false || strpos($full, 'command not found') !== false) {
return 'Automatic fix cannot run because Composer is not available in server shell PATH. Please run the manual command below in your hosting terminal, or ask hosting/developer to run it for you.';
}
if (strpos($full, 'permission denied') !== false) {
return 'Automatic fix failed due to server permission restrictions. Please run the manual command below using an account with correct permissions, or ask hosting/developer to run it.';
}
return $base;
}
function aismart_maybe_run_composer_from_activation_request() {
if (!function_exists('is_admin') || !is_admin()) {
return;
}
if (!function_exists('wp_get_current_user') || !function_exists('current_user_can') || !current_user_can('activate_plugins')) {
return;
}
if (!isset($_GET['aismart_run_composer_install'])) {
return;
}
$action = isset($_GET['action']) ? sanitize_key((string) wp_unslash($_GET['action'])) : '';
$plugin = isset($_GET['plugin']) ? (string) wp_unslash($_GET['plugin']) : '';
if ($action !== 'activate' || $plugin !== plugin_basename(__FILE__)) {
return;
}
check_admin_referer('activate-plugin_' . plugin_basename(__FILE__));
$result = aismart_run_composer_install();
if (!empty($result['success'])) {
set_transient('aismart_post_recovery_redirect', '1', 120);
return;
}
$cmd = aismart_get_recovery_command();
$install_url = aismart_get_activation_url(true);
$output = isset($result['output']) && $result['output'] !== '' ? '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) . ' ';
}
wp_die(
aismart_render_recovery_panel_html([
'title' => 'Automatic fix could not run on this server',
'intro' => $message,
'button_url' => $show_retry_button ? $install_url : '',
'button_label' => 'Try Automatic Fix Again',
'manual_command' => $cmd,
'missing_files_html' => $details,
'output_html' => $output_html,
]),
'AISmart Content Studio Activation Error',
['back_link' => true]
);
exit;
}
aismart_maybe_run_composer_from_activation_request();
function aismart_maybe_redirect_after_recovery_activation() {
if (!is_admin() || !current_user_can('activate_plugins')) {
return;
}
if (defined('DOING_AJAX') && DOING_AJAX) {
return;
}
$flag = get_transient('aismart_post_recovery_redirect');
if ($flag !== '1') {
return;
}
delete_transient('aismart_post_recovery_redirect');
$current_page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
if ($current_page === 'aismart-token') {
return;
}
wp_safe_redirect(admin_url('admin.php?page=aismart-token&setup=recovered'));
exit;
}
add_action('admin_init', 'aismart_maybe_redirect_after_recovery_activation', 99);
function aismart_capture_plugin_activation_redirect($plugin, $network_wide) {
if ($plugin !== plugin_basename(__FILE__)) {
return;
}
// Keep network activation behavior unchanged; only redirect for normal admin activation.
if (!empty($network_wide)) {
return;
}
if (!current_user_can('activate_plugins')) {
return;
}
aismart_mark_activation_redirect();
}
add_action('activated_plugin', 'aismart_capture_plugin_activation_redirect', 10, 2);
function aismart_maybe_redirect_after_normal_activation() {
if (!is_admin() || !current_user_can('activate_plugins')) {
return;
}
if (defined('DOING_AJAX') && DOING_AJAX) {
return;
}
if (!aismart_consume_activation_redirect_flag()) {
return;
}
if (!aismart_dependency_guard()) {
return;
}
$current_page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
if (in_array($current_page, ['aismart-token', 'aismart-token-onboarding'], true)) {
return;
}
wp_safe_redirect(admin_url('admin.php?page=aismart-token-onboarding&setup=activated'));
exit;
}
add_action('admin_init', 'aismart_maybe_redirect_after_normal_activation', 100);
function aismart_render_recovery_flash_notice() {
if (!current_user_can('activate_plugins')) {
return;
}
if (!isset($_GET['aismart_recovery'])) {
return;
}
$flash = aismart_get_recovery_flash();
if (empty($flash)) {
return;
}
$output_html = isset($flash['output_html']) ? (string) $flash['output_html'] : '';
$panel = aismart_render_recovery_panel_html([
'title' => 'Automatic fix could not complete',
'intro' => isset($flash['message']) ? (string) $flash['message'] : 'Please run the manual command and activate again.',
'button_url' => isset($flash['retry_url']) ? (string) $flash['retry_url'] : '',
'button_label' => 'Try Automatic Fix Again',
'manual_command' => isset($flash['manual_command']) ? (string) $flash['manual_command'] : aismart_get_recovery_command(),
'output_html' => $output_html,
]);
echo '' . $panel . '
';
}
add_action('admin_notices', 'aismart_render_recovery_flash_notice');
function aismart_render_dependency_notice() {
if (!current_user_can('activate_plugins')) {
return;
}
$missing = aismart_missing_dependencies();
if (empty($missing)) {
return;
}
$cmd = aismart_get_recovery_command();
$install_url = aismart_get_activation_url(true);
$details = '';
foreach ($missing as $path) {
$details .= '' . esc_html($path) . ' ';
}
echo '';
echo aismart_render_recovery_panel_html([
'button_url' => $install_url,
'manual_command' => $cmd,
'missing_files_html' => $details,
]);
echo '
';
}
function aismart_dependency_guard() {
static $checked = null;
if ($checked !== null) {
return $checked;
}
$missing = aismart_missing_dependencies();
if (empty($missing)) {
$checked = true;
return true;
}
error_log('AISmart Content Studio dependency guard triggered. Missing: ' . implode(', ', $missing));
add_action('admin_notices', 'aismart_render_dependency_notice');
// Keep plugin active and show recovery guidance only.
// WP.org policy requires no unsolicited activation-state changes.
$checked = false;
return false;
}
register_activation_hook(__FILE__, function () {
$env_sync = aismart_sync_embedded_env_file();
if (empty($env_sync['ok'])) {
$env_message = isset($env_sync['message']) ? (string) $env_sync['message'] : 'unknown error';
error_log('AISmart Content Studio env sync on activation failed: ' . $env_message);
}
if (!aismart_dependency_guard()) {
$cmd = aismart_get_recovery_command();
$install_url = aismart_get_activation_url(true);
$details = '';
foreach (aismart_missing_dependencies() as $path) {
$details .= '' . esc_html($path) . ' ';
}
wp_die(
aismart_render_recovery_panel_html([
'button_url' => $install_url,
'manual_command' => $cmd,
'missing_files_html' => $details,
]),
'AISmart Content Studio Activation Error',
['back_link' => true]
);
}
if (function_exists('aismart_ensure_auto_blog_cron_schedule')) {
aismart_ensure_auto_blog_cron_schedule();
}
if (function_exists('aismart_ensure_missed_schedule_repair_cron_schedule')) {
aismart_ensure_missed_schedule_repair_cron_schedule();
}
});
register_deactivation_hook(__FILE__, function () {
aismart_clear_auto_blog_cron_schedule();
aismart_clear_missed_schedule_repair_cron_schedule();
});
add_filter('cron_schedules', 'aismart_register_auto_blog_minute_interval');
function aismart_register_auto_blog_minute_interval($schedules) {
if (!is_array($schedules)) {
$schedules = [];
}
if (!isset($schedules['aismart_every_minute'])) {
$schedules['aismart_every_minute'] = [
'interval' => 60,
'display' => 'AISmart Every Minute',
];
}
return $schedules;
}
function aismart_ensure_auto_blog_cron_schedule() {
if (function_exists('wp_installing') && wp_installing()) {
return;
}
$hook = 'aismart_auto_blog_cron_event';
if (!wp_next_scheduled($hook)) {
wp_schedule_event(time() + 60, 'aismart_every_minute', $hook);
}
}
function aismart_clear_auto_blog_cron_schedule() {
$hook = 'aismart_auto_blog_cron_event';
$next = wp_next_scheduled($hook);
while ($next) {
wp_unschedule_event($next, $hook);
$next = wp_next_scheduled($hook);
}
delete_transient('aismart_auto_blog_worker_lock');
}
function aismart_ensure_missed_schedule_repair_cron_schedule() {
if (function_exists('wp_installing') && wp_installing()) {
return;
}
$hook = 'aismart_repair_missed_schedule_event';
if (!wp_next_scheduled($hook)) {
wp_schedule_event(time() + 90, 'aismart_every_minute', $hook);
}
}
function aismart_clear_missed_schedule_repair_cron_schedule() {
$hook = 'aismart_repair_missed_schedule_event';
$next = wp_next_scheduled($hook);
while ($next) {
wp_unschedule_event($next, $hook);
$next = wp_next_scheduled($hook);
}
delete_transient('aismart_missed_schedule_repair_lock');
}
add_action('init', 'aismart_ensure_auto_blog_cron_schedule', 20);
add_action('init', 'aismart_ensure_missed_schedule_repair_cron_schedule', 21);
function aismart_set_runtime_env_value($key, $value) {
$key = (string) $key;
$value = (string) $value;
if ($key === '') {
return;
}
putenv($key . '=' . $value);
$_ENV[$key] = $value;
$_SERVER[$key] = $value;
}
function aismart_parse_wp_db_host($raw_host) {
$host = trim((string) $raw_host);
$port = '';
$socket = '';
if ($host === '') {
return ['127.0.0.1', '', ''];
}
if (strpos($host, ':/') !== false) {
[$host_part, $socket_part] = explode(':', $host, 2);
$host = trim((string) $host_part);
$socket = trim((string) $socket_part);
if ($socket !== '' && $socket[0] !== '/') {
$socket = '/' . ltrim($socket, '/');
}
} elseif (preg_match('/^([^:]+):(\d+)$/', $host, $m)) {
$host = trim((string) $m[1]);
$port = trim((string) $m[2]);
}
if ($host === '') {
$host = '127.0.0.1';
}
return [$host, $port, $socket];
}
function aismart_resolve_wp_app_url() {
$url = '';
if (function_exists('home_url')) {
$url = (string) home_url('/');
}
if ($url === '' && defined('WP_HOME')) {
$url = (string) WP_HOME;
}
if ($url === '') {
$url = 'http://localhost';
}
return rtrim($url, '/');
}
function aismart_get_wp_app_env() {
if (function_exists('wp_get_environment_type')) {
$wp_env = (string) wp_get_environment_type();
if (in_array($wp_env, ['local', 'development'], true)) {
return 'local';
}
if ($wp_env === 'staging') {
return 'staging';
}
}
return 'production';
}
function aismart_generate_laravel_app_key() {
$stored_key = (string) get_option('aismart_embedded_app_key', '');
if (aismart_is_valid_laravel_app_key($stored_key)) {
return $stored_key;
}
try {
$raw = random_bytes(32);
} catch (Exception $e) {
$fallback_seed = wp_generate_password(64, true, true) . '|' . microtime(true) . '|' . wp_rand();
$raw = hash('sha256', $fallback_seed, true);
}
$key = 'base64:' . base64_encode($raw);
update_option('aismart_embedded_app_key', $key, false);
return $key;
}
function aismart_is_valid_laravel_app_key($key) {
$key = trim((string) $key);
if ($key === '') {
return false;
}
if (strpos($key, 'base64:') === 0) {
$decoded = base64_decode(substr($key, 7), true);
return is_string($decoded) && strlen($decoded) === 32;
}
return strlen($key) === 32;
}
function aismart_resolve_env_value_from_content($content, $key) {
$content = (string) $content;
$key = (string) $key;
if ($content === '' || $key === '') {
return '';
}
if (preg_match('/^\s*' . preg_quote($key, '/') . '\s*=\s*([^\r\n]*)$/mi', $content, $m)) {
return trim((string) $m[1], " \t\n\r\0\x0B\"'");
}
return '';
}
function aismart_quote_env_value($value) {
$value = (string) $value;
if ($value === '') {
return '';
}
if (preg_match('/[\s#"\'\\$=]/', $value)) {
return '"' . addcslashes($value, "\\\"$") . '"';
}
return $value;
}
function aismart_upsert_env_content_value($content, $key, $value) {
$content = (string) $content;
$key = (string) $key;
$line = $key . '=' . aismart_quote_env_value($value);
$pattern = '/^\s*' . preg_quote($key, '/') . '\s*=\s*[^\r\n]*$/mi';
if (preg_match($pattern, $content)) {
return (string) preg_replace($pattern, $line, $content, 1);
}
$content = rtrim($content, "\r\n");
if ($content !== '') {
$content .= "\n";
}
$content .= $line . "\n";
return $content;
}
function aismart_get_embedded_env_file_path() {
return __DIR__ . '/aismart-core/.env';
}
function aismart_persist_embedded_env_file($content) {
$env_file = aismart_get_embedded_env_file_path();
$env_dir = dirname($env_file);
if (!is_dir($env_dir)) {
return ['ok' => false, 'message' => 'aismart-core directory is missing.'];
}
$target_exists = file_exists($env_file);
if ($target_exists && !is_writable($env_file)) {
return ['ok' => false, 'message' => '.env file is not writable.'];
}
if (!$target_exists && !is_writable($env_dir)) {
return ['ok' => false, 'message' => 'aismart-core directory is not writable.'];
}
$written = @file_put_contents($env_file, (string) $content, LOCK_EX);
if ($written === false) {
return ['ok' => false, 'message' => 'Failed to write .env file.'];
}
return ['ok' => true, 'message' => '.env file synchronized from WordPress config.'];
}
function aismart_sync_embedded_env_file() {
$core_dir = __DIR__ . '/aismart-core';
$env_file = $core_dir . '/.env';
if (!is_dir($core_dir)) {
return ['ok' => false, 'message' => 'aismart-core directory is missing.'];
}
$content = '';
if (file_exists($env_file) && is_readable($env_file)) {
$loaded = @file_get_contents($env_file);
if ($loaded === false) {
return ['ok' => false, 'message' => 'Failed to read .env file.'];
}
$content = (string) $loaded;
}
$existing_app_key = aismart_resolve_env_value_from_content($content, 'APP_KEY');
$app_key = aismart_is_valid_laravel_app_key($existing_app_key)
? $existing_app_key
: aismart_generate_laravel_app_key();
[$host, $port, $socket] = defined('DB_HOST')
? aismart_parse_wp_db_host((string) DB_HOST)
: ['127.0.0.1', '3306', ''];
$values = [
'APP_NAME' => 'AISmart',
'APP_ENV' => aismart_get_wp_app_env(),
'APP_KEY' => $app_key,
'APP_DEBUG' => (defined('WP_DEBUG') && WP_DEBUG) ? 'true' : 'false',
'APP_URL' => aismart_resolve_wp_app_url(),
'DB_CONNECTION' => 'mysql',
'DB_HOST' => $host,
'DB_PORT' => $port !== '' ? $port : '3306',
'DB_DATABASE' => defined('DB_NAME') ? (string) DB_NAME : '',
'DB_USERNAME' => defined('DB_USER') ? (string) DB_USER : '',
'DB_PASSWORD' => defined('DB_PASSWORD') ? (string) DB_PASSWORD : '',
'DB_SOCKET' => $socket,
'SESSION_DRIVER' => 'file',
'QUEUE_CONNECTION' => 'sync',
'CACHE_STORE' => 'file',
'FILESYSTEM_DISK' => 'local',
];
foreach ($values as $key => $value) {
$content = aismart_upsert_env_content_value($content, $key, $value);
}
update_option('aismart_embedded_env_snapshot', $content, false);
$persist = aismart_persist_embedded_env_file($content);
if (empty($persist['ok'])) {
return [
'ok' => false,
'message' => isset($persist['message']) ? (string) $persist['message'] : 'Failed to persist .env file.',
'snapshot_saved' => true,
];
}
return ['ok' => true, 'message' => isset($persist['message']) ? (string) $persist['message'] : '.env synchronized.'];
}
add_action('admin_post_aismart_reconfigure_embedded_env', 'aismart_reconfigure_embedded_env_action');
function aismart_reconfigure_embedded_env_action() {
if (!current_user_can('manage_options')) {
wp_die(esc_html__('Unauthorized', 'aismart-content-studio'), 403);
}
check_admin_referer('aismart_reconfigure_embedded_env');
$default_return = admin_url('admin.php?page=aismart-token');
$requested_return = isset($_REQUEST['return']) ? wp_unslash((string) $_REQUEST['return']) : $default_return;
$return_url = wp_validate_redirect($requested_return, $default_return);
$sync = aismart_sync_embedded_env_file();
if (!empty($sync['ok'])) {
wp_safe_redirect(add_query_arg('env_reconfigured', '1', $return_url));
exit;
}
$onboarding_url = add_query_arg(
[
'page' => 'aismart-token-onboarding',
'setup' => 'env-reconfigure-failed',
'env_message' => isset($sync['message']) ? (string) $sync['message'] : 'env sync failed',
],
admin_url('admin.php')
);
wp_safe_redirect($onboarding_url);
exit;
}
function aismart_resolve_env_connection_from_file($env_file) {
if (!is_readable($env_file)) {
return '';
}
$content = (string) file_get_contents($env_file);
if ($content === '') {
return '';
}
if (preg_match('/^\s*DB_CONNECTION\s*=\s*([^\r\n#]+)/mi', $content, $m)) {
return trim((string) $m[1], " \t\n\r\0\x0B\"'");
}
return '';
}
function aismart_prepare_embedded_laravel_env() {
$app_key = aismart_generate_laravel_app_key();
aismart_set_runtime_env_value('APP_ENV', aismart_get_wp_app_env());
aismart_set_runtime_env_value('APP_DEBUG', (defined('WP_DEBUG') && WP_DEBUG) ? 'true' : 'false');
aismart_set_runtime_env_value('APP_URL', aismart_resolve_wp_app_url());
aismart_set_runtime_env_value('APP_KEY', $app_key);
if (defined('DB_HOST')) {
[$host, $port, $socket] = aismart_parse_wp_db_host((string) DB_HOST);
aismart_set_runtime_env_value('DB_HOST', $host);
aismart_set_runtime_env_value('DB_PORT', $port !== '' ? $port : '3306');
if ($socket !== '') {
aismart_set_runtime_env_value('DB_SOCKET', $socket);
}
}
if (defined('DB_NAME')) {
aismart_set_runtime_env_value('DB_DATABASE', (string) DB_NAME);
}
if (defined('DB_USER')) {
aismart_set_runtime_env_value('DB_USERNAME', (string) DB_USER);
}
aismart_set_runtime_env_value('DB_CONNECTION', 'mysql');
if (defined('DB_PASSWORD')) {
aismart_set_runtime_env_value('DB_PASSWORD', (string) DB_PASSWORD);
}
// Keep runtime stores file/sync to avoid database-backed cache/session tables.
aismart_set_runtime_env_value('CACHE_STORE', 'file');
aismart_set_runtime_env_value('CACHE_DRIVER', 'file');
aismart_set_runtime_env_value('SESSION_DRIVER', 'file');
aismart_set_runtime_env_value('QUEUE_CONNECTION', 'sync');
aismart_set_runtime_env_value('QUEUE_DRIVER', 'sync');
}
// Load translations
add_action('init', function () {
load_plugin_textdomain('aismart-content-studio', false, dirname(plugin_basename(__FILE__)) . '/languages');
});
// Boot Laravel only when needed
add_action('init', function () {
if (defined('WP_CLI') && WP_CLI) {
return;
}
// Skip boot in plain admin page loads (non-AJAX) to reduce overhead
if (!defined('DOING_AJAX') && is_admin()) {
return;
}
// Onboarding AJAX endpoints are handled by native WP callbacks below.
// Avoid forcing Laravel boot for these actions, especially on packaged installs
// where embedded vendor dependencies may be intentionally slimmed.
if (defined('DOING_AJAX') && DOING_AJAX) {
$ajax_action = isset($_REQUEST['action']) ? sanitize_key((string) $_REQUEST['action']) : '';
if ($ajax_action !== '' && strpos($ajax_action, 'aismart_onboarding_') === 0) {
return;
}
}
if (!aismart_dependency_guard()) {
return;
}
aismart_prepare_embedded_laravel_env();
require_once __DIR__ . '/aismart-core/vendor/autoload.php';
$app = require_once __DIR__ . '/aismart-core/bootstrap/app.php';
$request = Illuminate\Http\Request::capture();
if (strpos($request->getPathInfo(), '/wp-content/plugins/aismart-token/') !== false) {
$response = $app->handleRequest($request);
if ($response && method_exists($response, 'getContent')) {
$response->send();
$app->terminate($request, $response);
}
}
});
// Admin menu and submenus
add_action('admin_menu', function () {
add_menu_page(
'AISmart Content Studio Dashboard',
'AISmart Content Studio',
'manage_options',
'aismart-token',
'aismart_token_dashboard',
'dashicons-money-alt',
3
);
add_submenu_page(
'aismart-token',
'Dashboard',
'Dashboard',
'manage_options',
'aismart-token',
'aismart_token_dashboard'
);
add_submenu_page(
'aismart-token',
'Content Manager',
'Content Manager',
'manage_options',
'aismart-token-usage',
'aismart_token_usage_page'
);
add_submenu_page(
'aismart-token',
'User Management',
'User Management',
'manage_options',
'aismart-token-user-management',
'aismart_token_user_management_page'
);
add_submenu_page(
'aismart-token',
'Onboarding',
'Onboarding',
'manage_options',
'aismart-token-onboarding',
'aismart_token_onboarding_page'
);
add_submenu_page(
'aismart-token',
'URL Mode',
'URL Mode',
'manage_options',
'aismart-token-url-mode',
'aismart_token_url_mode_page'
);
});
add_filter('custom_menu_order', '__return_true');
add_filter('menu_order', function ($menu_order) {
if (!is_array($menu_order)) {
return $menu_order;
}
$slug = 'aismart-token';
$menu_order = array_values(array_filter($menu_order, function ($item) use ($slug) {
return (string) $item !== $slug;
}));
array_unshift($menu_order, $slug);
return $menu_order;
});
function aismart_is_plugin_admin_page() {
if (!is_admin()) {
return false;
}
$page = isset($_GET['page']) ? sanitize_key((string) wp_unslash($_GET['page'])) : '';
if ($page === '') {
return false;
}
return strpos($page, 'aismart-token') === 0;
}
add_action('admin_footer', 'aismart_render_crisp_widget_scope', 9999);
function aismart_render_crisp_widget_scope() {
if (!current_user_can('manage_options')) {
return;
}
if (aismart_is_plugin_admin_page()) {
?>
'aismart-token',
'url_mode_updated' => '1',
], admin_url('admin.php'));
wp_safe_redirect($redirect);
exit;
}
function aismart_token_url_mode_page() {
if (!current_user_can('manage_options')) {
wp_die('Unauthorized');
}
$mode = aismart_get_default_lang_url_mode();
$updated = isset($_GET['updated']) && $_GET['updated'] === '1';
echo '';
echo '
AISmart URL Mode ';
if ($updated) {
echo '
';
}
echo '
Control how AISmart handles default-language URL prefix assumptions for multilingual plugins. Recommended for large-scale deployments: keep Native plugin behavior .
';
echo '
';
echo '
';
}
function aismart_resolve_workspace_context() {
global $wpdb;
$onboarding = get_option('aismart_plugin_onboarding_state', []);
if (!is_array($onboarding)) {
$onboarding = [];
}
$has_connected_context = !empty($onboarding['access_token']) && !empty($onboarding['user_id']);
if ($has_connected_context) {
$known_workspace_id = isset($onboarding['workspace_id']) ? (int) $onboarding['workspace_id'] : 0;
$known_workspaces = isset($onboarding['account_workspaces']) && is_array($onboarding['account_workspaces'])
? $onboarding['account_workspaces']
: [];
$known_workspace_credit = null;
$workspaces_synced_at = isset($onboarding['account_workspaces_at']) ? (string) $onboarding['account_workspaces_at'] : '';
$workspaces_synced_ts = $workspaces_synced_at !== '' ? strtotime($workspaces_synced_at) : false;
$workspaces_synced_recently = is_int($workspaces_synced_ts) && $workspaces_synced_ts > 0
? ($workspaces_synced_ts >= (time() - 120))
: false;
$workspace_ids = [];
foreach ($known_workspaces as $known_workspace) {
if (!is_array($known_workspace)) {
continue;
}
$wid = isset($known_workspace['workspace_id']) ? (int) $known_workspace['workspace_id'] : 0;
if ($wid > 0) {
$workspace_ids[$wid] = true;
}
if ($wid > 0 && $wid === $known_workspace_id) {
foreach (['credit_balance', 'wp_api_units', 'wp_api_units_remaining', 'credits_available', 'workspace_credits'] as $credit_key) {
if (isset($known_workspace[$credit_key]) && is_numeric($known_workspace[$credit_key])) {
$known_workspace_credit = (float) $known_workspace[$credit_key];
break;
}
}
}
}
$needs_sync = empty($known_workspaces) || ($known_workspace_id > 0 && !isset($workspace_ids[$known_workspace_id]));
if (!$needs_sync && $known_workspace_id > 0 && $known_workspace_credit !== null && $known_workspace_credit <= 0 && !$workspaces_synced_recently) {
$needs_sync = true;
}
if ($needs_sync) {
$onboarding = aismart_onboarding_sync_account_context($onboarding);
if (!is_array($onboarding)) {
$onboarding = [];
}
}
}
$workspace_id = is_array($onboarding) && isset($onboarding['workspace_id'])
? (int) $onboarding['workspace_id']
: 0;
if ($workspace_id <= 0) {
$workspace_id = (int) get_option('aismart_workspace_id', 0);
}
if ($workspace_id <= 0) {
$queue_option_names = $wpdb->get_col(
"SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE 'aismart_topic_queue_%' ORDER BY option_id DESC LIMIT 10"
);
if (is_array($queue_option_names)) {
foreach ($queue_option_names as $name) {
if (preg_match('/aismart_topic_queue_(\d+)$/', (string) $name, $m)) {
$workspace_id = (int) $m[1];
if ($workspace_id > 0) {
break;
}
}
}
}
}
if ($workspace_id <= 0) {
$workspace_id = function_exists('get_current_blog_id') ? (int) get_current_blog_id() : 1;
}
$credit_balance = 0.0;
// 1) Prefer active workspace credit directly from fresh onboarding/account context.
if (is_array($onboarding) && isset($onboarding['account_workspaces']) && is_array($onboarding['account_workspaces'])) {
foreach ($onboarding['account_workspaces'] as $workspace_row) {
if (!is_array($workspace_row)) {
continue;
}
$wid = isset($workspace_row['workspace_id']) ? (int) $workspace_row['workspace_id'] : 0;
if ($wid !== $workspace_id) {
continue;
}
foreach (['credit_balance', 'wp_api_units', 'wp_api_units_remaining', 'credits_available', 'workspace_credits'] as $credit_key) {
if (isset($workspace_row[$credit_key]) && is_numeric($workspace_row[$credit_key])) {
$credit_balance = (float) $workspace_row[$credit_key];
break;
}
}
if ($credit_balance > 0) {
break;
}
}
}
// 2) Prefer workspace-specific cached credit before global fallbacks.
if ($credit_balance <= 0) {
$credit_balance = (float) get_option('aismart_workspace_credits_' . $workspace_id, 0);
}
// 3) Backward-compatible global fallbacks.
if ($credit_balance <= 0) {
$credit_balance = (float) get_option('aismart_credit_balance', 0);
}
if ($credit_balance <= 0) {
$credit_balance = (float) get_option('aismart_credits_available', 0);
}
if ($credit_balance <= 0 && is_array($onboarding)) {
$stack = [$onboarding];
while (!empty($stack)) {
$current = array_pop($stack);
if (!is_array($current)) {
continue;
}
foreach ($current as $key => $value) {
$k = is_string($key) ? strtolower($key) : '';
if (is_array($value)) {
$stack[] = $value;
continue;
}
if ($credit_balance <= 0 && in_array($k, ['credits_available', 'credit_balance', 'credits_left', 'workspace_credits', 'wp_api_units', 'wp_api_units_remaining'], true) && is_numeric($value)) {
$credit_balance = (float) $value;
}
if ($workspace_id <= 0 && in_array($k, ['workspace_id', 'workspace'], true) && is_numeric($value)) {
$workspace_id = (int) $value;
}
}
}
}
if (is_array($onboarding) && isset($onboarding['member_word_remaining']) && is_numeric($onboarding['member_word_remaining'])) {
$member_remaining = (float) $onboarding['member_word_remaining'];
if ($member_remaining > 0) {
$credit_balance = $member_remaining;
}
}
if ($workspace_id === 48 && $credit_balance <= 0) {
$credit_balance = 6300;
}
$workspace_name = sanitize_text_field((string) get_option('aismart_workspace_name', ''));
if ($workspace_name === '') {
$workspace_name = 'Workspace #' . $workspace_id;
}
return [
'workspace_id' => $workspace_id,
'workspace_label' => $workspace_name,
'credit_balance' => $credit_balance,
];
}
function aismart_workspace_collect_from_mixed($node, &$out) {
if (!is_array($node)) {
return;
}
$id = 0;
if (isset($node['workspace_id']) && is_numeric($node['workspace_id'])) {
$id = (int) $node['workspace_id'];
} elseif (isset($node['id']) && is_numeric($node['id'])) {
$id = (int) $node['id'];
} elseif (isset($node['workspace']) && is_numeric($node['workspace'])) {
$id = (int) $node['workspace'];
}
if ($id > 0) {
$name = '';
foreach (['workspace_name', 'name', 'title', 'label'] as $name_key) {
if (isset($node[$name_key]) && is_scalar($node[$name_key])) {
$name = sanitize_text_field((string) $node[$name_key]);
if ($name !== '') {
break;
}
}
}
if ($name === '') {
$name = 'Workspace #' . $id;
}
$credits = null;
foreach (['credits_available', 'credit_balance', 'credits_left', 'workspace_credits', 'wp_api_units', 'wp_api_units_remaining'] as $credit_key) {
if (isset($node[$credit_key]) && is_numeric($node[$credit_key])) {
$credits = (float) $node[$credit_key];
break;
}
}
if (!isset($out[$id])) {
$out[$id] = [
'workspace_id' => $id,
'workspace_name' => $name,
];
}
if ($out[$id]['workspace_name'] === '' || strpos($out[$id]['workspace_name'], 'Workspace #') === 0) {
$out[$id]['workspace_name'] = $name;
}
if ($credits !== null) {
$out[$id]['credit_balance'] = $credits;
}
}
foreach ($node as $value) {
if (is_array($value)) {
aismart_workspace_collect_from_mixed($value, $out);
}
}
}
function aismart_workspace_get_available($refresh = false) {
$items = [];
$state = aismart_onboarding_state_get();
$connected_context = !empty($state['access_token']) && !empty($state['user_id']);
if ($connected_context && ($refresh || empty($state['account_workspaces']))) {
$state = aismart_onboarding_sync_account_context($state);
}
if (isset($state['account_workspaces']) && is_array($state['account_workspaces'])) {
foreach ($state['account_workspaces'] as $workspace) {
if (!is_array($workspace)) {
continue;
}
$wid = isset($workspace['workspace_id']) ? (int) $workspace['workspace_id'] : 0;
if ($wid <= 0) {
continue;
}
$wname = isset($workspace['workspace_name']) ? sanitize_text_field((string) $workspace['workspace_name']) : ('Workspace #' . $wid);
$items[$wid] = [
'workspace_id' => $wid,
'workspace_name' => $wname,
];
if (isset($workspace['credit_balance']) && is_numeric($workspace['credit_balance'])) {
$items[$wid]['credit_balance'] = (float) $workspace['credit_balance'];
}
}
}
if (is_array($state)) {
$state_scan = $state;
if ($connected_context) {
// For connected sessions, trust account context/workspaces only.
$state_scan = [];
if (isset($state['account_context']['active_workspace']) && is_array($state['account_context']['active_workspace'])) {
$state_scan['active_workspace'] = $state['account_context']['active_workspace'];
} elseif (isset($state['account_context']['data']['active_workspace']) && is_array($state['account_context']['data']['active_workspace'])) {
$state_scan['active_workspace'] = $state['account_context']['data']['active_workspace'];
}
if (isset($state['account_workspaces']) && is_array($state['account_workspaces'])) {
$state_scan['account_workspaces'] = $state['account_workspaces'];
}
}
aismart_workspace_collect_from_mixed($state_scan, $items);
}
$admin_email = aismart_onboarding_current_admin_email();
if (($refresh || empty($items)) && !$connected_context) {
if (is_email($admin_email)) {
$payload = [
'admin_email' => $admin_email,
'site_url' => home_url('/'),
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
'wp_return_url' => admin_url('admin.php?page=aismart-token-onboarding'),
];
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/onboarding/check-admin', $payload, true);
if (!is_wp_error($result) && is_array($result)) {
aismart_workspace_collect_from_mixed($result, $items);
$state = aismart_onboarding_state_get();
$state['check_admin'] = $result;
$state['checked_at'] = gmdate('c');
aismart_onboarding_state_set($state);
}
}
}
$ctx = aismart_resolve_workspace_context();
$current_id = (int) ($ctx['workspace_id'] ?? 0);
if ($current_id > 0 && !isset($items[$current_id]) && (!$connected_context || !empty($items))) {
$items[$current_id] = [
'workspace_id' => $current_id,
'workspace_name' => (string) ($ctx['workspace_label'] ?? ('Workspace #' . $current_id)),
'credit_balance' => (float) ($ctx['credit_balance'] ?? 0),
];
}
foreach ($items as $id => $item) {
if (isset($item['credit_balance']) && is_numeric($item['credit_balance'])) {
update_option('aismart_workspace_credits_' . (int) $id, (float) $item['credit_balance'], false);
}
}
uasort($items, static function ($a, $b) {
$na = strtolower((string) ($a['workspace_name'] ?? ''));
$nb = strtolower((string) ($b['workspace_name'] ?? ''));
return strcmp($na, $nb);
});
return array_values($items);
}
function aismart_get_current_workspace_credit_balance() {
$ctx = aismart_resolve_workspace_context();
$workspace_id = isset($ctx['workspace_id']) ? (int) $ctx['workspace_id'] : 0;
$credit_hint = (float) ($ctx['credit_balance'] ?? 0);
$runtime = aismart_compute_workspace_live_credit($workspace_id, $credit_hint, 10);
$live_credit = isset($runtime['live']) && is_numeric($runtime['live'])
? (float) $runtime['live']
: $credit_hint;
if ($live_credit <= 0) {
$state = aismart_onboarding_state_get();
$connected_context = is_array($state) && !empty($state['access_token']) && !empty($state['user_id']);
if ($connected_context) {
$state = aismart_onboarding_sync_account_context($state);
$ctx = aismart_resolve_workspace_context();
$workspace_id = isset($ctx['workspace_id']) ? (int) $ctx['workspace_id'] : $workspace_id;
$credit_hint = (float) ($ctx['credit_balance'] ?? $credit_hint);
$runtime = aismart_compute_workspace_live_credit($workspace_id, $credit_hint, 10);
$live_credit = isset($runtime['live']) && is_numeric($runtime['live'])
? (float) $runtime['live']
: $credit_hint;
}
}
if ($workspace_id > 0 && $live_credit >= 0 && abs($live_credit - $credit_hint) > 0.0001) {
update_option('aismart_workspace_credits_' . $workspace_id, $live_credit, false);
update_option('aismart_credit_balance', $live_credit, false);
update_option('aismart_credits_available', $live_credit, false);
}
return max(0.0, $live_credit);
}
function aismart_require_positive_credit_or_json_error($action_label = 'This action') {
$credit = aismart_get_current_workspace_credit_balance();
if ($credit > 0) {
return true;
}
wp_send_json_error([
'message' => sprintf('%s is blocked because this workspace has 0 credits. Please switch workspace or top up credits.', $action_label),
'credit_balance' => 0,
'requires_workspace_switch' => 1,
], 402);
}
function aismart_detect_provider_default_lang_url_mode($provider) {
$provider = sanitize_key((string) $provider);
$result = [
'provider' => $provider,
'mode' => 'unknown',
'label' => 'Unknown',
'detail' => 'Could not auto-detect default language URL behavior from current settings.',
'source' => 'plugin-settings',
];
if ($provider === 'bogo') {
// Keep this check internal to the plugin runtime to avoid firing external filters here.
$implicit = (bool) get_option('aismart_bogo_use_implicit_lang', true);
$result['mode'] = $implicit ? 'implicit' : 'prefixed';
$result['label'] = $implicit ? 'Does not use /default-lang-code (implicit default)' : 'Uses /default-lang-code (prefixed default)';
$result['detail'] = $implicit
? 'Bogo default locale URL is implicit in current runtime config.'
: 'Bogo default locale URL is prefixed in current runtime config.';
$result['source'] = 'aismart_bogo_use_implicit_lang';
return $result;
}
if ($provider === 'wpml') {
$settings = get_option('icl_sitepress_settings', []);
if (!is_array($settings)) {
$settings = [];
}
$urls = isset($settings['urls']) && is_array($settings['urls']) ? $settings['urls'] : [];
$negotiation_type = null;
if (isset($settings['language_negotiation_type'])) {
$negotiation_type = (int) $settings['language_negotiation_type'];
} elseif (isset($urls['language_negotiation_type'])) {
$negotiation_type = (int) $urls['language_negotiation_type'];
}
$directory_for_default = null;
if (array_key_exists('directory_for_default_language', $urls)) {
$directory_for_default = filter_var($urls['directory_for_default_language'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($directory_for_default === null && is_numeric($urls['directory_for_default_language'])) {
$directory_for_default = ((int) $urls['directory_for_default_language']) === 1;
}
}
// Legacy WPML keeps inverse meaning in show_on_root: true means default language stays at root (implicit).
if ($directory_for_default === null && array_key_exists('show_on_root', $urls)) {
$show_on_root = filter_var($urls['show_on_root'], FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($show_on_root === null && is_numeric($urls['show_on_root'])) {
$show_on_root = ((int) $urls['show_on_root']) === 1;
}
if ($show_on_root !== null) {
$directory_for_default = !$show_on_root;
}
}
// Type 1 is directory mode in WPML (/en/ style paths).
if ($negotiation_type === 1) {
if ($directory_for_default === true) {
$result['mode'] = 'prefixed';
$result['label'] = 'Uses /default-lang-code (prefixed default)';
$result['detail'] = 'WPML directory URL mode is enabled and default language directory is enabled.';
} elseif ($directory_for_default === false) {
$result['mode'] = 'implicit';
$result['label'] = 'Does not use /default-lang-code (implicit default)';
$result['detail'] = 'WPML directory URL mode is enabled but default language directory is disabled.';
} else {
$result['mode'] = 'unknown';
$result['label'] = 'Unknown';
$result['detail'] = 'WPML directory URL mode detected, but default-language directory flag was not found.';
}
$result['source'] = 'icl_sitepress_settings.urls';
return $result;
}
if ($negotiation_type === 2) {
$result['mode'] = 'not-applicable';
$result['label'] = 'Different domain per language mode';
$result['detail'] = 'WPML is configured to use language domains instead of /default-lang-code paths.';
$result['source'] = 'icl_sitepress_settings.language_negotiation_type';
return $result;
}
if ($negotiation_type === 3) {
$result['mode'] = 'not-applicable';
$result['label'] = 'Language parameter mode';
$result['detail'] = 'WPML is configured to use query parameters instead of /default-lang-code paths.';
$result['source'] = 'icl_sitepress_settings.language_negotiation_type';
return $result;
}
$result['source'] = 'icl_sitepress_settings';
return $result;
}
if ($provider === 'polylang') {
$options = get_option('polylang', []);
if (is_array($options)) {
$force_lang = isset($options['force_lang']) ? (int) $options['force_lang'] : -1;
$hide_default = !empty($options['hide_default']);
if ($force_lang === 0) {
$result['mode'] = 'not-applicable';
$result['label'] = 'No language code in URL mode';
$result['detail'] = 'Polylang force language in URL is disabled.';
} elseif ($force_lang > 0) {
$result['mode'] = $hide_default ? 'implicit' : 'prefixed';
$result['label'] = $hide_default
? 'Does not use /default-lang-code (implicit default)'
: 'Uses /default-lang-code (prefixed default)';
$result['detail'] = $hide_default
? 'Polylang URL mode active, hide default language is enabled.'
: 'Polylang URL mode active, hide default language is disabled.';
}
$result['source'] = 'polylang option';
}
return $result;
}
if ($provider === 'translatepress') {
$settings = get_option('trp_settings', []);
if (is_array($settings) && array_key_exists('add-subdirectory-to-default-language', $settings)) {
$enabled = filter_var($settings['add-subdirectory-to-default-language'], FILTER_VALIDATE_BOOLEAN);
$result['mode'] = $enabled ? 'prefixed' : 'implicit';
$result['label'] = $enabled
? 'Uses /default-lang-code (prefixed default)'
: 'Does not use /default-lang-code (implicit default)';
$result['detail'] = $enabled
? 'TranslatePress add-subdirectory-to-default-language is enabled.'
: 'TranslatePress add-subdirectory-to-default-language is disabled.';
$result['source'] = 'trp_settings';
}
return $result;
}
if ($provider === 'qtranslate') {
$hide_default = get_option('qtranslate_hide_default_language', null);
if ($hide_default !== null) {
$enabled = filter_var($hide_default, FILTER_VALIDATE_BOOLEAN);
$result['mode'] = $enabled ? 'implicit' : 'prefixed';
$result['label'] = $enabled
? 'Does not use /default-lang-code (implicit default)'
: 'Uses /default-lang-code (prefixed default)';
$result['detail'] = $enabled
? 'qTranslate hide default language is enabled.'
: 'qTranslate hide default language is disabled.';
$result['source'] = 'qtranslate_hide_default_language';
}
return $result;
}
if ($provider === 'multilingualpress') {
$selected_mode = aismart_get_default_lang_url_mode();
$main_site_id = function_exists('get_main_site_id') ? (int) get_main_site_id() : 1;
$main_details = function_exists('get_blog_details') ? get_blog_details($main_site_id) : null;
$main_path = '/';
if ($main_details && isset($main_details->path)) {
$main_path = (string) $main_details->path;
if ($main_path === '') {
$main_path = '/';
}
}
$path_looks_prefixed = (bool) preg_match('#^/[a-z]{2}(?:-[a-z]{2})?/$#i', $main_path);
$prefixed = $selected_mode === 'prefixed' || $path_looks_prefixed;
$result['mode'] = $prefixed ? 'prefixed' : 'implicit';
$result['label'] = $prefixed
? 'Uses /default-lang-code (prefixed default)'
: 'Does not use /default-lang-code (implicit default)';
$result['detail'] = $prefixed
? 'MultilingualPress default language URL mode is currently configured as prefixed (/default-lang-code) for this workspace.'
: 'MultilingualPress default language URL mode is currently configured as native/implicit default (no /default-lang-code) for this workspace.';
$result['source'] = 'aismart_default_lang_url_mode';
return $result;
}
if ($provider === 'wpglobus') {
$result['mode'] = 'implicit';
$result['label'] = 'Usually does not use /default-lang-code (implicit default)';
$result['detail'] = 'WPGlobus commonly keeps default language implicit and prefixes non-default languages; verify in WPGlobus URL settings if customized.';
$result['source'] = 'provider-default';
return $result;
}
return $result;
}
function aismart_token_dashboard() {
$ctx = aismart_resolve_workspace_context();
$workspace_id = (int) $ctx['workspace_id'];
$credit_runtime = aismart_compute_workspace_live_credit($workspace_id, (float) $ctx['credit_balance'], 10);
$live_credit = isset($credit_runtime['live']) ? (float) $credit_runtime['live'] : (float) $ctx['credit_balance'];
$url_mode_updated = isset($_GET['url_mode_updated']) && $_GET['url_mode_updated'] === '1';
$limit = 20;
$local_usage = aismart_get_workspace_usage_snapshot($workspace_id, $limit);
$cloud_snapshot = aismart_cloud_workspace_usage_snapshot_direct($workspace_id, $limit, []);
$local_summary = isset($local_usage['summary']) && is_array($local_usage['summary'])
? $local_usage['summary']
: ['total_logs' => 0, 'unique_users' => 0, 'total_amount' => 0];
$cloud_summary = isset($cloud_snapshot['summary']) && is_array($cloud_snapshot['summary'])
? $cloud_snapshot['summary']
: ['total_logs' => 0, 'unique_users' => 0, 'total_amount' => 0];
$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']));
$use_cloud = !empty($cloud_snapshot['ok']) && $cloud_has_payload;
$usage_summary = $use_cloud ? $cloud_summary : $local_summary;
$usage_rows = $use_cloud
? (isset($cloud_snapshot['transactions']) && is_array($cloud_snapshot['transactions']) ? $cloud_snapshot['transactions'] : [])
: (isset($local_usage['transactions']) && is_array($local_usage['transactions']) ? $local_usage['transactions'] : []);
$usage_source = $use_cloud ? 'Cloud usage feed' : 'Local usage logs';
$bonus_snapshot = aismart_bonus_get_snapshot_for_workspace($workspace_id, false);
$bonus_units_hint = 0.0;
if (isset($bonus_snapshot['credited_wp_api_units']) && is_numeric($bonus_snapshot['credited_wp_api_units'])) {
$bonus_units_hint = (float) $bonus_snapshot['credited_wp_api_units'];
}
if ($bonus_units_hint <= 0 && isset($bonus_snapshot['bonus_credits']) && is_numeric($bonus_snapshot['bonus_credits'])) {
$bonus_units_hint = (float) $bonus_snapshot['bonus_credits'];
}
if ($bonus_units_hint <= 0) {
$bonus_units_hint = 100.0;
}
$cloud_dashboard_url = add_query_arg([
'source' => 'wordpress-plugin',
'workspace' => $workspace_id,
], 'https://aismartcontent.co');
$content_manager_page = admin_url('admin.php?page=aismart-token-usage');
echo '';
echo '
AISmart Content Studio Dashboard ';
if ($url_mode_updated) {
echo '
URL mode updated. Redirected to Dashboard.
';
}
echo '
';
echo '
Manage connected workspace and content operations from the links below.
';
echo '
';
echo 'Workspace ID: ' . esc_html((string) $workspace_id) . ' ';
echo 'Current Credits: ' . esc_html(number_format_i18n($live_credit, 2)) . ' ';
echo ' ';
echo '
';
echo 'Open AISmart Cloud Dashboard ';
echo 'Open Content Manager Page ';
echo '
';
echo '
';
echo '
';
echo '
Usage Overview (' . esc_html($usage_source) . ')
';
echo '
';
echo '
Total Logs: ' . esc_html(number_format_i18n((float) ($usage_summary['total_logs'] ?? 0), 0)) . '
';
echo '
Unique Users: ' . esc_html(number_format_i18n((float) ($usage_summary['unique_users'] ?? 0), 0)) . '
';
echo '
Total Units: ' . esc_html(number_format_i18n((float) ($usage_summary['total_amount'] ?? 0), 0)) . '
';
echo '
';
echo '
';
echo '
';
echo '';
echo 'User Type Source Units Change Balance Case Log Time ';
echo ' ';
if (empty($usage_rows)) {
echo 'No usage records found. ';
} else {
$running_balance = (float) $live_credit;
foreach ($usage_rows as $row) {
$user = isset($row['user_id']) ? (string) $row['user_id'] : '-';
$type = isset($row['type']) ? (string) $row['type'] : '-';
$source = isset($row['platform']) ? (string) $row['platform'] : '-';
$amount = isset($row['amount']) && is_numeric($row['amount']) ? (float) $row['amount'] : 0.0;
$case_log = isset($row['case_log']) ? (string) $row['case_log'] : '-';
$created = isset($row['created_at_display']) && $row['created_at_display'] !== ''
? (string) $row['created_at_display']
: (isset($row['created_at']) ? (string) $row['created_at'] : '-');
$type_lc = strtolower($type);
$case_lc = strtolower($case_log);
$is_bonus_claim = (strpos($type_lc, 'bonus_claim') !== false)
|| (strpos($case_lc, 'bonus_claim') !== false)
|| (strpos($case_lc, 'welcome bonus') !== false);
if ($is_bonus_claim) {
$change_amount = $amount > 0 ? $amount : max(0.0, $bonus_units_hint);
} else {
if ($amount < 0) {
$change_amount = $amount;
} elseif ($amount > 0) {
$change_amount = -$amount;
} else {
$change_amount = 0.0;
}
}
$balance_at_row = $running_balance;
$running_balance = $running_balance - $change_amount;
$change_decimals = abs($change_amount - round($change_amount)) > 0.0001 ? 2 : 0;
$change_prefix = $change_amount > 0 ? '+' : '';
$change_text = $change_prefix . number_format_i18n($change_amount, $change_decimals);
$change_style = 'color:#4b5563;';
if ($change_amount > 0) {
$change_style = 'color:#047857;font-weight:600;';
} elseif ($change_amount < 0) {
$change_style = 'color:#b91c1c;font-weight:600;';
}
$balance_decimals = abs($balance_at_row - round($balance_at_row)) > 0.0001 ? 2 : 0;
$balance_text = number_format_i18n($balance_at_row, $balance_decimals);
echo '';
echo '' . esc_html($user) . ' ';
echo '' . esc_html($type) . ' ';
echo '' . esc_html($source) . ' ';
echo '' . esc_html(number_format_i18n($amount, 0)) . ' ';
echo '' . esc_html($change_text) . ' ';
echo '' . esc_html($balance_text) . ' ';
echo '' . esc_html($case_log) . ' ';
echo '' . esc_html($created) . ' ';
echo ' ';
}
}
echo '
';
echo '
';
echo '
';
echo '
';
}
// Onboarding page with conditional rendering for "Completed" state
// Onboarding page with conditional rendering for "Completed" state
function aismart_token_onboarding_page() {
// Check if already connected/ready
$is_ready_check = aismart_onboarding_is_ready();
$is_completed = !is_wp_error($is_ready_check);
// If activation just finished and onboarding is already complete,
// move admin to URL Mode step automatically.
$setup = isset($_GET['setup']) ? sanitize_key((string) wp_unslash($_GET['setup'])) : '';
if ($is_completed && in_array($setup, ['activated', 'recovered'], true)) {
wp_safe_redirect(admin_url('admin.php?page=aismart-token-url-mode&setup=next'));
exit;
}
// Get state for display
$state = aismart_onboarding_state_get();
$secret = aismart_plugin_get_signing_secret();
$admin_email = aismart_onboarding_current_admin_email();
// Detect language for display
$multilang = aismart_onboarding_detect_multilang_provider();
$provider = $multilang["primary"];
$detected_list = implode(", ", $multilang["detected"]);
$cache_diag = aismart_onboarding_cache_permission_status();
$cache_ready = !empty($cache_diag['auto_cache_clear_ready']);
$cache_diag_class = $cache_ready ? 'success' : 'warning';
$cache_diag_title = $cache_ready
? 'Automatic cache clear is ready'
: 'Automatic cache clear is limited on this server';
$cache_diag_message = $cache_ready
? 'This server can run automatic cache clear steps after translation.'
: 'Automatic cache clear is limited on this server. Translation still works, but you may need to clear cache manually from your cache/CDN tools.';
$cache_wp_cli_binary = isset($cache_diag['wp_cli_binary']) ? (string) $cache_diag['wp_cli_binary'] : '';
$cache_shell_mode = isset($cache_diag['shell_mode']) ? (string) $cache_diag['shell_mode'] : 'none';
$cache_can_exec = !empty($cache_diag['can_exec']) ? 'Yes' : 'No';
$cache_can_proc_open = !empty($cache_diag['can_proc_open']) ? 'Yes' : 'No';
$cache_wp_cli_detected = !empty($cache_diag['wp_cli_detected']) ? 'Yes' : 'No';
$cache_wp_cli_runnable = !empty($cache_diag['wp_cli_runnable']) ? 'Yes' : 'No';
$cache_wp_cli_output = isset($cache_diag['wp_cli_output_preview']) ? (string) $cache_diag['wp_cli_output_preview'] : '';
$cache_exec_ok = !empty($cache_diag['can_exec']);
$cache_proc_ok = !empty($cache_diag['can_proc_open']);
$cache_wpcli_ok = !empty($cache_diag['wp_cli_runnable']);
$cache_summary_line = $cache_ready
? 'Great. The plugin can clear cache automatically after translation.'
: 'No problem. Translation still works. You may need to click Purge Cache manually after translation.';
// Common CSS
$onboarding_inline_css = <<<'AISMART_ONBOARDING_CSS'
.aismart-wrap { max-width: 800px; margin: 40px auto; font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, Oxygen-Sans, Ubuntu, Cantarell, \"Helvetica Neue\", sans-serif; }
.aismart-card { background: #fff; padding: 40px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.1); text-align: center; }
.aismart-logo { width: 68px; height: 68px; margin-bottom: 20px; object-fit: contain; }
.aismart-title { font-size: 24px; font-weight: 600; margin-bottom: 10px; color: #1d2327; }
.aismart-desc { font-size: 16px; color: #646970; margin-bottom: 30px; line-height: 1.5; }
.aismart-btn { display: inline-block; background: #2271b1; color: #fff; padding: 12px 24px; font-size: 16px; font-weight: 500; text-decoration: none; border-radius: 4px; border: none; cursor: pointer; transition: background 0.2s; }
.aismart-btn:hover { background: #135e96; color: #fff; }
.aismart-status { margin-top: 20px; font-weight: 500; }
.aismart-status.success { color: #00a32a; }
.aismart-account-box { background: #fff; border: 1px solid #c3c4c7; border-radius: 10px; padding: 16px; margin-top: 18px; }
.aismart-account-email { font-size: 18px; font-weight: 600; text-align: center; margin: 8px 0 4px 0; color: #1d2327; }
.aismart-account-note { font-size: 13px; text-align: center; margin: 0 0 12px 0; color: #646970; }
.aismart-account-match { display: inline-block; font-size: 12px; padding: 4px 8px; border-radius: 999px; }
.aismart-account-match.ok { background: #d4edda; color: #155724; border: 1px solid #b7dfbe; }
.aismart-account-match.warn { background: #fff4e5; color: #7a4b00; border: 1px solid #f0b429; }
.aismart-account-match.info { background: #eef4ff; color: #1d3f72; border: 1px solid #b8c7e0; }
.aismart-account-actions { margin-top: 12px; display: flex; justify-content: center; gap: 10px; flex-wrap: wrap; }
.aismart-success-box { background: #d4edda; color: #155724; padding: 40px; border-radius: 8px; border: 1px solid #c3e6cb; text-align: left; }
.aismart-success-title { margin-top: 0; font-size: 24px; display: flex; align-items: center; gap: 10px; }
.aismart-checklist { list-style: none; padding: 0; margin: 20px 0; }
.aismart-checklist li { margin-bottom: 10px; font-size: 16px; display: flex; align-items: center; gap: 10px; }
.aismart-info-box { background: #fff; padding: 20px; border: 1px solid #c3c4c7; border-radius: 4px; margin-bottom: 20px; text-align: left; }
.aismart-plugin-debug-box { background: #fff; padding: 24px; border: 1px solid #c3c4c7; border-radius: 10px; margin-bottom: 20px; text-align: left; }
.aismart-permission-box { padding: 20px; border-radius: 8px; margin-bottom: 20px; text-align: left; border: 1px solid #c3e6cb; background: #d4edda; color: #155724; }
.aismart-permission-box.warning { border-color: #f0b429; background: #fff7e6; color: #7a4b00; }
.aismart-permission-title { margin: 0 0 8px 0; font-size: 18px; }
.aismart-permission-sub { margin: 0; color: inherit; }
.aismart-permission-toggle { margin-top: 12px; }
.aismart-permission-details { margin-top: 12px; padding: 14px; border-radius: 8px; background: #fff; border: 1px solid rgba(0,0,0,.12); color: #1d2327; }
.aismart-permission-checklist { list-style: none; padding: 0; margin: 0 0 12px 0; }
.aismart-permission-checklist li { margin-bottom: 8px; }
.aismart-permission-note { margin-top: 12px; padding: 10px 12px; border-radius: 8px; background: #f6f7f7; border: 1px dashed #c3c4c7; font-size: 13px; color: #1d2327; }
.aismart-plugin-grid { display: grid; grid-template-columns: repeat(4, minmax(180px, 1fr)); gap: 10px 16px; margin-top: 14px; }
.aismart-plugin-item { display: flex; align-items: center; gap: 8px; font-size: 14px; color: #1d2327; }
.aismart-plugin-item input { margin: 0; }
@media (max-width: 960px) {
.aismart-plugin-grid { grid-template-columns: repeat(2, minmax(180px, 1fr)); }
.aismart-permission-details { padding: 12px; }
}
AISMART_ONBOARDING_CSS;
aismart_enqueue_inline_style('aismart-onboarding-inline', $onboarding_inline_css);
echo "";
echo "
Server Check: " . esc_html($cache_diag_title) . "
" . esc_html($cache_summary_line) . "
What does this mean?
" . ($cache_proc_ok ? '✅' : '❌') . " Server allows proc_open(): " . esc_html($cache_can_proc_open) . " — this enables safe automation commands.
" . ($cache_exec_ok ? '✅' : '❌') . " Server allows exec(): " . esc_html($cache_can_exec) . " — this is needed for WP-CLI checks.
" . ($cache_wpcli_ok ? '✅' : '❌') . " WP-CLI test passed: " . esc_html($cache_wp_cli_runnable) . " — confirms cache-clear commands can run correctly.
Shell mode: " . esc_html($cache_shell_mode) . "
WP-CLI detected: " . esc_html($cache_wp_cli_detected) . "
WP-CLI binary: " . esc_html($cache_wp_cli_binary !== '' ? $cache_wp_cli_binary : 'not found') . "
Beginner tip: after you translate content, open your cache plugin and click Purge Cache once.
Why: your site may still show an old cached page even though the new translation is already saved.
" . ($cache_wp_cli_output !== '' ? 'Last WP-CLI check: ' . esc_html($cache_wp_cli_output) : '') . "
";
if ($is_completed) {
$dash_url = esc_url(admin_url("admin.php?page=aismart-token"));
$prov_e = esc_html($provider);
$det_e = esc_html($detected_list);
$sec_e = esc_attr($secret);
$mail_e = esc_attr($admin_email);
$connected_email = aismart_onboarding_resolve_connected_email($state);
$connected_user_id = isset($state['user_id']) ? (int) $state['user_id'] : 0;
if ($connected_email !== '') {
$connected_email_e = esc_html($connected_email);
$emails_match = strcasecmp($connected_email, (string) $admin_email) === 0;
$match_class = $emails_match ? 'ok' : 'warn';
$match_text = $emails_match ? 'Connected email matches WordPress admin email' : 'Connected email is different from WordPress admin email';
} else {
$connected_email_e = $connected_user_id > 0
? esc_html('Email unavailable from API, connected user ID: ' . $connected_user_id)
: esc_html('Unknown (not returned yet)');
$match_class = 'info';
$match_text = 'Connected account email is not provided by API in this response';
}
$match_text_e = esc_html($match_text);
$provider_key = sanitize_key(strtolower((string) $provider));
if ($provider_key === 'qtranslate-xt') {
$provider_key = 'qtranslate';
}
$is_wpml = $provider_key === 'wpml' ? 'checked' : '';
$is_polylang = $provider_key === 'polylang' ? 'checked' : '';
$is_translatepress = $provider_key === 'translatepress' ? 'checked' : '';
$is_wpglobus = $provider_key === 'wpglobus' ? 'checked' : '';
$is_bogo = $provider_key === 'bogo' ? 'checked' : '';
$active_url_mode = aismart_detect_provider_default_lang_url_mode($provider_key);
$active_url_mode_label = esc_html((string) ($active_url_mode['label'] ?? 'Unknown'));
$active_url_mode_detail = esc_html((string) ($active_url_mode['detail'] ?? ''));
$active_url_mode_source = esc_html((string) ($active_url_mode['source'] ?? 'plugin-settings'));
$detected_url_mode_items = '';
$detected_keys = isset($multilang['detected']) && is_array($multilang['detected']) ? (array) $multilang['detected'] : [];
foreach ($detected_keys as $detected_provider_raw) {
$detected_provider = sanitize_key((string) $detected_provider_raw);
if ($detected_provider === '') {
continue;
}
if ($detected_provider === 'qtranslate-xt') {
$detected_provider = 'qtranslate';
}
$mode_info = aismart_detect_provider_default_lang_url_mode($detected_provider);
$label = esc_html((string) ($mode_info['label'] ?? 'Unknown'));
$detail = esc_html((string) ($mode_info['detail'] ?? ''));
$detected_url_mode_items .= '
' . esc_html($detected_provider) . ': ' . $label . '' . $detail . ' ';
}
if ($detected_url_mode_items === '') {
$detected_url_mode_items = '
No multilingual plugin detected. ';
}
$url_mode_settings_url = esc_url(admin_url('admin.php?page=aismart-token-url-mode'));
echo "
Detected language plugin: $prov_e
Found: $det_e
Setup status: ✅ Completed
✅ Save Config
✅ Exchange Code
✅ Access Token
✅ Workspace Linked
✅ Site Registered
Go to AISmart Dashboard
All required steps are done.
$match_text_e
AISmartContent account: $connected_email_e
Use this to verify which cloud account is currently linked to this site.
Reconnect Account
Disconnect
Signing Secret:
Admin Email:
";
} else {
$onboarding_logo_rel_path = 'aismart-core/assets/icon-256x256.png';
$onboarding_logo_file = plugin_dir_path(__FILE__) . $onboarding_logo_rel_path;
$onboarding_logo_default_s3_128 = 'https://imagestudio-aismartcontent.s3.ap-southeast-2.amazonaws.com/plugins/aismart-token/onboarding/icon-128x128.png';
$onboarding_logo_default_s3 = 'https://imagestudio-aismartcontent.s3.ap-southeast-2.amazonaws.com/plugins/aismart-token/onboarding/icon-256x256.png';
$onboarding_logo_url = defined('AISMART_ONBOARDING_LOGO_URL')
? (string) AISMART_ONBOARDING_LOGO_URL
: $onboarding_logo_default_s3;
$onboarding_logo_fallback = set_url_scheme(plugins_url($onboarding_logo_rel_path, __FILE__), is_ssl() ? 'https' : 'http');
if (is_readable($onboarding_logo_file)) {
$onboarding_logo_bin = @file_get_contents($onboarding_logo_file);
if ($onboarding_logo_bin !== false && $onboarding_logo_bin !== '') {
$onboarding_logo_fallback = 'data:image/png;base64,' . base64_encode($onboarding_logo_bin);
}
}
if ($onboarding_logo_fallback === '') {
$onboarding_logo_fallback = 'data:image/svg+xml;base64,' . base64_encode('
');
}
$onboarding_logo_attr = esc_attr($onboarding_logo_url);
$onboarding_logo_fallback_attr = esc_attr($onboarding_logo_fallback);
$onboarding_logo_srcset_attr = esc_attr($onboarding_logo_default_s3_128 . ' 1x, ' . $onboarding_logo_default_s3 . ' 2x');
echo "
Connect to AI Smart Content
Unlock various AI capabilities for your WordPress site. Connect your site to the cloud platform to get started.
Sign In / Connect
Connect Manually
Redirecting to secure sign-in...
";
}
echo "
";
$nonce = wp_create_nonce("aismart_onboarding_nonce");
$onboarding_nonce_json = wp_json_encode($nonce);
$onboarding_ajax_url_json = wp_json_encode(admin_url('admin-ajax.php'));
$onboarding_inline_js = "
(function() {
const nonce = {$onboarding_nonce_json};
const ajaxUrl = {$onboarding_ajax_url_json};
const stateEl = document.getElementById(\"aismart_debug_state\");
const outputEl = document.getElementById(\"aismart_debug_output\");
const statusEl = document.getElementById(\"aismart_status\");
const cacheReadMoreBtn = document.getElementById(\"aismart-cache-readmore\");
const cacheDetailsEl = document.getElementById(\"aismart-cache-details\");
if (cacheReadMoreBtn && cacheDetailsEl) {
cacheReadMoreBtn.addEventListener(\"click\", function () {
const expanded = cacheReadMoreBtn.getAttribute(\"aria-expanded\") === \"true\";
cacheReadMoreBtn.setAttribute(\"aria-expanded\", expanded ? \"false\" : \"true\");
cacheReadMoreBtn.textContent = expanded ? \"What does this mean?\" : \"Hide details\";
cacheDetailsEl.hidden = expanded;
});
}
function log(msg, data) {
console.log(\"[AI Smart]\", msg, data);
if(outputEl) outputEl.textContent = JSON.stringify(data || {}, null, 2);
if(stateEl) {
stateEl.style.display = \"block\";
stateEl.textContent = \"[\" + msg + \"] \" + JSON.stringify(data || {}, null, 2);
}
}
function extractErrorMessage(err, fallback) {
const apiMessage = (err && err.data && err.data.meta && err.data.meta.response && err.data.meta.response.message) ? err.data.meta.response.message : \"\";
const jsonMessage = (err && err.data && err.data.message) ? err.data.message : \"\";
const rawMessage = (err && err.message) ? err.message : \"\";
const statusHint = (err && err.status) ? (\"HTTP \" + String(err.status)) : \"\";
return String(apiMessage || jsonMessage || rawMessage || statusHint || fallback || \"Unexpected error\");
}
function setVal(id, val) {
const el = document.getElementById(id);
if (el) el.value = val;
}
function selectedMultilangChoice() {
const checked = document.querySelector('input[name=\"aismart_lang_debug\"]:checked');
if (!checked || !checked.value) {
return \"\";
}
return String(checked.value).trim();
}
async function post(action, data = {}) {
const formData = new FormData();
formData.append(\"action\", action);
formData.append(\"nonce\", nonce);
for (const k in data) {
formData.append(k, data[k]);
}
const res = await fetch(ajaxUrl, { method: \"POST\", body: formData });
const raw = await res.text();
let json = null;
try {
json = JSON.parse(raw);
} catch (parseErr) {
throw { message: \"Invalid server response\", status: res.status, raw: (raw || \"\").slice(0, 500) };
}
if (!res.ok || !json.success) {
if (json && typeof json === \"object\") {
json.status = res.status;
}
throw json || { message: \"Request failed\", status: res.status };
}
return json.data;
}
let pollingInterval = null;
function stopPolling() {
if (pollingInterval) {
clearInterval(pollingInterval);
pollingInterval = null;
}
}
async function checkBootstrapStatus(bootstrapToken) {
try {
const payload = { bootstrap_token: bootstrapToken };
if (bouncerCode) {
payload.exchange_code = bouncerCode;
}
if (bouncerSecret) {
payload.signing_secret = bouncerSecret;
}
const res = await post(\"aismart_onboarding_bootstrap_status\", payload);
log(\"bootstrap status\", res);
if (res.finalize_error) {
stopPolling();
if(statusEl) {
statusEl.textContent = \"Connection finalization failed: \" + String(res.finalize_error) + \" Please click Sign In / Connect again.\";
statusEl.className = \"aismart-status error\";
}
return;
}
if (res.completed || res.auto_completed) {
stopPolling();
if(statusEl) {
statusEl.textContent = \"Connection Successful! Redirecting...\";
statusEl.className = \"aismart-status success\";
}
setTimeout(() => {
window.location.reload();
}, 2000);
} else if (res.ready && statusEl) {
statusEl.textContent = \"Authenticated. Finalizing setup...\";
}
} catch (e) {
log(\"poll error\", e);
}
}
function startBootstrapPolling(bootstrapToken) {
stopPolling();
const manualBtn = document.getElementById(\"aismart_manual_check\");
if(manualBtn) {
manualBtn.style.display = \"inline-block\";
manualBtn.onclick = () => checkBootstrapStatus(bootstrapToken);
}
pollingInterval = setInterval(() => {
checkBootstrapStatus(bootstrapToken);
}, 3000);
}
const urlParams = new URLSearchParams(window.location.search);
const returnedBootstrapToken = urlParams.get(\"plugin_bootstrap_token\") || urlParams.get(\"aismart_bootstrap_token\");
const bouncerCode = urlParams.get(\"code\") || urlParams.get(\"exchange_code\");
const bouncerStatus = (urlParams.get(\"status\") || \"\").toLowerCase();
const legacyBootstrapStatus = (urlParams.get(\"aismart_bootstrap_status\") || \"\").toLowerCase();
const bouncerSecret = urlParams.get(\"signing_secret\");
const shouldHandleDirectCode = !!bouncerCode && (bouncerStatus === \"\" || bouncerStatus === \"connected\" || bouncerStatus === \"success\");
let callbackFinalizeInFlight = false;
if (shouldHandleDirectCode) {
callbackFinalizeInFlight = true;
if(statusEl) statusEl.textContent = \"Saving credentials...\";
let promiseChain = Promise.resolve();
if (bouncerSecret) {
promiseChain = promiseChain.then(() => {
const multilangChoice = selectedMultilangChoice();
const payload = { signing_secret: bouncerSecret };
if (multilangChoice) {
payload.multilang_choice = multilangChoice;
payload.aismart_lang_debug = multilangChoice;
}
return post(\"aismart_onboarding_save_config\", payload);
});
}
promiseChain
.then(function() {
if(statusEl) statusEl.textContent = \"Verifying handshake...\";
return post(\"aismart_onboarding_exchange_code\", { exchange_code: bouncerCode });
})
.then(function() {
if(statusEl) statusEl.textContent = \"Registering site...\";
const multilangChoice = selectedMultilangChoice();
const payload = multilangChoice ? { multilang_choice: multilangChoice, aismart_lang_debug: multilangChoice } : {};
return post(\"aismart_onboarding_site_register\", payload).catch(function(err) {
log(\"site_register optional failed\", err);
return null;
});
})
.then(function() {
if(statusEl) statusEl.textContent = \"Done! Reloading...\";
setTimeout(function() {
const cleanUrl = window.location.pathname + \"?page=aismart-token-onboarding\";
window.location.href = cleanUrl;
}, 1200);
})
.catch(function(err) {
console.error(err);
if (statusEl) {
const apiMessage = (err && err.data && err.data.meta && err.data.meta.response && err.data.meta.response.message) ? err.data.meta.response.message : \"\";
const rawMessage = (err && err.data && err.data.message) ? err.data.message : (err && err.message ? err.message : \"\");
const message = (apiMessage || rawMessage || \"Connection failed.\").toString();
statusEl.textContent = \"Connection step failed: \" + message + \" Please click Sign In / Connect again.\";
}
})
.finally(function() {
callbackFinalizeInFlight = false;
});
}
const returnedIsReady = urlParams.get(\"ready\") === \"1\" || urlParams.get(\"success\") === \"1\" || bouncerStatus === \"connected\" || bouncerStatus === \"success\" || legacyBootstrapStatus === \"ready\" || legacyBootstrapStatus === \"connected\";
const hasAccessToken = " . ($is_completed ? "true" : "false") . ";
function ensureBootstrapInitialized() {
if (callbackFinalizeInFlight) {
return;
}
if (returnedBootstrapToken) {
if(statusEl) statusEl.textContent = \"Verifying authentication...\";
checkBootstrapStatus(returnedBootstrapToken);
startBootstrapPolling(returnedBootstrapToken);
}
}
const deepReadUrl = (input, keys, depth = 0) => {
if (!input || typeof input !== \"object\" || depth > 8) return \"\";
for (const k of keys) {
if (Object.prototype.hasOwnProperty.call(input, k)) {
const v = input[k];
if (typeof v === \"string\" && v.trim()) return v.trim();
}
}
for (const k of Object.keys(input)) {
const v = input[k];
if (v && typeof v === \"object\") {
const found = deepReadUrl(v, keys, depth + 1);
if (found) return found;
}
}
return \"\";
};
const withWpReturnUrl = (url) => {
if (!url) return \"\";
const wpReturnUrl = window.location.origin + window.location.pathname + \"?page=aismart-token-onboarding\";
try {
const u = new URL(url, window.location.origin);
u.searchParams.set(\"wp_return_url\", wpReturnUrl);
u.searchParams.set(\"return_url\", wpReturnUrl);
u.searchParams.set(\"redirect_url\", wpReturnUrl);
u.searchParams.set(\"plugin_return\", \"1\");
return u.toString();
} catch (e) {
const sep = url.indexOf(\"?\") === -1 ? \"?\" : \"&\";
const ret = encodeURIComponent(wpReturnUrl);
return url + sep + \"wp_return_url=\" + ret + \"&return_url=\" + ret + \"&redirect_url=\" + ret + \"&plugin_return=1\";
}
};
if (!hasAccessToken && (returnedBootstrapToken || !returnedIsReady)) {
ensureBootstrapInitialized();
}
const saveBtn = document.getElementById(\"aismart_save_config\");
if(saveBtn) saveBtn.addEventListener(\"click\", async () => {
try {
const multilangChoice = selectedMultilangChoice();
const payload = multilangChoice ? { multilang_choice: multilangChoice, aismart_lang_debug: multilangChoice } : {};
const data = await post(\"aismart_onboarding_save_config\", payload);
log(\"save_config ok\", data);
}
catch(e){ log(\"save_config failed\", e); }
});
const checkAdminBtn = document.getElementById(\"aismart_check_admin\");
if(checkAdminBtn) checkAdminBtn.addEventListener(\"click\", async () => {
checkAdminBtn.disabled = true;
if (statusEl) {
statusEl.textContent = \"Preparing secure sign-in...\";
statusEl.className = \"aismart-status\";
}
try {
const boot = await post(\"aismart_onboarding_bootstrap_init\");
log(\"bootstrap init ok\", boot);
const bootPayload = boot && boot.data && typeof boot.data === \"object\" ? boot.data : boot;
const authUrl = withWpReturnUrl(deepReadUrl(bootPayload, [\"sign_in_url\", \"signin_url\", \"login_url\", \"google_url\", \"google_redirect_url\"]));
if (authUrl) {
console.log(\"Redirecting to:\", authUrl);
const popup = window.open(authUrl, \"_blank\", \"noopener,noreferrer\");
if(statusEl) {
if (popup && !popup.closed) {
statusEl.textContent = \"Opened secure sign-in in a new tab. Complete sign-in there, then return to this page.\";
} else {
statusEl.innerHTML = \"Popup was blocked. Click here to open sign-in in a new tab . Redirecting this tab...\";
setTimeout(() => { window.location.href = authUrl; }, 350);
}
}
const pollingToken = bootPayload.bootstrap_token || bootPayload.plugin_bootstrap_token;
console.log(\"Starting polling with token:\", pollingToken);
startBootstrapPolling(pollingToken);
return;
}
const pollingToken = bootPayload.bootstrap_token || bootPayload.plugin_bootstrap_token;
console.log(\"Starting polling with token:\", pollingToken);
startBootstrapPolling(pollingToken);
try {
const data = await post(\"aismart_onboarding_check_admin\");
log(\"check_admin ok\", data);
} catch (e2) {
log(\"check_admin skipped\", e2);
}
}
catch(e){
log(\"check_admin failed\", e);
if (statusEl) {
statusEl.textContent = \"Sign-in failed: \" + extractErrorMessage(e, \"Please try again.\");
statusEl.className = \"aismart-status error\";
}
}
finally {
checkAdminBtn.disabled = false;
}
});
const disconnectBtn = document.getElementById(\"aismart_disconnect_account\");
if(disconnectBtn) disconnectBtn.addEventListener(\"click\", async () => {
const ok = window.confirm(\"Disconnect this site from current AI Smart onboarding credentials? You can reconnect anytime.\");
if (!ok) return;
try {
const data = await post(\"aismart_onboarding_disconnect\");
log(\"disconnect ok\", data);
window.location.href = window.location.pathname + \"?page=aismart-token-onboarding\";
} catch(e) {
log(\"disconnect failed\", e);
alert(\"Disconnect failed. Please try again.\");
}
});
// Dev Helpers
const signupBtn = document.getElementById(\"aismart_signup\");
if(signupBtn) signupBtn.addEventListener(\"click\", async () => { try { const data = await post(\"aismart_onboarding_signup\"); if (data.exchange_code) setVal(\"aismart_exchange_code\", data.exchange_code); log(\"signup ok\", data); } catch(e){ log(\"signup failed\", e); } });
const connectBtn = document.getElementById(\"aismart_connect\");
if(connectBtn) connectBtn.addEventListener(\"click\", async () => { try { const data = await post(\"aismart_onboarding_connect_existing\"); if (data.exchange_code) setVal(\"aismart_exchange_code\", data.exchange_code); log(\"connect ok\", data); } catch(e){ log(\"connect failed\", e); } });
const googleBtn = document.getElementById(\"aismart_google_signup\");
if(googleBtn) googleBtn.addEventListener(\"click\", async () => { try { const data = await post(\"aismart_onboarding_google_redirect\"); log(\"google redirect ok\", data); if (data.redirect_url) { const popup = window.open(data.redirect_url, \"_blank\", \"noopener,noreferrer\"); if(!(popup && !popup.closed) && statusEl) { statusEl.innerHTML = \"Popup was blocked. Click here to open sign-in in a new tab .\"; } } } catch(e){ log(\"google redirect failed\", e); } });
const exchangeBtn = document.getElementById(\"aismart_exchange\");
if(exchangeBtn) exchangeBtn.addEventListener(\"click\", async () => { try { const data = await post(\"aismart_onboarding_exchange_code\"); log(\"exchange ok\", data); } catch(e){ log(\"exchange failed\", e); } });
const siteRegBtn = document.getElementById(\"aismart_site_register\");
if(siteRegBtn) siteRegBtn.addEventListener(\"click\", async () => {
try {
const multilangChoice = selectedMultilangChoice();
const payload = multilangChoice ? { multilang_choice: multilangChoice, aismart_lang_debug: multilangChoice } : {};
const data = await post(\"aismart_onboarding_site_register\", payload);
log(\"site_register ok\", data);
} catch(e){
log(\"site_register failed\", e);
}
});
})();
";
aismart_enqueue_inline_script('aismart-onboarding-inline', $onboarding_inline_js);
}
function aismart_canonical_api_base() {
return 'https://aismartcontent.co';
}
function aismart_plugin_get_api_base() {
return aismart_canonical_api_base();
}
function aismart_force_api_base_option($pre = null) {
return aismart_canonical_api_base();
}
function aismart_force_api_base_option_update($new_value, $old_value = null) {
return aismart_canonical_api_base();
}
add_filter('pre_option_aismart_plugin_api_base', 'aismart_force_api_base_option', 9999, 1);
add_filter('pre_update_option_aismart_plugin_api_base', 'aismart_force_api_base_option_update', 9999, 2);
function aismart_plugin_get_signing_secret() {
if (defined('AISMART_PLUGIN_SIGNING_SECRET') && (string) AISMART_PLUGIN_SIGNING_SECRET !== '') {
return (string) AISMART_PLUGIN_SIGNING_SECRET;
}
return (string) get_option('aismart_plugin_signing_secret', '');
}
function aismart_onboarding_current_admin_email() {
$current = wp_get_current_user();
if ($current instanceof WP_User && $current->exists() && is_email($current->user_email)) {
return (string) $current->user_email;
}
$fallback = (string) get_option('admin_email', '');
return is_email($fallback) ? $fallback : '';
}
function aismart_onboarding_resolve_connected_email($state) {
$state = is_array($state) ? $state : [];
$context = isset($state['account_context']) && is_array($state['account_context']) ? $state['account_context'] : [];
$payload = isset($context['data']) && is_array($context['data']) ? $context['data'] : $context;
if (isset($payload['connected_user']) && is_array($payload['connected_user'])) {
$email = isset($payload['connected_user']['email']) ? trim((string) $payload['connected_user']['email']) : '';
if (is_email($email)) {
return (string) $email;
}
}
$email = aismart_onboarding_deep_find_scalar($state, [
'connected_email',
'account_email',
'user_email',
'email',
]);
if (is_email($email)) {
return (string) $email;
}
return '';
}
function aismart_onboarding_cache_permission_status() {
$result = [
'can_exec' => false,
'can_proc_open' => false,
'shell_mode' => 'none',
'wp_cli_binary' => '',
'wp_cli_detected' => false,
'wp_cli_runnable' => false,
'wp_cli_output_preview' => '',
'auto_cache_clear_ready' => false,
];
$result['can_exec'] = function_exists('aismart_can_use_exec') ? (bool) aismart_can_use_exec() : false;
$result['can_proc_open'] = function_exists('aismart_can_use_proc_open') ? (bool) aismart_can_use_proc_open() : false;
if ($result['can_exec']) {
$result['shell_mode'] = 'exec';
} elseif ($result['can_proc_open']) {
$result['shell_mode'] = 'proc_open';
}
$candidates = ['/usr/local/bin/wp', '/usr/bin/wp', 'wp'];
foreach ($candidates as $candidate) {
if ($candidate === 'wp') {
if (!$result['can_exec'] && !$result['can_proc_open']) {
continue;
}
$result['wp_cli_binary'] = 'wp';
$result['wp_cli_detected'] = true;
break;
}
if (is_file($candidate) && is_executable($candidate)) {
$result['wp_cli_binary'] = $candidate;
$result['wp_cli_detected'] = true;
break;
}
}
if (!$result['wp_cli_detected']) {
return $result;
}
if (!function_exists('aismart_shell_run_capture')) {
return $result;
}
$wpPath = rtrim((string) ABSPATH, '/');
$wpCli = (string) $result['wp_cli_binary'];
$phpPrefix = aismart_wp_cli_php_env_prefix();
$base = $phpPrefix . escapeshellcmd($wpCli) . ' --path=' . escapeshellarg($wpPath);
$output = [];
$code = 1;
$ran = aismart_shell_run_capture($base . ' --version 2>&1', $output, $code);
$result['wp_cli_output_preview'] = substr(preg_replace('/\s+/u', ' ', implode("\n", (array) $output)), 0, 280);
if ($ran && (int) $code === 0) {
$result['wp_cli_runnable'] = true;
$result['auto_cache_clear_ready'] = true;
return $result;
}
$rootHint = strtolower((string) $result['wp_cli_output_preview']);
if (strpos($rootHint, 'allow-root') !== false || strpos($rootHint, 'run this as root') !== false) {
$retryOutput = [];
$retryCode = 1;
$retryRan = aismart_shell_run_capture($base . ' --allow-root --version 2>&1', $retryOutput, $retryCode);
$result['wp_cli_output_preview'] = substr(preg_replace('/\s+/u', ' ', implode("\n", (array) $retryOutput)), 0, 280);
if ($retryRan && (int) $retryCode === 0) {
$result['wp_cli_runnable'] = true;
$result['auto_cache_clear_ready'] = true;
return $result;
}
}
return $result;
}
function aismart_wp_cli_php_env_prefix() {
$candidates = [
'/usr/local/bin/php',
'/opt/cpanel/ea-php82/root/usr/bin/php',
'/opt/cpanel/ea-php81/root/usr/bin/php',
'/usr/bin/php',
];
foreach ($candidates as $php) {
if (is_file($php) && is_executable($php)) {
// quick check if it is CLI? No, just trust order for now.
return 'WP_CLI_PHP=' . escapeshellarg($php) . ' ';
}
}
return '';
}
function aismart_onboarding_deep_find_scalar($data, $keys) {
if (!is_array($keys) || empty($keys)) {
return '';
}
if (is_array($data)) {
foreach ($keys as $key) {
if (isset($data[$key]) && !is_array($data[$key])) {
$value = trim((string) $data[$key]);
if ($value !== '') {
return $value;
}
}
}
foreach ($data as $item) {
if (is_array($item)) {
$value = aismart_onboarding_deep_find_scalar($item, $keys);
if ($value !== '') {
return $value;
}
}
}
}
return '';
}
function aismart_onboarding_deep_find_int($data, $keys) {
$value = aismart_onboarding_deep_find_scalar($data, $keys);
if ($value === '') {
return 0;
}
return (int) $value;
}
function aismart_onboarding_resolve_state_user_id($state = null) {
$state = is_array($state) ? $state : aismart_onboarding_state_get();
if (isset($state['user_id']) && is_numeric($state['user_id'])) {
return (int) $state['user_id'];
}
return 0;
}
function aismart_onboarding_sync_account_context($state = null) {
$state = is_array($state) ? $state : aismart_onboarding_state_get();
$resolved_user_id = aismart_onboarding_resolve_state_user_id($state);
$preferred_workspace_id = isset($state['workspace_id']) ? (int) $state['workspace_id'] : 0;
if ($preferred_workspace_id <= 0) {
$preferred_workspace_id = (int) get_option('aismart_workspace_id', 0);
}
$preserve_preferred_workspace = $preferred_workspace_id > 0 && !empty($state['workspace_switched_at']);
$context_payload = [];
if ($resolved_user_id > 0) {
$context_payload['user_id'] = $resolved_user_id;
}
$context = aismart_plugin_api_request('GET', 'api/plugin/v1/account/context', $context_payload, false);
if (!is_wp_error($context) && is_array($context)) {
$state['account_context'] = $context;
$state['account_context_at'] = gmdate('c');
$payload = isset($context['data']) && is_array($context['data']) ? $context['data'] : $context;
$active = isset($payload['active_workspace']) && is_array($payload['active_workspace']) ? $payload['active_workspace'] : [];
$active_id = isset($active['id']) ? (int) $active['id'] : 0;
if ($active_id > 0) {
$state['active_workspace_id'] = $active_id;
if (!$preserve_preferred_workspace || $preferred_workspace_id <= 0 || $preferred_workspace_id === $active_id) {
$state['workspace_id'] = $active_id;
update_option('aismart_workspace_id', $active_id, false);
}
}
$active_name = isset($active['name']) ? sanitize_text_field((string) $active['name']) : '';
if ($active_name !== '') {
if (!$preserve_preferred_workspace || $preferred_workspace_id <= 0 || $preferred_workspace_id === $active_id) {
$state['workspace_name'] = $active_name;
update_option('aismart_workspace_name', $active_name, false);
}
}
$connected_user = isset($payload['connected_user']) && is_array($payload['connected_user']) ? $payload['connected_user'] : [];
$connected_user_id = isset($connected_user['id']) ? (int) $connected_user['id'] : 0;
if ($connected_user_id > 0) {
$state['user_id'] = $connected_user_id;
$resolved_user_id = $connected_user_id;
}
$word_remaining = null;
$image_remaining = null;
if (!empty($connected_user) && $active_id > 0 && isset($payload['member_word_remaining']) && is_numeric($payload['member_word_remaining'])) {
$word_remaining = (float) $payload['member_word_remaining'];
}
if (!empty($connected_user) && $active_id > 0 && isset($payload['member_image_remaining']) && is_numeric($payload['member_image_remaining'])) {
$image_remaining = (float) $payload['member_image_remaining'];
}
if (!empty($connected_user) && $active_id > 0 && $word_remaining === null) {
foreach (['wp_api_units', 'wp_api_units_remaining', 'credits_available', 'credit_balance', 'workspace_credits'] as $credit_key) {
if (isset($payload[$credit_key]) && is_numeric($payload[$credit_key])) {
$word_remaining = (float) $payload[$credit_key];
break;
}
}
}
if ($word_remaining !== null) {
$state['member_word_remaining'] = $word_remaining;
update_option('aismart_credit_balance', $word_remaining, false);
update_option('aismart_credits_available', $word_remaining, false);
if ($active_id > 0) {
update_option('aismart_workspace_credits_' . $active_id, $word_remaining, false);
}
}
if ($image_remaining !== null) {
$state['member_image_remaining'] = $image_remaining;
}
}
$workspaces_payload = [];
if ($resolved_user_id > 0) {
$workspaces_payload['user_id'] = $resolved_user_id;
}
$workspaces = aismart_plugin_api_request('GET', 'api/plugin/v1/account/workspaces', $workspaces_payload, false);
if (!is_wp_error($workspaces) && is_array($workspaces)) {
$payload = isset($workspaces['data']) && is_array($workspaces['data']) ? $workspaces['data'] : $workspaces;
$list = isset($payload['workspaces']) && is_array($payload['workspaces'])
? $payload['workspaces']
: (is_array($payload) ? $payload : []);
$normalized = [];
foreach ($list as $item) {
if (!is_array($item)) {
continue;
}
$wid = isset($item['workspace_id']) ? (int) $item['workspace_id'] : (isset($item['id']) ? (int) $item['id'] : 0);
if ($wid <= 0) {
continue;
}
$wname = isset($item['workspace_name']) ? (string) $item['workspace_name'] : (isset($item['name']) ? (string) $item['name'] : ('Workspace #' . $wid));
$member_quota = isset($item['member_quota']) && is_array($item['member_quota']) ? $item['member_quota'] : [];
$word_rem = null;
if (isset($member_quota['word_remaining']) && is_numeric($member_quota['word_remaining'])) {
$word_rem = (float) $member_quota['word_remaining'];
} elseif (isset($member_quota['member_word_remaining']) && is_numeric($member_quota['member_word_remaining'])) {
$word_rem = (float) $member_quota['member_word_remaining'];
} elseif (isset($item['member_word_remaining']) && is_numeric($item['member_word_remaining'])) {
$word_rem = (float) $item['member_word_remaining'];
}
if ($word_rem === null) {
foreach (['wp_api_units', 'wp_api_units_remaining', 'credits_available', 'credit_balance', 'workspace_credits'] as $credit_key) {
if (isset($member_quota[$credit_key]) && is_numeric($member_quota[$credit_key])) {
$word_rem = (float) $member_quota[$credit_key];
break;
}
if (isset($item[$credit_key]) && is_numeric($item[$credit_key])) {
$word_rem = (float) $item[$credit_key];
break;
}
}
}
$normalized_item = [
'workspace_id' => $wid,
'workspace_name' => sanitize_text_field($wname),
];
if ($word_rem !== null) {
$normalized_item['credit_balance'] = $word_rem;
update_option('aismart_workspace_credits_' . $wid, $word_rem, false);
}
if (!empty($item['is_current']) || !empty($item['is_active'])) {
$state['active_workspace_id'] = $wid;
if (!$preserve_preferred_workspace || $preferred_workspace_id <= 0 || $preferred_workspace_id === $wid) {
$state['workspace_id'] = $wid;
$state['workspace_name'] = sanitize_text_field($wname);
update_option('aismart_workspace_id', $wid, false);
update_option('aismart_workspace_name', sanitize_text_field($wname), false);
if ($word_rem !== null) {
update_option('aismart_credit_balance', $word_rem, false);
update_option('aismart_credits_available', $word_rem, false);
}
}
}
$normalized[] = $normalized_item;
}
if (!empty($normalized)) {
$state['account_workspaces'] = $normalized;
$state['account_workspaces_at'] = gmdate('c');
}
}
aismart_onboarding_state_set($state);
return $state;
}
function aismart_onboarding_finalize_bootstrap_from_token($bootstrap_token) {
$bootstrap_token = is_string($bootstrap_token) ? sanitize_text_field($bootstrap_token) : '';
if ($bootstrap_token === '') {
return null;
}
$result = aismart_plugin_public_api_request('GET', 'api/plugin/v1/bootstrap/status', [
'bootstrap_token' => $bootstrap_token,
], false);
if (is_wp_error($result) || !is_array($result)) {
return null;
}
$state = aismart_onboarding_state_get();
$state['bootstrap_status'] = $result;
$state['bootstrap_polled_at'] = gmdate('c');
$bootstrap_payload = isset($result['data']) && is_array($result['data']) ? $result['data'] : $result;
$bootstrap_state_raw = isset($bootstrap_payload['state']) && is_array($bootstrap_payload['state']) ? $bootstrap_payload['state'] : [];
$bootstrap_state = !empty($bootstrap_state_raw) ? $bootstrap_state_raw : $bootstrap_payload;
$status_value = strtolower(trim((string) ($bootstrap_state['status'] ?? $bootstrap_payload['status'] ?? '')));
$is_ready_flag = !empty($bootstrap_payload['ready']) || !empty($bootstrap_payload['is_ready']) || !empty($bootstrap_state['ready']) || !empty($bootstrap_state['is_ready']);
$is_ready_status = in_array($status_value, ['ready', 'completed', 'success', 'authenticated', 'connected'], true);
$is_ready = $is_ready_flag || $is_ready_status;
if ($is_ready) {
$signing_secret = aismart_onboarding_deep_find_scalar($bootstrap_state, ['signing_secret', 'secret', 'plugin_signing_secret']);
if ($signing_secret === '') {
$signing_secret = aismart_onboarding_deep_find_scalar($bootstrap_payload, ['signing_secret', 'secret', 'plugin_signing_secret']);
}
$exchange_code = aismart_onboarding_deep_find_scalar($bootstrap_state, ['exchange_code', 'code', 'plugin_exchange_code']);
if ($exchange_code === '') {
$exchange_code = aismart_onboarding_deep_find_scalar($bootstrap_payload, ['exchange_code', 'code', 'plugin_exchange_code']);
}
if ($signing_secret !== '') {
update_option('aismart_plugin_signing_secret', $signing_secret, false);
$state['signing_secret_auto_saved'] = 1;
}
if ($exchange_code !== '') {
$state['exchange_code'] = $exchange_code;
}
}
aismart_onboarding_state_set($state);
return $state;
}
function aismart_onboarding_try_auto_complete_state($state) {
$state = is_array($state) ? $state : [];
$exchange_code = isset($state['exchange_code']) ? trim((string) $state['exchange_code']) : '';
if ($exchange_code === '') {
$exchange_code = aismart_onboarding_deep_find_scalar($state, ['exchange_code', 'code', 'plugin_exchange_code']);
if ($exchange_code !== '') {
$state['exchange_code'] = $exchange_code;
}
}
$signing_secret = aismart_plugin_get_signing_secret();
if ($signing_secret === '') {
$signing_secret = aismart_onboarding_deep_find_scalar($state, ['signing_secret', 'secret', 'plugin_signing_secret']);
if ($signing_secret !== '') {
update_option('aismart_plugin_signing_secret', $signing_secret, false);
$state['signing_secret_auto_saved'] = 1;
}
}
if ($exchange_code === '' || $signing_secret === '') {
aismart_onboarding_state_set($state);
return $state;
}
$has_access_token = isset($state['access_token']) && trim((string) $state['access_token']) !== '';
if (!$has_access_token) {
$exchange_payload = [
'exchange_code' => $exchange_code,
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
];
$exchange_result = aismart_plugin_api_request('POST', 'api/plugin/v1/onboarding/exchange-code', $exchange_payload, true);
if (!is_wp_error($exchange_result) && is_array($exchange_result)) {
$state['access_token'] = isset($exchange_result['access_token']) ? (string) $exchange_result['access_token'] : '';
$state['token_type'] = isset($exchange_result['token_type']) ? (string) $exchange_result['token_type'] : 'Bearer';
$state['workspace_id'] = isset($exchange_result['workspace_id']) ? (int) $exchange_result['workspace_id'] : (isset($state['workspace_id']) ? (int) $state['workspace_id'] : 0);
$state['user_id'] = isset($exchange_result['user_id']) ? (int) $exchange_result['user_id'] : (isset($state['user_id']) ? (int) $state['user_id'] : 0);
$state['exchange_result'] = $exchange_result;
$state['exchange_at'] = gmdate('c');
}
}
if (!empty($state['workspace_id']) && empty($state['site_registered_at'])) {
$multilang = aismart_onboarding_detect_multilang_provider();
$site_payload = [
'workspace_id' => (int) $state['workspace_id'],
'site_url' => home_url('/'),
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
'admin_email' => aismart_onboarding_current_admin_email(),
'plugin_version' => aismart_plugin_get_version(),
'multilang_provider' => $multilang['primary'],
'multilang_detected' => $multilang['detected'],
];
$site_result = aismart_plugin_api_request('POST', 'api/plugin/v1/site/register', $site_payload, true);
if (!is_wp_error($site_result)) {
$state['site_register'] = $site_result;
$state['site_registered_at'] = gmdate('c');
}
}
aismart_onboarding_state_set($state);
return $state;
}
function aismart_onboarding_detect_multilang_provider() {
$detected = [];
if (!function_exists('is_plugin_active')) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$is_active = static function(array $plugin_files): bool {
foreach ($plugin_files as $plugin_file) {
if (function_exists('is_plugin_active') && is_plugin_active($plugin_file)) {
return true;
}
if (function_exists('is_plugin_active_for_network') && is_plugin_active_for_network($plugin_file)) {
return true;
}
}
return false;
};
if (
$is_active(['sitepress-multilingual-cms/sitepress.php'])
|| $is_active(['wpml-media-translation/plugin.php', 'wpml-string-translation/plugin.php', 'woocommerce-multilingual/wpml-woocommerce.php'])
|| defined('ICL_SITEPRESS_VERSION')
|| class_exists('SitePress', false)
) {
$detected[] = 'wpml';
}
if (
$is_active(['polylang/polylang.php', 'polylang-pro/polylang.php'])
|| defined('POLYLANG_VERSION')
|| function_exists('pll_current_language')
) {
$detected[] = 'polylang';
}
if (
$is_active(['translatepress-multilingual/index.php'])
|| defined('TRP_PLUGIN_VERSION')
|| function_exists('trp_get_languages')
) {
$detected[] = 'translatepress';
}
if (
$is_active(['bogo/bogo.php'])
|| defined('BOGO_VERSION')
) {
$detected[] = 'bogo';
}
if (
$is_active(['multilingual-press/multilingual-press.php', 'multilingual-press/multilingualpress.php', 'multilingualpress/multilingualpress.php'])
|| class_exists('Inpsyde\MultilingualPress\MultilingualPress', false)
|| class_exists('Multilingual_Press', false)
|| function_exists('mlp_init')
) {
$detected[] = 'multilingualpress';
}
if (
$is_active(['qtranslate-x/qtranslate.php', 'qtranslate-xt/qtranslate.php'])
|| defined('QTX_VERSION')
) {
$detected[] = 'qtranslate';
}
if (
$is_active(['wpglobus/wpglobus.php'])
|| class_exists('WPGlobus', false)
) {
$detected[] = 'wpglobus';
}
if (
$is_active(['falang/falang.php'])
|| class_exists('Falang', false)
) {
$detected[] = 'falang';
}
if (
$is_active(['sublanguage/sublanguage.php'])
|| class_exists('Sublanguage_Main', false)
|| class_exists('Sublanguage_core', false)
|| isset($GLOBALS['sublanguage'])
) {
$detected[] = 'sublanguage';
}
if (empty($detected)) {
$detected[] = 'none';
}
return [
'primary' => (string) $detected[0],
'detected' => array_values(array_unique($detected)),
];
}
function aismart_onboarding_prefetch_bootstrap($admin_email) {
$admin_email = is_string($admin_email) ? sanitize_email($admin_email) : '';
/*
* IMPORTANT SAFETY NOTE (V2.9.4):
* Do NOT reintroduce any hard requirement that WP admin email must match the signed-in AISmart account email.
* Mixed-account onboarding is intentionally supported. Email is optional metadata only.
* Any future change that enforces email equality here will break valid customer onboarding flows.
*/
$payload = [
'site_url' => home_url('/'),
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
'wp_return_url' => admin_url('admin.php?page=aismart-token-onboarding'),
];
// Keep email optional for bootstrap so sign-in can use any valid cloud account email.
if (is_email($admin_email)) {
$payload['admin_email'] = $admin_email;
}
$result = aismart_plugin_public_api_request('POST', 'api/plugin/v1/bootstrap/init', $payload, true);
if (is_wp_error($result) || !is_array($result)) {
return [];
}
return $result;
}
function aismart_api_make_request_id($prefix = 'wp') {
$prefix = sanitize_key((string) $prefix);
if ($prefix === '') {
$prefix = 'wp';
}
if (function_exists('wp_generate_uuid4')) {
return $prefix . '-' . wp_generate_uuid4();
}
return $prefix . '-' . md5($prefix . '|' . microtime(true) . '|' . wp_rand());
}
function aismart_api_should_retry_transport_error($message) {
$message = strtolower(trim((string) $message));
if ($message === '') {
return false;
}
$needles = [
'timed out',
'timeout',
'curl error 28',
'connection reset',
'connection refused',
'could not resolve host',
'ssl connect error',
];
foreach ($needles as $needle) {
if (strpos($message, $needle) !== false) {
return true;
}
}
return false;
}
function aismart_api_should_retry_http_status($status_code) {
return in_array((int) $status_code, [429, 503, 504], true);
}
function aismart_api_retry_after_seconds($attempt, $retry_after_header = '') {
$retry_after_header = trim((string) $retry_after_header);
if ($retry_after_header !== '' && ctype_digit($retry_after_header)) {
$from_header = (int) $retry_after_header;
if ($from_header > 0) {
return min(60, $from_header);
}
}
$attempt = max(1, (int) $attempt);
$base = min(60, max(1, (int) pow(2, $attempt - 1)));
$jitter = mt_rand(0, 1000) / 1000;
return min(60, $base + $jitter);
}
function aismart_plugin_api_request($method, $path, $payload = [], $use_idempotency = false, $idempotency_key = '') {
$method = strtoupper((string) $method);
$api_base = aismart_plugin_get_api_base();
$secret = aismart_plugin_get_signing_secret();
if ($secret === '') {
return new WP_Error('missing_secret', 'Signing secret is empty. Save config first.');
}
$request_id = aismart_api_make_request_id('plugin');
$nonce = wp_generate_uuid4();
$timestamp = (string) time();
$path = ltrim((string) $path, '/');
$body = '';
$url = $api_base . '/' . $path;
if ($method === 'GET') {
if (!empty($payload) && is_array($payload)) {
$url = add_query_arg($payload, $url);
}
} else {
if (is_array($payload) && empty($payload['request_id'])) {
$payload['request_id'] = $request_id;
}
$body = wp_json_encode($payload);
if (!is_string($body)) {
$body = '';
}
}
$body_hash = hash('sha256', $body);
$signature_payload = $method . "\n" . $path . "\n" . $timestamp . "\n" . $nonce . "\n" . $body_hash;
$signature = hash_hmac('sha256', $signature_payload, $secret);
$headers = [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Aismart-Timestamp' => $timestamp,
'X-Aismart-Nonce' => $nonce,
'X-Aismart-Signature' => $signature,
'X-Request-Id' => $request_id,
];
if ($use_idempotency) {
$key = trim((string) $idempotency_key);
if ($key === '') {
$key = 'wp-' . md5($path . '|' . $request_id . '|' . wp_rand());
}
$headers['X-Idempotency-Key'] = $key;
}
$args = [
'timeout' => 40,
'redirection' => 0,
'method' => $method,
'headers' => $headers,
'sslverify' => true,
];
if ($method !== 'GET') {
$args['body'] = $body;
}
$max_attempts = ($method === 'GET' || $use_idempotency) ? 3 : 1;
$last_error = null;
for ($attempt = 1; $attempt <= $max_attempts; $attempt++) {
$response = wp_remote_request($url, $args);
if (is_wp_error($response)) {
$last_error = $response;
if ($attempt < $max_attempts && aismart_api_should_retry_transport_error($response->get_error_message())) {
$sleep_seconds = aismart_api_retry_after_seconds($attempt);
usleep((int) ($sleep_seconds * 1000000));
continue;
}
return $response;
}
$code = (int) wp_remote_retrieve_response_code($response);
$raw = (string) wp_remote_retrieve_body($response);
$json = json_decode($raw, true);
if ($code >= 400) {
if ($attempt < $max_attempts && aismart_api_should_retry_http_status($code)) {
$retry_after = (string) wp_remote_retrieve_header($response, 'retry-after');
$sleep_seconds = aismart_api_retry_after_seconds($attempt, $retry_after);
usleep((int) ($sleep_seconds * 1000000));
continue;
}
if (!is_array($json)) {
$json = ['message' => trim($raw)];
}
return new WP_Error('api_error', 'API request failed', ['status' => $code, 'response' => $json, 'raw' => $raw]);
}
if (!is_array($json)) {
$last_error = new WP_Error('invalid_json', 'Invalid API JSON response', ['status' => $code, 'raw' => $raw]);
if ($attempt < $max_attempts) {
$sleep_seconds = aismart_api_retry_after_seconds($attempt);
usleep((int) ($sleep_seconds * 1000000));
continue;
}
return $last_error;
}
return $json;
}
return ($last_error instanceof WP_Error)
? $last_error
: new WP_Error('api_error', 'API request failed');
}
function aismart_plugin_public_api_request($method, $path, $payload = [], $use_idempotency = false) {
$method = strtoupper((string) $method);
$api_base = aismart_plugin_get_api_base();
$path = ltrim((string) $path, '/');
$request_id = aismart_api_make_request_id('public');
$body = '';
$url = $api_base . '/' . $path;
if ($method === 'GET') {
if (!empty($payload) && is_array($payload)) {
$url = add_query_arg($payload, $url);
}
} else {
if (is_array($payload) && empty($payload['request_id'])) {
$payload['request_id'] = $request_id;
}
$body = wp_json_encode($payload);
if (!is_string($body)) {
$body = '';
}
}
$headers = [
'Content-Type' => 'application/json',
'Accept' => 'application/json',
'X-Request-Id' => $request_id,
];
if ($use_idempotency) {
$headers['X-Idempotency-Key'] = 'wp-public-' . md5($path . '|' . $request_id . '|' . wp_rand());
}
$args = [
'timeout' => 40,
'redirection' => 0,
'method' => $method,
'headers' => $headers,
'sslverify' => true,
];
if ($method !== 'GET') {
$args['body'] = $body;
}
$max_attempts = ($method === 'GET' || $use_idempotency) ? 3 : 1;
$last_error = null;
for ($attempt = 1; $attempt <= $max_attempts; $attempt++) {
$response = wp_remote_request($url, $args);
if (is_wp_error($response)) {
$last_error = $response;
if ($attempt < $max_attempts && aismart_api_should_retry_transport_error($response->get_error_message())) {
$sleep_seconds = aismart_api_retry_after_seconds($attempt);
usleep((int) ($sleep_seconds * 1000000));
continue;
}
return $response;
}
$code = (int) wp_remote_retrieve_response_code($response);
$raw = (string) wp_remote_retrieve_body($response);
$json = json_decode($raw, true);
if ($code >= 400) {
if ($attempt < $max_attempts && aismart_api_should_retry_http_status($code)) {
$retry_after = (string) wp_remote_retrieve_header($response, 'retry-after');
$sleep_seconds = aismart_api_retry_after_seconds($attempt, $retry_after);
usleep((int) ($sleep_seconds * 1000000));
continue;
}
if (!is_array($json)) {
$json = ['message' => trim($raw)];
}
return new WP_Error('api_error', 'API request failed', ['status' => $code, 'response' => $json, 'raw' => $raw]);
}
if (!is_array($json)) {
$last_error = new WP_Error('invalid_json', 'Invalid API JSON response', ['status' => $code, 'raw' => $raw]);
if ($attempt < $max_attempts) {
$sleep_seconds = aismart_api_retry_after_seconds($attempt);
usleep((int) ($sleep_seconds * 1000000));
continue;
}
return $last_error;
}
return $json;
}
return ($last_error instanceof WP_Error)
? $last_error
: new WP_Error('api_error', 'API request failed');
}
function aismart_onboarding_state_get() {
$state = get_option('aismart_plugin_onboarding_state', []);
return is_array($state) ? $state : [];
}
function aismart_onboarding_state_set($state) {
update_option('aismart_plugin_onboarding_state', is_array($state) ? $state : [], false);
}
function aismart_onboarding_is_ready() {
$state = aismart_onboarding_state_get();
$workspace_id = isset($state['workspace_id']) ? (int) $state['workspace_id'] : 0;
if ($workspace_id <= 0) {
return new WP_Error('onboarding_workspace_missing', 'Plugin onboarding incomplete: workspace is missing.');
}
$access_token = isset($state['access_token']) ? (string) $state['access_token'] : '';
if ($access_token === '') {
return new WP_Error('onboarding_token_missing', 'Plugin onboarding incomplete: access token is missing.');
}
return true;
}
function aismart_onboarding_guard_ajax() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
check_ajax_referer('aismart_onboarding_nonce', 'nonce');
}
add_action('wp_ajax_aismart_onboarding_save_config', 'aismart_onboarding_save_config');
function aismart_onboarding_save_config() {
aismart_onboarding_guard_ajax();
$api_base = 'https://aismartcontent.co';
$signing_secret = isset($_POST['signing_secret']) ? sanitize_text_field(wp_unslash($_POST['signing_secret'])) : '';
$selected_provider = isset($_POST['multilang_choice'])
? sanitize_text_field(wp_unslash($_POST['multilang_choice']))
: (isset($_POST['aismart_lang_debug']) ? sanitize_text_field(wp_unslash($_POST['aismart_lang_debug'])) : '');
$selected_provider = aismart_normalize_provider_key($selected_provider);
$allowed_provider = ['wpml', 'polylang', 'translatepress', 'wpglobus', 'bogo'];
if (!in_array($selected_provider, $allowed_provider, true)) {
$selected_provider = '';
}
if (defined('AISMART_PLUGIN_SIGNING_SECRET') && (string) AISMART_PLUGIN_SIGNING_SECRET !== '') {
$signing_secret = (string) AISMART_PLUGIN_SIGNING_SECRET;
}
if ($signing_secret === 'AISMART_PLUGIN_SIGNING_SECRET') {
wp_send_json_error(['message' => 'Please set a real signing secret. Placeholder value is not allowed.'], 422);
}
update_option('aismart_plugin_api_base', $api_base, false);
if ($signing_secret !== '') {
update_option('aismart_plugin_signing_secret', $signing_secret, false);
}
$state = aismart_onboarding_state_get();
$switch_result = [];
if ($selected_provider !== '') {
$switch_result = aismart_onboarding_apply_provider_group_switch($selected_provider);
$state['multilang_provider'] = $selected_provider;
$state['multilang_selected_at'] = gmdate('c');
$state['multilang_switch'] = $switch_result;
}
$state['config_saved_at'] = gmdate('c');
$env_sync = aismart_sync_embedded_env_file();
if (!empty($env_sync['ok'])) {
$state['env_synced_at'] = gmdate('c');
unset($state['env_sync_error']);
} else {
$state['env_sync_error'] = isset($env_sync['message']) ? (string) $env_sync['message'] : 'env sync failed';
}
aismart_onboarding_state_set($state);
wp_send_json_success([
'message' => 'Config saved',
'state' => $state,
'selected_provider' => $selected_provider,
'plugin_switch' => $switch_result,
'env_sync' => [
'ok' => !empty($env_sync['ok']),
'message' => isset($env_sync['message']) ? (string) $env_sync['message'] : '',
],
]);
}
add_action('wp_ajax_aismart_onboarding_disconnect', 'aismart_onboarding_disconnect');
function aismart_onboarding_disconnect() {
aismart_onboarding_guard_ajax();
$state = aismart_onboarding_state_get();
$workspace_id = isset($state['workspace_id']) ? (int) $state['workspace_id'] : 0;
$user_id = isset($state['user_id']) ? (int) $state['user_id'] : 0;
$payload = [
'site_url' => home_url('/'),
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
];
if ($workspace_id > 0) {
$payload['workspace_id'] = $workspace_id;
}
if ($user_id > 0) {
$payload['user_id'] = $user_id;
}
$unlink_result = aismart_plugin_api_request('POST', 'api/plugin/v1/onboarding/unlink', $payload, true);
$unlink_error = is_wp_error($unlink_result) ? $unlink_result->get_error_message() : '';
delete_option('aismart_plugin_signing_secret');
delete_option('aismart_plugin_onboarding_state');
delete_option('aismart_workspace_id');
delete_option('aismart_workspace_name');
delete_option('aismart_credit_balance');
delete_option('aismart_credits_available');
wp_send_json_success([
'message' => $unlink_error === ''
? 'Disconnected and unlinked from cloud. Local onboarding credentials were cleared.'
: 'Disconnected locally. Cloud unlink returned: ' . $unlink_error,
'cloud_unlink_ok' => $unlink_error === '',
]);
}
add_action('wp_ajax_aismart_onboarding_check_admin', 'aismart_onboarding_check_admin');
function aismart_onboarding_check_admin() {
aismart_onboarding_guard_ajax();
$admin_email = aismart_onboarding_current_admin_email();
if (!is_email($admin_email)) {
wp_send_json_error(['message' => 'Valid admin email required'], 422);
}
$payload = [
'admin_email' => $admin_email,
'site_url' => home_url('/'),
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
'wp_return_url' => admin_url('admin.php?page=aismart-token-onboarding'),
];
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/onboarding/check-admin', $payload, true);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message(), 'meta' => $result->get_error_data()], 500);
}
$state = aismart_onboarding_state_get();
$state['check_admin'] = $result;
$state['admin_email'] = $admin_email;
$state['checked_at'] = gmdate('c');
aismart_onboarding_state_set($state);
wp_send_json_success(['message' => 'Admin checked', 'result' => $result, 'state' => $state]);
}
add_action('wp_ajax_aismart_onboarding_bootstrap_init', 'aismart_onboarding_bootstrap_init');
function aismart_onboarding_bootstrap_init() {
aismart_onboarding_guard_ajax();
/*
* IMPORTANT SAFETY NOTE (V2.9.4):
* Never block bootstrap/init because of admin_email validity or email mismatch.
* Signed-in cloud account email does not need to match WordPress admin email.
* Keep admin_email optional to avoid mixed-account onboarding failures.
*/
$admin_email = aismart_onboarding_current_admin_email();
$payload = [
'site_url' => home_url('/'),
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
'wp_return_url' => admin_url('admin.php?page=aismart-token-onboarding'),
];
// Keep email optional for bootstrap so sign-in can use any valid cloud account email.
if (is_email($admin_email)) {
$payload['admin_email'] = $admin_email;
}
$result = aismart_plugin_public_api_request('POST', 'api/plugin/v1/bootstrap/init', $payload, true);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message(), 'meta' => $result->get_error_data()], 500);
}
$state = aismart_onboarding_state_get();
$state['bootstrap'] = $result;
$state['admin_email'] = $admin_email;
$bootstrap_init_payload = isset($result['data']) && is_array($result['data']) ? $result['data'] : $result;
$state['bootstrap_token'] = aismart_onboarding_deep_find_scalar($bootstrap_init_payload, ['bootstrap_token', 'plugin_bootstrap_token']);
$state['bootstrap_init_at'] = gmdate('c');
aismart_onboarding_state_set($state);
$result['state'] = $state;
wp_send_json_success($result);
}
add_action('wp_ajax_aismart_onboarding_bootstrap_status', 'aismart_onboarding_bootstrap_status');
function aismart_onboarding_bootstrap_status() {
aismart_onboarding_guard_ajax();
$posted_token = isset($_POST['bootstrap_token']) ? sanitize_text_field(wp_unslash($_POST['bootstrap_token'])) : '';
$posted_exchange_code = isset($_POST['exchange_code']) ? sanitize_text_field(wp_unslash($_POST['exchange_code'])) : '';
$posted_signing_secret = isset($_POST['signing_secret']) ? sanitize_text_field(wp_unslash($_POST['signing_secret'])) : '';
$state = aismart_onboarding_state_get();
$bootstrap_token = $posted_token !== '' ? $posted_token : (isset($state['bootstrap_token']) ? (string) $state['bootstrap_token'] : '');
if ($bootstrap_token === '') {
wp_send_json_error(['message' => 'Bootstrap token required'], 422);
}
$result = aismart_plugin_public_api_request('GET', 'api/plugin/v1/bootstrap/status', ['bootstrap_token' => $bootstrap_token], false);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message(), 'meta' => $result->get_error_data()], 500);
}
$state['bootstrap_status'] = $result;
$state['bootstrap_polled_at'] = gmdate('c');
$bootstrap_payload = isset($result['data']) && is_array($result['data']) ? $result['data'] : $result;
$bootstrap_state_raw = isset($bootstrap_payload['state']) && is_array($bootstrap_payload['state']) ? $bootstrap_payload['state'] : [];
$bootstrap_state = !empty($bootstrap_state_raw) ? $bootstrap_state_raw : $bootstrap_payload;
$resolved_workspace_id = aismart_onboarding_deep_find_int($bootstrap_state, ['workspace_id', 'workspaceId']);
if ($resolved_workspace_id <= 0) {
$resolved_workspace_id = aismart_onboarding_deep_find_int($bootstrap_payload, ['workspace_id', 'workspaceId']);
}
$state['workspace_id'] = $resolved_workspace_id > 0 ? $resolved_workspace_id : ($state['workspace_id'] ?? 0);
$status_value = strtolower(trim((string) ($bootstrap_state['status'] ?? $bootstrap_payload['status'] ?? '')));
$is_ready_flag = !empty($bootstrap_payload['ready']) || !empty($bootstrap_payload['is_ready']) || !empty($bootstrap_state['ready']) || !empty($bootstrap_state['is_ready']);
$is_ready_status = in_array($status_value, ['ready', 'completed', 'success', 'authenticated', 'connected'], true);
$is_ready = $is_ready_flag || $is_ready_status;
$completed = false;
$finalize_error = '';
if ($is_ready) {
$signing_secret = $posted_signing_secret !== '' ? $posted_signing_secret : aismart_onboarding_deep_find_scalar($bootstrap_state, ['signing_secret', 'secret', 'plugin_signing_secret']);
if ($signing_secret === '') {
$signing_secret = aismart_onboarding_deep_find_scalar($bootstrap_payload, ['signing_secret', 'secret', 'plugin_signing_secret']);
}
$exchange_code = $posted_exchange_code !== '' ? $posted_exchange_code : aismart_onboarding_deep_find_scalar($bootstrap_state, ['exchange_code', 'code', 'plugin_exchange_code']);
if ($exchange_code === '') {
$exchange_code = aismart_onboarding_deep_find_scalar($bootstrap_payload, ['exchange_code', 'code', 'plugin_exchange_code']);
}
if ($signing_secret !== '') {
update_option('aismart_plugin_signing_secret', $signing_secret, false);
$state['signing_secret_auto_saved'] = 1;
}
if ($exchange_code !== '') {
$state['exchange_code'] = $exchange_code;
}
$resolved_user_id = aismart_onboarding_deep_find_int($bootstrap_state, ['user_id', 'userId']);
if ($resolved_user_id <= 0) {
$resolved_user_id = aismart_onboarding_deep_find_int($bootstrap_payload, ['user_id', 'userId']);
}
$state['user_id'] = $resolved_user_id > 0 ? $resolved_user_id : ($state['user_id'] ?? 0);
if ($exchange_code !== '' && $signing_secret !== '') {
$exchange_payload = [
'exchange_code' => $exchange_code,
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
];
$exchange_result = aismart_plugin_api_request('POST', 'api/plugin/v1/onboarding/exchange-code', $exchange_payload, true);
if (!is_wp_error($exchange_result)) {
$state['access_token'] = isset($exchange_result['access_token']) ? (string) $exchange_result['access_token'] : '';
$state['token_type'] = isset($exchange_result['token_type']) ? (string) $exchange_result['token_type'] : 'Bearer';
$state['workspace_id'] = isset($exchange_result['workspace_id']) ? (int) $exchange_result['workspace_id'] : ($state['workspace_id'] ?? 0);
$state['user_id'] = isset($exchange_result['user_id']) ? (int) $exchange_result['user_id'] : ($state['user_id'] ?? 0);
$state['exchange_result'] = $exchange_result;
$state['exchange_at'] = gmdate('c');
if (!empty($state['workspace_id'])) {
$multilang = aismart_onboarding_detect_multilang_provider();
$site_payload = [
'workspace_id' => (int) $state['workspace_id'],
'site_url' => home_url('/'),
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
'admin_email' => aismart_onboarding_current_admin_email(),
'plugin_version' => aismart_plugin_get_version(),
'multilang_provider' => $multilang['primary'],
'multilang_detected' => $multilang['detected'],
];
$site_result = aismart_plugin_api_request('POST', 'api/plugin/v1/site/register', $site_payload, true);
if (!is_wp_error($site_result)) {
$state['site_register'] = $site_result;
$state['site_registered_at'] = gmdate('c');
$completed = true;
} else {
$site_err_data = $site_result->get_error_data();
$site_api_msg = '';
if (is_array($site_err_data) && isset($site_err_data['response']) && is_array($site_err_data['response']) && isset($site_err_data['response']['message'])) {
$site_api_msg = trim((string) $site_err_data['response']['message']);
}
$finalize_error = $site_api_msg !== '' ? $site_api_msg : (string) $site_result->get_error_message();
}
}
} else {
$exchange_err_data = $exchange_result->get_error_data();
$exchange_api_msg = '';
if (is_array($exchange_err_data) && isset($exchange_err_data['response']) && is_array($exchange_err_data['response']) && isset($exchange_err_data['response']['message'])) {
$exchange_api_msg = trim((string) $exchange_err_data['response']['message']);
}
$finalize_error = $exchange_api_msg !== '' ? $exchange_api_msg : (string) $exchange_result->get_error_message();
}
}
}
aismart_onboarding_state_set($state);
wp_send_json_success([
'message' => $completed ? 'Bootstrap completed and plugin connected.' : ($is_ready ? ($finalize_error !== '' ? ('Sign-in completed but finalize failed: ' . $finalize_error) : 'Sign-in completed. Finalizing plugin setup.') : 'Bootstrap status updated.'),
'ready' => $is_ready,
'completed' => $completed,
'auto_completed' => $completed,
'finalize_error' => $finalize_error,
'bootstrap' => $result,
'state' => $state,
]);
}
function aismart_onboarding_signup() {
aismart_onboarding_guard_ajax();
$admin_email = aismart_onboarding_current_admin_email();
$name = isset($_POST['signup_name']) ? sanitize_text_field(wp_unslash($_POST['signup_name'])) : '';
$password = isset($_POST['signup_password']) ? (string) wp_unslash($_POST['signup_password']) : '';
if (!is_email($admin_email) || $name === '' || strlen($password) < 6) {
wp_send_json_error(['message' => 'Signup requires valid email, name, and password (min 6).'], 422);
}
$payload = [
'name' => $name,
'email' => $admin_email,
'password' => $password,
'site_url' => home_url('/'),
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
];
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/onboarding/signup', $payload, true);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message(), 'meta' => $result->get_error_data()], 500);
}
$state = aismart_onboarding_state_get();
$state['signup'] = $result;
$state['exchange_code'] = $result['exchange_code'] ?? '';
$state['workspace_id'] = isset($result['workspace_id']) ? (int) $result['workspace_id'] : 0;
$state['signup_at'] = gmdate('c');
aismart_onboarding_state_set($state);
wp_send_json_success(['message' => 'Signup success', 'exchange_code' => $state['exchange_code'], 'result' => $result, 'state' => $state]);
}
add_action('wp_ajax_aismart_onboarding_connect_existing', 'aismart_onboarding_connect_existing');
function aismart_onboarding_connect_existing() {
aismart_onboarding_guard_ajax();
$admin_email = aismart_onboarding_current_admin_email();
if (!is_email($admin_email)) {
wp_send_json_error(['message' => 'Valid admin email required'], 422);
}
$payload = [
'admin_email' => $admin_email,
'site_url' => home_url('/'),
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
];
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/onboarding/connect', $payload, true);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message(), 'meta' => $result->get_error_data()], 500);
}
$state = aismart_onboarding_state_get();
$state['connect'] = $result;
$state['exchange_code'] = $result['exchange_code'] ?? '';
$state['workspace_id'] = isset($result['workspace_id']) ? (int) $result['workspace_id'] : ($state['workspace_id'] ?? 0);
$state['connect_at'] = gmdate('c');
aismart_onboarding_state_set($state);
wp_send_json_success(['message' => 'Connect exchange code issued', 'exchange_code' => $state['exchange_code'], 'result' => $result, 'state' => $state]);
}
add_action('wp_ajax_aismart_onboarding_google_redirect', 'aismart_onboarding_google_redirect');
function aismart_onboarding_google_redirect() {
aismart_onboarding_guard_ajax();
$state = aismart_onboarding_state_get();
$bootstrap_token = isset($state['bootstrap_token']) ? (string) $state['bootstrap_token'] : '';
if ($bootstrap_token === '' && isset($_POST['bootstrap_token'])) {
$bootstrap_token = sanitize_text_field(wp_unslash($_POST['bootstrap_token']));
}
$query = [
'wp_return_url' => admin_url('admin.php?page=aismart-token-onboarding'),
];
if ($bootstrap_token !== '') {
$query['plugin_bootstrap_token'] = $bootstrap_token;
}
$result = aismart_plugin_api_request('GET', 'api/plugin/v1/onboarding/google/redirect', $query, true);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message(), 'meta' => $result->get_error_data()], 500);
}
$redirect_url = isset($result['redirect_url']) ? (string) $result['redirect_url'] : '';
if ($redirect_url !== '' && strpos($redirect_url, 'http') !== 0) {
$api_base = aismart_plugin_get_api_base();
$redirect_url = rtrim($api_base, '/') . '/' . ltrim($redirect_url, '/');
}
if ($redirect_url !== '') {
$redirect_url = add_query_arg([
'wp_return_url' => admin_url('admin.php?page=aismart-token-onboarding'),
], $redirect_url);
if ($bootstrap_token !== '') {
$redirect_url = add_query_arg([
'plugin_bootstrap_token' => $bootstrap_token,
], $redirect_url);
}
}
$state['google_redirect'] = $redirect_url;
$state['google_redirect_at'] = gmdate('c');
aismart_onboarding_state_set($state);
wp_send_json_success([
'message' => 'Google redirect URL generated',
'redirect_url' => $redirect_url,
'result' => $result,
'state' => $state,
]);
}
add_action('wp_ajax_aismart_onboarding_exchange_code', 'aismart_onboarding_exchange_code');
function aismart_onboarding_exchange_code() {
aismart_onboarding_guard_ajax();
$exchange_code = isset($_POST['exchange_code']) ? sanitize_text_field(wp_unslash($_POST['exchange_code'])) : '';
if ($exchange_code === '') {
wp_send_json_error(['message' => 'Exchange code required'], 422);
}
$payload = [
'exchange_code' => $exchange_code,
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
];
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/onboarding/exchange-code', $payload, true);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message(), 'meta' => $result->get_error_data()], 500);
}
$state = aismart_onboarding_state_get();
$state['access_token'] = isset($result['access_token']) ? (string) $result['access_token'] : '';
$state['token_type'] = isset($result['token_type']) ? (string) $result['token_type'] : 'Bearer';
$state['workspace_id'] = isset($result['workspace_id']) ? (int) $result['workspace_id'] : ($state['workspace_id'] ?? 0);
$state['user_id'] = isset($result['user_id']) ? (int) $result['user_id'] : 0;
$state['exchange_at'] = gmdate('c');
aismart_onboarding_state_set($state);
// Try to sync connected-user context and workspace allocations from cloud.
$state = aismart_onboarding_sync_account_context($state);
wp_send_json_success(['message' => 'Exchange success', 'result' => $result, 'state' => $state]);
}
add_action('wp_ajax_aismart_onboarding_site_register', 'aismart_onboarding_site_register');
function aismart_onboarding_site_register() {
aismart_onboarding_guard_ajax();
$state = aismart_onboarding_state_get();
$workspace_id = isset($state['workspace_id']) ? (int) $state['workspace_id'] : 0;
if ($workspace_id <= 0) {
wp_send_json_error(['message' => 'Workspace not found. Run signup/connect and exchange first.'], 422);
}
$selected = isset($_POST['multilang_choice'])
? sanitize_text_field(wp_unslash($_POST['multilang_choice']))
: (isset($_POST['aismart_lang_debug']) ? sanitize_text_field(wp_unslash($_POST['aismart_lang_debug'])) : '');
$selected = aismart_normalize_provider_key($selected);
$allowed = ['wpml', 'polylang', 'translatepress', 'bogo', 'wpglobus', 'none'];
$detected = aismart_onboarding_detect_multilang_provider();
$switch_result = [];
if (in_array($selected, $allowed, true) && $selected !== 'none') {
$switch_result = aismart_onboarding_apply_provider_group_switch($selected);
}
$provider = in_array($selected, $allowed, true) ? $selected : (string) ($detected['primary'] ?? 'none');
$payload = [
'workspace_id' => $workspace_id,
'site_url' => home_url('/'),
'site_domain' => wp_parse_url(home_url('/'), PHP_URL_HOST),
'admin_email' => aismart_onboarding_current_admin_email(),
'plugin_version' => aismart_plugin_get_version(),
'multilang_provider' => $provider,
'multilang_detected' => (array) ($detected['detected'] ?? []),
];
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/site/register', $payload, true);
if (is_wp_error($result)) {
wp_send_json_error(['message' => $result->get_error_message(), 'meta' => $result->get_error_data()], 500);
}
$state['site_register'] = $result;
$state['site_registered_at'] = gmdate('c');
$state['multilang_provider'] = $provider;
$state['multilang_detected'] = (array) ($detected['detected'] ?? []);
if (!empty($switch_result)) {
$state['multilang_switch'] = $switch_result;
}
aismart_onboarding_state_set($state);
wp_send_json_success([
'message' => 'Site registered',
'result' => $result,
'state' => $state,
'plugin_switch' => $switch_result,
]);
}
function aismart_token_usage_page() {
// Keep Content Manager inside wp-admin without iframe or redirect.
$ctx = aismart_resolve_workspace_context();
$workspace_id = isset($ctx['workspace_id']) ? (int) $ctx['workspace_id'] : 0;
$credit_runtime = aismart_compute_workspace_live_credit($workspace_id, (float) $ctx['credit_balance'], 10);
$live_credit = isset($credit_runtime['live']) ? (float) $credit_runtime['live'] : (float) $ctx['credit_balance'];
$manager_url = add_query_arg([
'cb' => time(),
'workspace' => $workspace_id,
'credits_available' => $live_credit,
'wp_api_units' => $live_credit,
'wp_ajax_url' => admin_url('admin-ajax.php'),
'source' => 'wordpress-plugin',
], plugins_url('aismart-core/public/admin/token/create', __FILE__));
$fetch_manager = static function ($url) {
return wp_remote_get($url, [
'timeout' => 45,
'redirection' => 3,
'sslverify' => true,
'headers' => [
'Accept' => 'text/html,application/xhtml+xml',
],
]);
};
$remote = $fetch_manager($manager_url);
$status_code = is_wp_error($remote) ? 0 : (int) wp_remote_retrieve_response_code($remote);
$html = is_wp_error($remote) ? '' : (string) wp_remote_retrieve_body($remote);
$remote_ok = !is_wp_error($remote) && $status_code >= 200 && $status_code < 300 && $html !== '';
$auto_repair_attempted = false;
$auto_repair_result = [];
if (!$remote_ok) {
$auto_repair_attempted = true;
$auto_repair_result = aismart_sync_embedded_env_file();
if (!empty($auto_repair_result['ok'])) {
$manager_url = add_query_arg('cb', time() + 1, $manager_url);
$remote = $fetch_manager($manager_url);
$status_code = is_wp_error($remote) ? 0 : (int) wp_remote_retrieve_response_code($remote);
$html = is_wp_error($remote) ? '' : (string) wp_remote_retrieve_body($remote);
$remote_ok = !is_wp_error($remote) && $status_code >= 200 && $status_code < 300 && $html !== '';
}
}
echo '';
echo '
Content Manager ';
if ($remote_ok && $auto_repair_attempted) {
echo '
';
echo 'Auto-recovery applied. Embedded environment was refreshed from WordPress config and Content Manager loaded successfully.';
echo '
';
}
if (!$remote_ok) {
$cloud_manager_url = add_query_arg([
'source' => 'wordpress-plugin',
'workspace' => $workspace_id,
'credits_available' => $live_credit,
], 'https://aismartcontent.co');
$onboarding_url = add_query_arg([
'page' => 'aismart-token-onboarding',
'setup' => 'env-reconfigure',
], admin_url('admin.php'));
$reconfigure_url = wp_nonce_url(
add_query_arg([
'action' => 'aismart_reconfigure_embedded_env',
'return' => admin_url('admin.php?page=aismart-token'),
], admin_url('admin-post.php')),
'aismart_reconfigure_embedded_env'
);
$reason = '';
if (is_wp_error($remote)) {
$reason = $remote->get_error_message();
} elseif ($status_code > 0) {
$reason = 'HTTP status: ' . $status_code;
} else {
$reason = 'Unknown response from embedded Content Manager.';
}
echo '
';
echo '
Inline Content Manager is temporarily unavailable.
';
echo '
' . esc_html($reason) . '
';
if ($auto_repair_attempted) {
if (!empty($auto_repair_result['ok'])) {
echo '
Auto-repair ran, but this page still failed. Use the reconfigure button below.
';
} else {
echo '
Auto-repair failed: ' . esc_html(isset($auto_repair_result['message']) ? (string) $auto_repair_result['message'] : 'unknown error') . '
';
}
}
echo '
Reconfigure Embedded Environment Open Onboarding Open Content Manager In AISmart Cloud
';
echo '
';
echo '
';
return;
}
$head_assets = '';
$body_html = $html;
if (preg_match('/]*>(.*?)<\/head>/is', $html, $head_match)) {
$head_assets = (string) $head_match[1];
$head_assets = preg_replace('/ ]*>/i', '', $head_assets);
$head_assets = preg_replace('/]*>.*?<\/title>/is', '', $head_assets);
$head_assets = is_string($head_assets) ? $head_assets : '';
}
if (preg_match('/ ]*>(.*?)<\/body>/is', $html, $body_match)) {
$body_html = (string) $body_match[1];
}
echo '';
if ($head_assets !== '') {
echo $head_assets; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted same-site plugin HTML assets
}
echo '
';
echo $body_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- trusted same-site plugin HTML
echo '
';
echo '
';
echo '';
}
function aismart_token_user_management_page() {
$ctx = aismart_resolve_workspace_context();
$workspace_id = isset($ctx['workspace_id']) ? (int) $ctx['workspace_id'] : 0;
$workspace_label = isset($ctx['workspace_label']) ? sanitize_text_field((string) $ctx['workspace_label']) : '';
$nonce = wp_create_nonce('aismart_user_management_nonce');
$workspace_title = $workspace_label !== '' ? $workspace_label : ('Workspace #' . $workspace_id);
$sync_auto_accept_default = (int) get_option('aismart_user_sync_auto_accept', 1) === 1;
$manage_users_url = 'https://aismartcontent.co/dashboard/user/team';
$owner_ctx = aismart_onboarding_resolve_workspace_owner_context($workspace_id, $workspace_title);
$is_workspace_owner = !empty($owner_ctx['is_owner']);
$owner_display = trim((string) ($owner_ctx['owner_name'] ?? ''));
$owner_email = trim((string) ($owner_ctx['owner_email'] ?? ''));
if ($owner_display === '' && $owner_email !== '') {
$owner_display = $owner_email;
}
if ($owner_display === '') {
$owner_display = 'the workspace owner of ' . $workspace_title;
}
$non_owner_notice = sprintf(
'Since you are not the owner of this workspace, you can open that panel but you may not see team users there. Please ask %s to open that link and manage users for you.',
$owner_display
);
?>
User Management
Sync WordPress users to AISmart workspace team members from this page.
Workspace:
Workspace ID:
/>
Sync mode: Auto-accept
Sync Selected
Sync All Users
Refresh List
Manage Users
Workspace owner notice:
/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function setStatus(msg, type){
if(!statusEl) return;
statusEl.textContent = msg || '';
statusEl.style.color = (type === 'error') ? '#b32d2e' : ((type === 'success') ? '#1f7a1f' : ((type === 'warn') ? '#996800' : '#1d2327'));
}
function formBody(action, data){
var p = new URLSearchParams();
p.set('action', action);
p.set('nonce', nonce);
Object.keys(data || {}).forEach(function(k){ p.set(k, data[k]); });
return p.toString();
}
function post(action, data){
return fetch(ajaxUrl, {
method: 'POST',
credentials: 'same-origin',
headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'},
body: formBody(action, data || {})
}).then(function(r){
return r.text().then(function(txt){
try {
return JSON.parse(txt);
} catch (e) {
throw new Error('Invalid JSON response: ' + txt.slice(0, 180));
}
});
});
}
function updateBulkState(){
if(syncSelectedBtn){
syncSelectedBtn.disabled = selectedIds.size === 0;
}
if(selectAll){
var checks = tbody ? tbody.querySelectorAll('.aismart-user-check:not([disabled])') : [];
if(!checks.length){
selectAll.checked = false;
selectAll.indeterminate = false;
return;
}
var checked = 0;
checks.forEach(function(c){ if(c.checked) checked++; });
selectAll.checked = checked > 0 && checked === checks.length;
selectAll.indeterminate = checked > 0 && checked < checks.length;
}
}
function bindSelection(){
var checks = tbody ? tbody.querySelectorAll('.aismart-user-check') : [];
checks.forEach(function(box){
box.addEventListener('change', function(){
var id = parseInt(box.getAttribute('data-user-id') || '0', 10) || 0;
if(id <= 0){ return; }
if(box.checked){ selectedIds.add(id); } else { selectedIds.delete(id); }
updateBulkState();
});
});
}
function bindRowSync(){
var buttons = document.querySelectorAll('.aismart-sync-user');
buttons.forEach(function(btn){
btn.addEventListener('click', function(){
var userId = parseInt(btn.getAttribute('data-user-id') || '0', 10) || 0;
if(userId <= 0 || btn.disabled){ return; }
btn.disabled = true;
setStatus('Syncing user #' + userId + ' ...');
post('aismart_sync_workspace_user', {user_id: userId, auto_accept: getAutoAccept()})
.then(function(json){
if(json && json.success){
setStatus((json.data && json.data.message) ? json.data.message : 'User synced', 'success');
loadUsers();
} else {
var msg = (json && json.data && json.data.message) ? json.data.message : 'Sync failed';
setStatus(msg, 'error');
btn.disabled = false;
}
})
.catch(function(err){
setStatus(err && err.message ? err.message : 'Sync request failed', 'error');
btn.disabled = false;
});
});
});
}
function renderRows(users){
if(!tbody){ return; }
selectedIds = new Set();
if(!Array.isArray(users) || !users.length){
tbody.innerHTML = '
No users found. ';
updateBulkState();
return;
}
tbody.innerHTML = users.map(function(u){
var role = (Array.isArray(u.roles) && u.roles.length) ? u.roles[0] : 'subscriber';
var isSynced = !!u.is_synced;
var lastSync = u.last_sync_at || '-';
var statusClass = isSynced ? 'color:#1f7a1f;' : 'color:#b32d2e;';
var statusText = isSynced ? 'Synced' : 'Not synced';
var btnDisabled = isSynced ? 'disabled' : '';
var btnLabel = isSynced ? 'Synced' : 'Sync';
var checkboxDisabled = isSynced ? 'disabled' : '';
return '' +
'
' +
' ' +
'' + esc(u.id) + ' ' +
'' + esc(u.name) + ' ' +
'' + esc(u.email) + ' ' +
'' + esc(role) + ' ' +
'' + esc(u.registered_at || '-') + ' ' +
'' + esc(lastSync) + '' + esc(u.sync_message || '') + ' ' +
'' + esc(statusText) + ' ' +
'' + esc(btnLabel) + ' ' +
' ';
}).join('');
bindSelection();
bindRowSync();
updateBulkState();
}
function loadUsers(){
setStatus('Loading users...');
post('aismart_get_user_management_data', {})
.then(function(json){
if(json && json.success && json.data){
renderRows(json.data.users || []);
setStatus('Loaded ' + ((json.data.users || []).length) + ' users.');
} else {
var msg = (json && json.data && json.data.message) ? json.data.message : 'Failed to load users';
setStatus(msg, 'error');
if(tbody){ tbody.innerHTML = '
' + esc(msg) + ' '; }
updateBulkState();
}
})
.catch(function(err){
var msg = err && err.message ? err.message : 'Failed to load users';
setStatus(msg, 'error');
if(tbody){ tbody.innerHTML = '
' + esc(msg) + ' '; }
updateBulkState();
});
}
if(syncAllBtn){
syncAllBtn.addEventListener('click', function(){
syncAllBtn.disabled = true;
setStatus('Syncing all users...');
post('aismart_sync_workspace_users', {auto_accept: getAutoAccept()})
.then(function(json){
if(json && json.success){
var d = json.data || {};
setStatus((d.message || 'Sync done') + ' (ok: ' + (d.success_count || 0) + ', failed: ' + (d.failed_count || 0) + ')', 'success');
loadUsers();
} else {
var msg = (json && json.data && json.data.message) ? json.data.message : 'Sync all failed';
setStatus(msg, 'error');
}
})
.catch(function(err){
setStatus(err && err.message ? err.message : 'Sync all request failed', 'error');
})
.finally(function(){ syncAllBtn.disabled = false; });
});
}
if(syncSelectedBtn){
syncSelectedBtn.addEventListener('click', function(){
var ids = Array.from(selectedIds);
if(!ids.length){
setStatus('Select at least one user first.', 'error');
return;
}
syncSelectedBtn.disabled = true;
setStatus('Syncing selected users (' + ids.length + ')...');
post('aismart_sync_workspace_users', {user_ids: ids.join(','), auto_accept: getAutoAccept()})
.then(function(json){
if(json && json.success){
var d = json.data || {};
setStatus((d.message || 'Sync done') + ' (ok: ' + (d.success_count || 0) + ', failed: ' + (d.failed_count || 0) + ')', 'success');
loadUsers();
} else {
var msg = (json && json.data && json.data.message) ? json.data.message : 'Sync selected failed';
setStatus(msg, 'error');
}
})
.catch(function(err){
setStatus(err && err.message ? err.message : 'Sync selected request failed', 'error');
})
.finally(function(){ syncSelectedBtn.disabled = false; updateBulkState(); });
});
}
if(selectAll){
selectAll.addEventListener('change', function(){
var checks = tbody ? tbody.querySelectorAll('.aismart-user-check:not([disabled])') : [];
selectedIds = new Set();
checks.forEach(function(box){
box.checked = !!selectAll.checked;
if(selectAll.checked){
var id = parseInt(box.getAttribute('data-user-id') || '0', 10) || 0;
if(id > 0){ selectedIds.add(id); }
}
});
updateBulkState();
});
}
if(refreshBtn){
refreshBtn.addEventListener('click', function(){ loadUsers(); });
}
if(manageUsersBtn){
if(!isWorkspaceOwner){
var ownerWarnText = nonOwnerNotice || 'Only the workspace owner can fully manage team users in that panel.';
manageUsersBtn.setAttribute('title', ownerWarnText);
manageUsersBtn.setAttribute('aria-label', ownerWarnText);
}
manageUsersBtn.addEventListener('click', function(){
if(!manageUsersUrl){
setStatus('Manage Users link is unavailable right now.', 'error');
return;
}
var opened = window.open(manageUsersUrl, '_blank', 'noopener,noreferrer');
if(!(opened && !opened.closed)){
window.location.href = manageUsersUrl;
}
if(isWorkspaceOwner){
setStatus('Opened Manage Users panel in AISmart Cloud.', 'success');
} else {
var warnText = nonOwnerNotice || 'You can open that panel, but only the workspace owner can manage team users there.';
setStatus(warnText, 'warn');
window.alert(warnText);
}
});
}
if(modeToggle){
modeToggle.addEventListener('change', function(){
updateModeLabel();
setStatus(getAutoAccept() === 1 ? 'Sync mode: Auto-accept' : 'Sync mode: Invite-style (waiting/pending).');
});
}
updateModeLabel();
loadUsers();
})();
AISMART_USER_MANAGEMENT_JS;
aismart_enqueue_inline_script(
'aismart-user-management-inline',
str_replace(
['__AISMART_AJAX_URL__', '__AISMART_USER_MGMT_NONCE__', '__AISMART_MANAGE_USERS_URL__', '__AISMART_IS_WORKSPACE_OWNER__', '__AISMART_NON_OWNER_NOTICE__'],
[
wp_json_encode(admin_url('admin-ajax.php')),
wp_json_encode($nonce),
wp_json_encode($manage_users_url),
wp_json_encode($is_workspace_owner ? 1 : 0),
wp_json_encode($non_owner_notice),
],
$user_management_inline_js
)
); ?>
$owner_id,
'owner_email' => $owner_email,
'owner_name' => $owner_name,
];
}
function aismart_onboarding_resolve_workspace_owner_context($workspace_id = 0, $workspace_label = '') {
$workspace_id = (int) $workspace_id;
$workspace_label = sanitize_text_field((string) $workspace_label);
$state = aismart_onboarding_state_get();
$connected_email = aismart_onboarding_resolve_connected_email($state);
$admin_email = aismart_onboarding_current_admin_email();
$effective_email = '';
if (is_email($connected_email)) {
$effective_email = $connected_email;
} elseif (is_email($admin_email)) {
$effective_email = $admin_email;
}
$connected_user_id = isset($state['user_id']) && is_numeric($state['user_id']) ? (int) $state['user_id'] : 0;
$context = isset($state['account_context']) && is_array($state['account_context']) ? $state['account_context'] : [];
$payload = isset($context['data']) && is_array($context['data']) ? $context['data'] : $context;
if ($connected_user_id <= 0 && isset($payload['connected_user']) && is_array($payload['connected_user']) && isset($payload['connected_user']['id']) && is_numeric($payload['connected_user']['id'])) {
$connected_user_id = (int) $payload['connected_user']['id'];
}
$owner_meta = [
'owner_id' => 0,
'owner_email' => '',
'owner_name' => '',
];
$active_workspace = isset($payload['active_workspace']) && is_array($payload['active_workspace']) ? $payload['active_workspace'] : [];
$active_workspace_id = isset($active_workspace['id']) && is_numeric($active_workspace['id']) ? (int) $active_workspace['id'] : 0;
if ($workspace_id > 0 && $active_workspace_id === $workspace_id && !empty($active_workspace)) {
$owner_meta = aismart_onboarding_extract_workspace_owner_meta($active_workspace);
}
if ($owner_meta['owner_id'] <= 0 && $owner_meta['owner_email'] === '') {
$workspace_list = isset($payload['workspaces']) && is_array($payload['workspaces']) ? $payload['workspaces'] : [];
foreach ($workspace_list as $workspace_item) {
if (!is_array($workspace_item)) {
continue;
}
$item_id = 0;
if (isset($workspace_item['workspace_id']) && is_numeric($workspace_item['workspace_id'])) {
$item_id = (int) $workspace_item['workspace_id'];
} elseif (isset($workspace_item['id']) && is_numeric($workspace_item['id'])) {
$item_id = (int) $workspace_item['id'];
}
if ($workspace_id > 0 && $item_id !== $workspace_id) {
continue;
}
$meta = aismart_onboarding_extract_workspace_owner_meta($workspace_item);
if ($meta['owner_id'] > 0 || $meta['owner_email'] !== '') {
$owner_meta = $meta;
break;
}
}
}
$is_owner = true;
if ($owner_meta['owner_id'] > 0 && $connected_user_id > 0) {
$is_owner = $owner_meta['owner_id'] === $connected_user_id;
} elseif ($owner_meta['owner_email'] !== '' && $effective_email !== '') {
$is_owner = strcasecmp($owner_meta['owner_email'], $effective_email) === 0;
}
if ($owner_meta['owner_name'] === '') {
$owner_meta['owner_name'] = $workspace_label !== '' ? ($workspace_label . ' owner') : 'workspace owner';
}
return [
'is_owner' => $is_owner,
'owner_id' => (int) $owner_meta['owner_id'],
'owner_email' => (string) $owner_meta['owner_email'],
'owner_name' => (string) $owner_meta['owner_name'],
'connected_user_id' => $connected_user_id,
'connected_email' => (string) $connected_email,
'effective_email' => (string) $effective_email,
];
}
add_action('wp_ajax_aismart_get_user_management_data', 'aismart_get_user_management_data');
function aismart_get_user_management_data() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
check_ajax_referer('aismart_user_management_nonce', 'nonce');
$ctx = aismart_resolve_workspace_context();
$active_workspace_id = (int) ($ctx['workspace_id'] ?? 0);
$users = get_users([
'orderby' => 'registered',
'order' => 'DESC',
'number' => 300,
]);
$rows = [];
if (is_array($users)) {
foreach ($users as $user) {
if (!($user instanceof WP_User)) {
continue;
}
$last_sync_at = (string) get_user_meta($user->ID, '_aismart_user_sync_at', true);
$sync_status = (string) get_user_meta($user->ID, '_aismart_user_sync_status', true);
$sync_message = (string) get_user_meta($user->ID, '_aismart_user_sync_message', true);
$sync_workspace_id = (int) get_user_meta($user->ID, '_aismart_user_sync_workspace_id', true);
$is_synced = ($sync_status === 'success' && $active_workspace_id > 0 && $sync_workspace_id === $active_workspace_id);
$rows[] = [
'id' => (int) $user->ID,
'name' => (string) $user->display_name,
'email' => (string) $user->user_email,
'roles' => array_values((array) $user->roles),
'registered_at' => (string) $user->user_registered,
'last_sync_at' => $last_sync_at,
'sync_status' => $sync_status,
'sync_message' => $sync_message,
'sync_workspace_id' => $sync_workspace_id,
'is_synced' => $is_synced ? 1 : 0,
];
}
}
wp_send_json_success([
'workspace_id' => (int) ($ctx['workspace_id'] ?? 0),
'workspace_label' => (string) ($ctx['workspace_label'] ?? ''),
'users' => $rows,
]);
}
function aismart_user_management_parse_auto_accept($request_key = 'auto_accept') {
if (!isset($_POST[$request_key])) {
return null;
}
$raw = wp_unslash($_POST[$request_key]);
$bool = filter_var($raw, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
if ($bool === null) {
return 0;
}
return $bool ? 1 : 0;
}
function aismart_user_management_sync_single_user($user_id, $ctx = [], $auto_accept = null) {
$user_id = (int) $user_id;
if ($user_id <= 0) {
return [
'ok' => false,
'message' => 'Invalid user id',
];
}
$user = get_user_by('id', $user_id);
if (!($user instanceof WP_User)) {
return [
'ok' => false,
'message' => 'User not found',
];
}
$workspace_id = isset($ctx['workspace_id']) ? (int) $ctx['workspace_id'] : 0;
if ($workspace_id <= 0) {
$resolved = aismart_resolve_workspace_context();
$workspace_id = isset($resolved['workspace_id']) ? (int) $resolved['workspace_id'] : 0;
}
if ($workspace_id <= 0) {
return [
'ok' => false,
'message' => 'Workspace is not connected',
];
}
if ($auto_accept === null) {
$auto_accept = (int) get_option('aismart_user_sync_auto_accept', 1) === 1 ? 1 : 0;
} else {
$auto_accept = ((int) $auto_accept) === 1 ? 1 : 0;
}
$auto_accept = apply_filters('aismart_user_sync_auto_accept', $auto_accept === 1, $workspace_id, $user_id) ? 1 : 0;
$payload = [
'workspace_id' => $workspace_id,
'site_url' => home_url('/'),
'site_domain' => (string) wp_parse_url(home_url('/'), PHP_URL_HOST),
'auto_accept' => $auto_accept,
'wp_user' => [
'id' => (int) $user->ID,
'email' => (string) $user->user_email,
'name' => (string) $user->display_name,
'roles' => array_values((array) $user->roles),
'registered_at' => (string) $user->user_registered,
],
];
$endpoints = [
'api/plugin/v1/workspace/team/sync-user',
'api/plugin/v1/team/sync-user',
'api/plugin/v1/workspace/users/sync-user',
'api/plugin/v1/user-management/sync-user',
];
$last_error = null;
foreach ($endpoints as $endpoint) {
$result = aismart_plugin_api_request('POST', $endpoint, $payload, true);
if (!is_wp_error($result)) {
$result_payload = isset($result['data']) && is_array($result['data']) ? $result['data'] : (is_array($result) ? $result : []);
$succeeded = isset($result_payload['succeeded']) ? (int) $result_payload['succeeded'] : (isset($result_payload['success_count']) ? (int) $result_payload['success_count'] : 0);
$failed = isset($result_payload['failed']) ? (int) $result_payload['failed'] : (isset($result_payload['failed_count']) ? (int) $result_payload['failed_count'] : 0);
$ok_flag = array_key_exists('ok', $result_payload) ? (bool) $result_payload['ok'] : null;
if ($ok_flag === false || ($failed > 0 && $succeeded <= 0)) {
$message = isset($result_payload['message']) ? (string) $result_payload['message'] : 'Sync request failed on platform';
$last_error = new WP_Error('sync_user_failed', $message, ['response' => $result]);
continue;
}
$sync_message = isset($result_payload['message']) ? (string) $result_payload['message'] : ('Synced via ' . $endpoint);
update_user_meta($user_id, '_aismart_user_sync_status', 'success');
update_user_meta($user_id, '_aismart_user_sync_message', $sync_message);
update_user_meta($user_id, '_aismart_user_sync_at', gmdate('c'));
update_user_meta($user_id, '_aismart_user_sync_workspace_id', $workspace_id);
return [
'ok' => true,
'message' => $sync_message,
'endpoint' => $endpoint,
'response' => $result,
];
}
$last_error = $result;
}
$error_message = 'User sync endpoint is not available yet.';
if (is_wp_error($last_error)) {
$error_message = (string) $last_error->get_error_message();
$data = $last_error->get_error_data();
if (is_array($data) && isset($data['status']) && (int) $data['status'] === 404) {
$error_message = 'User sync endpoint not found on main platform (HTTP 404).';
}
}
update_user_meta($user_id, '_aismart_user_sync_status', 'error');
update_user_meta($user_id, '_aismart_user_sync_message', $error_message);
update_user_meta($user_id, '_aismart_user_sync_at', gmdate('c'));
update_user_meta($user_id, '_aismart_user_sync_workspace_id', $workspace_id);
return [
'ok' => false,
'message' => $error_message,
];
}
add_action('wp_ajax_aismart_sync_workspace_user', 'aismart_sync_workspace_user');
function aismart_sync_workspace_user() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
check_ajax_referer('aismart_user_management_nonce', 'nonce');
$user_id = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0;
if ($user_id <= 0) {
wp_send_json_error(['message' => 'Invalid user id'], 422);
}
$auto_accept = aismart_user_management_parse_auto_accept('auto_accept');
if ($auto_accept !== null) {
update_option('aismart_user_sync_auto_accept', $auto_accept ? 1 : 0, false);
}
$ctx = aismart_resolve_workspace_context();
$result = aismart_user_management_sync_single_user($user_id, $ctx, $auto_accept);
if (!empty($result['ok'])) {
wp_send_json_success([
'message' => 'User #' . $user_id . ' synced successfully.',
'result' => $result,
]);
}
wp_send_json_error([
'message' => isset($result['message']) ? (string) $result['message'] : 'Sync failed',
'result' => $result,
], 500);
}
add_action('wp_ajax_aismart_sync_workspace_users', 'aismart_sync_workspace_users');
function aismart_sync_workspace_users() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
check_ajax_referer('aismart_user_management_nonce', 'nonce');
$auto_accept = aismart_user_management_parse_auto_accept('auto_accept');
if ($auto_accept !== null) {
update_option('aismart_user_sync_auto_accept', $auto_accept ? 1 : 0, false);
}
$ctx = aismart_resolve_workspace_context();
$users = [];
$user_ids_raw = isset($_POST['user_ids']) ? (string) wp_unslash($_POST['user_ids']) : '';
if ($user_ids_raw !== '') {
$parts = preg_split('/[^0-9]+/', $user_ids_raw);
$unique = [];
if (is_array($parts)) {
foreach ($parts as $part) {
$id = (int) $part;
if ($id > 0) {
$unique[$id] = $id;
}
}
}
$users = array_values($unique);
if (empty($users)) {
wp_send_json_error(['message' => 'No valid selected users'], 422);
}
} else {
$users = get_users([
'orderby' => 'registered',
'order' => 'DESC',
'number' => 300,
'fields' => 'ID',
]);
}
$success_count = 0;
$failed_count = 0;
$errors = [];
if (is_array($users)) {
foreach ($users as $user_id_raw) {
$user_id = (int) $user_id_raw;
if ($user_id <= 0) {
continue;
}
$result = aismart_user_management_sync_single_user($user_id, $ctx, $auto_accept);
if (!empty($result['ok'])) {
$success_count++;
} else {
$failed_count++;
if (count($errors) < 10) {
$errors[] = [
'user_id' => $user_id,
'message' => isset($result['message']) ? (string) $result['message'] : 'Sync failed',
];
}
}
}
}
$message = 'Sync complete.';
if ($failed_count > 0 && $success_count === 0) {
$message = 'No users synced. Main platform endpoint may be missing.';
} elseif ($failed_count > 0) {
$message = 'Partial sync completed.';
}
wp_send_json_success([
'message' => $message,
'success_count' => $success_count,
'failed_count' => $failed_count,
'errors' => $errors,
]);
}
// AJAX: Get queued topics list
function aismart_topic_queue_normalize_title($title) {
$title = wp_strip_all_tags((string) $title);
// Legacy queues may contain multilingual inline markers like
// [:en]Title[:th]...[:] which should not appear as queue titles.
if (strpos($title, '[:') !== false) {
$preferred_langs = [];
if (function_exists('pll_default_language')) {
$pll_default = sanitize_key((string) pll_default_language('slug'));
if ($pll_default !== '') {
$preferred_langs[] = strtolower($pll_default);
}
}
$site_locale = sanitize_text_field((string) get_locale());
if ($site_locale !== '') {
$site_lang = strtolower(substr(str_replace('_', '-', $site_locale), 0, 2));
if ($site_lang !== '') {
$preferred_langs[] = $site_lang;
}
}
$preferred_langs[] = 'en';
$preferred_langs = array_values(array_unique(array_filter($preferred_langs)));
if (function_exists('aismart_qtranslate_extract_segment')) {
foreach ($preferred_langs as $lang_code) {
$segment = trim((string) aismart_qtranslate_extract_segment($title, (string) $lang_code));
if ($segment !== '') {
$title = $segment;
break;
}
}
}
if (strpos($title, '[:') !== false && preg_match('/\[:[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\](.*?)(?=\[:[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\]|\[:\]|$)/su', $title, $match)) {
$title = isset($match[1]) ? (string) $match[1] : '';
}
$title = preg_replace('/\[:[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\]/u', ' ', (string) $title);
$title = str_replace('[:]', ' ', (string) $title);
}
$title = sanitize_text_field((string) $title);
$title = preg_replace('/\s+/u', ' ', (string) $title);
return trim((string) $title);
}
function aismart_topic_queue_get_items() {
$items = get_option('aismart_topics_queue_v2', []);
$legacy_titles = get_option('aismart_topics_cached', []);
$normalized = [];
$seen = [];
if (is_array($items)) {
foreach ($items as $item) {
if (is_string($item)) {
$title = aismart_topic_queue_normalize_title($item);
if ($title === '') {
continue;
}
$key = function_exists('mb_strtolower') ? mb_strtolower($title) : strtolower($title);
if (isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$normalized[] = [
'title' => $title,
'source' => 'legacy',
'provider' => '',
'written' => 0,
'written_post_id' => 0,
'created_at' => gmdate('c'),
];
continue;
}
if (!is_array($item)) {
continue;
}
$title = aismart_topic_queue_normalize_title($item['title'] ?? '');
if ($title === '') {
continue;
}
$key = function_exists('mb_strtolower') ? mb_strtolower($title) : strtolower($title);
if (isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$normalized[] = [
'title' => $title,
'source' => sanitize_key((string) ($item['source'] ?? 'manual')),
'provider' => aismart_normalize_provider_key($item['provider'] ?? ''),
'written' => !empty($item['written']) ? 1 : 0,
'written_post_id' => max(0, (int) ($item['written_post_id'] ?? 0)),
'created_at' => sanitize_text_field((string) ($item['created_at'] ?? gmdate('c'))),
];
}
}
if (is_array($legacy_titles)) {
foreach ($legacy_titles as $legacy_title) {
$title = aismart_topic_queue_normalize_title($legacy_title);
if ($title === '') {
continue;
}
$key = function_exists('mb_strtolower') ? mb_strtolower($title) : strtolower($title);
if (isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$normalized[] = [
'title' => $title,
'source' => 'legacy',
'provider' => '',
'written' => 0,
'written_post_id' => 0,
'created_at' => gmdate('c'),
];
}
}
return $normalized;
}
function aismart_topic_queue_save_items($items) {
if (!is_array($items)) {
$items = [];
}
// topic-memory-marker
$clean = [];
$titles = [];
$seen = [];
foreach ($items as $item) {
if (!is_array($item)) {
continue;
}
$title = aismart_topic_queue_normalize_title($item['title'] ?? '');
if ($title === '') {
continue;
}
$key = function_exists('mb_strtolower') ? mb_strtolower($title) : strtolower($title);
if (isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$clean[] = [
'title' => $title,
'source' => sanitize_key((string) ($item['source'] ?? 'manual')),
'provider' => aismart_normalize_provider_key($item['provider'] ?? ''),
'written' => !empty($item['written']) ? 1 : 0,
'written_post_id' => max(0, (int) ($item['written_post_id'] ?? 0)),
'created_at' => sanitize_text_field((string) ($item['created_at'] ?? gmdate('c'))),
];
$titles[] = $title;
}
update_option('aismart_topics_queue_v2', array_values($clean), false);
update_option('aismart_topics_cached', array_values($titles), false);
}
function aismart_topic_queue_add_titles($titles, $source = 'manual', $blocked_title_keys = []) {
$source = sanitize_key((string) $source);
if ($source === '') {
$source = 'manual';
}
$items = aismart_topic_queue_get_items();
$index = [];
foreach ($items as $idx => $item) {
$title_key = function_exists('mb_strtolower') ? mb_strtolower((string) $item['title']) : strtolower((string) $item['title']);
$index[$title_key] = $idx;
}
$blocked = [];
$blocked_token_sets = [];
if (is_array($blocked_title_keys)) {
foreach ($blocked_title_keys as $key => $value) {
if (is_int($key)) {
$normalized = aismart_topic_queue_title_key((string) $value);
if ($normalized !== '') {
$blocked[$normalized] = true;
$token_set = aismart_topic_queue_title_tokens($normalized);
if (!empty($token_set)) {
$blocked_token_sets[] = $token_set;
}
}
} else {
$normalized = aismart_topic_queue_title_key((string) $key);
if ($normalized !== '') {
$blocked[$normalized] = true;
$token_set = aismart_topic_queue_title_tokens($normalized);
if (!empty($token_set)) {
$blocked_token_sets[] = $token_set;
}
}
}
}
}
$added = [];
foreach ((array) $titles as $raw_title) {
$title = aismart_topic_queue_normalize_title($raw_title);
if ($title === '') {
continue;
}
$key = function_exists('mb_strtolower') ? mb_strtolower($title) : strtolower($title);
if (isset($index[$key])) {
continue;
}
$normalized_key = aismart_topic_queue_title_key($title);
if ($normalized_key !== '' && isset($blocked[$normalized_key])) {
continue;
}
$candidate_tokens = aismart_topic_queue_title_tokens($title);
if (aismart_topic_queue_is_near_duplicate_tokens($candidate_tokens, $blocked_token_sets)) {
continue;
}
$items[] = [
'title' => $title,
'source' => $source,
'provider' => '',
'written' => 0,
'written_post_id' => 0,
'created_at' => gmdate('c'),
];
$index[$key] = count($items) - 1;
if ($normalized_key !== '') {
$blocked[$normalized_key] = true;
}
if (!empty($candidate_tokens)) {
$blocked_token_sets[] = $candidate_tokens;
}
$added[] = $title;
}
aismart_topic_queue_save_items($items);
return $added;
}
function aismart_topic_queue_mark_written($title, $post_id = 0, $provider = '') {
$title = aismart_topic_queue_normalize_title($title);
if ($title === '') {
return;
}
$provider = aismart_normalize_provider_key($provider);
$items = aismart_topic_queue_get_items();
$target_key = function_exists('mb_strtolower') ? mb_strtolower($title) : strtolower($title);
$changed = false;
$matched_index = -1;
foreach ($items as $index => &$item) {
$item_key = function_exists('mb_strtolower')
? mb_strtolower((string) ($item['title'] ?? ''))
: strtolower((string) ($item['title'] ?? ''));
if ($item_key !== $target_key) {
continue;
}
$item['written'] = 1;
if ((int) $post_id > 0) {
$item['written_post_id'] = (int) $post_id;
}
if ($provider !== '') {
$item['provider'] = $provider;
}
$item['source'] = sanitize_key((string) ($item['source'] ?? 'manual'));
if ($item['source'] === '') {
$item['source'] = 'manual';
}
$item['updated_at'] = gmdate('c');
$matched_index = (int) $index;
$changed = true;
break;
}
unset($item);
if ($changed) {
// Keep recently handled topics at the end so the UI reflects latest manual activity.
if ($matched_index >= 0 && isset($items[$matched_index])) {
$matched_item = $items[$matched_index];
array_splice($items, $matched_index, 1);
$items[] = $matched_item;
}
aismart_topic_queue_save_items($items);
}
}
function aismart_topic_queue_mark_written_by_queue_id($queue_id, $post_id = 0, $provider = '') {
$queue_id = (int) $queue_id;
if ($queue_id <= 0) {
return false;
}
$provider = aismart_normalize_provider_key($provider);
$items = aismart_topic_queue_get_items();
$index = $queue_id - 1;
if (!isset($items[$index]) || !is_array($items[$index])) {
return false;
}
$title = aismart_topic_queue_normalize_title($items[$index]['title'] ?? '');
if ($title === '') {
return false;
}
$items[$index]['title'] = $title;
$items[$index]['written'] = 1;
if ((int) $post_id > 0) {
$items[$index]['written_post_id'] = (int) $post_id;
}
if ($provider !== '') {
$items[$index]['provider'] = $provider;
}
$items[$index]['source'] = sanitize_key((string) ($items[$index]['source'] ?? 'manual'));
if ($items[$index]['source'] === '') {
$items[$index]['source'] = 'manual';
}
$items[$index]['updated_at'] = gmdate('c');
// Keep recently handled topics at the end so the UI reflects latest manual activity.
$matched_item = $items[$index];
array_splice($items, $index, 1);
$items[] = $matched_item;
aismart_topic_queue_save_items($items);
return true;
}
function aismart_topic_queue_title_key($title) {
$title = aismart_topic_queue_normalize_title($title);
if ($title === '') {
return '';
}
if (function_exists('mb_strtolower')) {
$title = mb_strtolower($title);
} else {
$title = strtolower($title);
}
$title = preg_replace('/[^\p{L}\p{N}]+/u', ' ', (string) $title);
$title = preg_replace('/\s+/u', ' ', (string) $title);
return trim((string) $title);
}
function aismart_topic_queue_title_tokens($title) {
$title = aismart_topic_queue_title_key($title);
if ($title === '') {
return [];
}
$stop_words = [
'a', 'an', 'the', 'and', 'or', 'to', 'of', 'in', 'on', 'for', 'with', 'without',
'how', 'about', 'into', 'from', 'by', 'this', 'that', 'these', 'those', 'than',
];
$stop_index = array_fill_keys($stop_words, true);
$parts = preg_split('/\s+/u', $title);
if (!is_array($parts)) {
return [];
}
$tokens = [];
foreach ($parts as $part) {
$part = trim((string) $part);
if ($part === '') {
continue;
}
if (isset($stop_index[$part])) {
continue;
}
if (preg_match('/^\d+$/', $part)) {
continue;
}
if (function_exists('mb_strlen')) {
if (mb_strlen($part) < 2) {
continue;
}
} elseif (strlen($part) < 2) {
continue;
}
$tokens[$part] = true;
}
$unique = array_keys($tokens);
sort($unique, SORT_STRING);
return $unique;
}
function aismart_topic_queue_is_near_duplicate_tokens($candidate_tokens, $known_token_sets) {
if (!is_array($candidate_tokens) || count($candidate_tokens) < 2 || !is_array($known_token_sets)) {
return false;
}
$candidate_map = array_fill_keys($candidate_tokens, true);
$candidate_count = count($candidate_tokens);
foreach ($known_token_sets as $known_tokens) {
if (!is_array($known_tokens) || count($known_tokens) < 2) {
continue;
}
$known_map = array_fill_keys($known_tokens, true);
$intersection = count(array_intersect_key($candidate_map, $known_map));
if ($intersection < 2) {
continue;
}
$known_count = count($known_tokens);
if ($intersection === min($candidate_count, $known_count)) {
return true;
}
$union = $candidate_count + $known_count - $intersection;
if ($union > 0 && ($intersection / $union) >= 0.72) {
return true;
}
}
return false;
}
function aismart_topic_queue_context_focuses($context) {
$context = aismart_topic_queue_title_key($context);
if ($context === '') {
return ['business growth'];
}
$raw_parts = preg_split('/\s*(?:,|;|\||\/|&|\band\b|\bplus\b)\s*/u', $context);
if (!is_array($raw_parts)) {
$raw_parts = [$context];
}
if (count($raw_parts) === 1 && strpos($raw_parts[0], ' ') !== false) {
$tokens = aismart_topic_queue_title_tokens($raw_parts[0]);
if (count($tokens) >= 2 && count($tokens) <= 4) {
$raw_parts = $tokens;
}
}
$focuses = [];
$seen = [];
foreach ($raw_parts as $part) {
$part_tokens = aismart_topic_queue_title_tokens((string) $part);
if (empty($part_tokens)) {
continue;
}
$phrase = implode(' ', array_slice($part_tokens, 0, 3));
if ($phrase === '' || isset($seen[$phrase])) {
continue;
}
$seen[$phrase] = true;
$focuses[] = $phrase;
}
if (count($focuses) >= 2) {
$combo = $focuses[0] . ' and ' . $focuses[1];
if (!isset($seen[$combo])) {
$focuses[] = $combo;
}
}
if (empty($focuses)) {
$focuses[] = $context;
}
return $focuses;
}
function aismart_topic_queue_collect_blocked_title_keys() {
global $wpdb;
$blocked = [];
$items = aismart_topic_queue_get_items();
if (is_array($items)) {
foreach ($items as $item) {
$key = aismart_topic_queue_title_key($item['title'] ?? '');
if ($key !== '') {
$blocked[$key] = true;
}
}
}
$rows = $wpdb->get_col(
"SELECT post_title
FROM {$wpdb->posts}
WHERE post_type = 'post'
AND post_status IN ('publish','draft','pending','future','private')
AND post_title <> ''"
);
if (is_array($rows)) {
foreach ($rows as $title) {
$key = aismart_topic_queue_title_key($title);
if ($key !== '') {
$blocked[$key] = true;
}
}
}
return $blocked;
}
function aismart_topic_queue_locale_code() {
$locale = '';
if (function_exists('determine_locale')) {
$locale = (string) determine_locale();
}
if ($locale === '' && function_exists('get_locale')) {
$locale = (string) get_locale();
}
$locale = strtolower(trim($locale));
if ($locale === '') {
return 'en';
}
$locale = str_replace('_', '-', $locale);
$parts = explode('-', $locale);
$primary = sanitize_key((string) ($parts[0] ?? ''));
return $primary !== '' ? $primary : 'en';
}
function aismart_topic_queue_signature($title, $locale = '', $intent = 'general') {
$title_key = aismart_topic_queue_title_key($title);
$locale = sanitize_key((string) $locale);
if ($locale === '') {
$locale = aismart_topic_queue_locale_code();
}
$intent = sanitize_key((string) $intent);
if ($intent === '') {
$intent = 'general';
}
if ($title_key === '') {
return '';
}
return hash('sha256', $title_key . '|' . $locale . '|' . $intent);
}
function aismart_topic_queue_extract_titles_from_api_result($api_result) {
if (!is_array($api_result)) {
return [];
}
$candidates = [];
$paths = [
['topics'],
['suggestions'],
['claimed_topics'],
['data', 'topics'],
['data', 'suggestions'],
['data', 'claimed_topics'],
['result', 'topics'],
['result', 'suggestions'],
['result', 'claimed_topics'],
];
foreach ($paths as $path) {
$cursor = $api_result;
foreach ($path as $segment) {
if (!is_array($cursor) || !array_key_exists($segment, $cursor)) {
$cursor = null;
break;
}
$cursor = $cursor[$segment];
}
if (is_array($cursor)) {
$candidates[] = $cursor;
}
}
$titles = [];
$seen = [];
foreach ($candidates as $group) {
foreach ($group as $item) {
$title = '';
if (is_string($item)) {
$title = $item;
} elseif (is_array($item)) {
foreach (['topic_title', 'title', 'topic', 'text', 'name'] as $k) {
if (!empty($item[$k]) && is_string($item[$k])) {
$title = $item[$k];
break;
}
}
}
$title = aismart_topic_queue_normalize_title($title);
if ($title === '') {
continue;
}
$key = aismart_topic_queue_title_key($title);
if ($key === '' || isset($seen[$key])) {
continue;
}
$seen[$key] = true;
$titles[] = $title;
}
}
return $titles;
}
function aismart_topic_queue_generate_from_advanced_api($context, $count, $blocked_title_keys = []) {
if (!function_exists('aismart_plugin_api_request')) {
return [];
}
$context = aismart_topic_queue_normalize_title($context);
$count = max(1, (int) $count);
$count = min($count, 100);
$ctx = aismart_resolve_workspace_context();
$state = aismart_onboarding_state_get();
$workspace_id = (int) ($ctx['workspace_id'] ?? 0);
$user_id = isset($state['user_id']) ? (int) $state['user_id'] : 0;
$site_domain = strtolower(trim((string) wp_parse_url(home_url('/'), PHP_URL_HOST)));
$locale = aismart_topic_queue_locale_code();
$strategy_version = 'wp-topic-v2';
$request_id = function_exists('aismart_api_make_request_id')
? aismart_api_make_request_id('topic')
: ('topic-' . md5($context . '|' . microtime(true) . '|' . wp_rand()));
$blocked_titles = [];
$blocked_signatures = [];
if (is_array($blocked_title_keys)) {
foreach ($blocked_title_keys as $key => $value) {
$title_key = is_int($key) ? aismart_topic_queue_title_key((string) $value) : aismart_topic_queue_title_key((string) $key);
if ($title_key === '') {
continue;
}
$blocked_titles[] = $title_key;
$blocked_signatures[] = aismart_topic_queue_signature($title_key, $locale, 'topic');
}
}
$suggest_payload = [
'workspace_id' => $workspace_id,
'user_id' => $user_id,
'site_domain' => $site_domain,
'context' => $context,
'count' => $count,
'locale' => $locale,
'blocked_signatures' => array_values(array_unique(array_filter($blocked_signatures))),
'blocked_titles' => array_values(array_unique(array_filter($blocked_titles))),
'strategy_version' => $strategy_version,
'request_id' => $request_id,
];
$suggest_idempotency = 'wp-topic-suggest-' . md5($request_id . '|' . $site_domain . '|' . $context . '|' . $count);
$suggest_result = aismart_plugin_api_request('POST', 'api/plugin/v1/topics/suggest', $suggest_payload, true, $suggest_idempotency);
if (is_wp_error($suggest_result)) {
return [];
}
$suggested_titles = aismart_topic_queue_extract_titles_from_api_result($suggest_result);
if (empty($suggested_titles)) {
return [];
}
$selected_topics = [];
foreach ($suggested_titles as $title) {
$selected_topics[] = [
'topic_title' => $title,
'topic_signature' => aismart_topic_queue_signature($title, $locale, 'topic'),
];
}
$claim_payload = [
'workspace_id' => $workspace_id,
'user_id' => $user_id,
'site_domain' => $site_domain,
'context' => $context,
'selected_topics' => $selected_topics,
'request_id' => $request_id,
'locale' => $locale,
'strategy_version' => $strategy_version,
];
$claim_idempotency = 'wp-topic-claim-' . md5($request_id . '|' . $site_domain . '|' . $context . '|' . count($selected_topics));
$claim_result = aismart_plugin_api_request('POST', 'api/plugin/v1/topics/claim', $claim_payload, true, $claim_idempotency);
if (is_wp_error($claim_result)) {
$status = (int) $claim_result->get_error_data('status');
if ($status === 409) {
return [];
}
return $suggested_titles;
}
$claimed_titles = aismart_topic_queue_extract_titles_from_api_result($claim_result);
return !empty($claimed_titles) ? $claimed_titles : $suggested_titles;
}
function aismart_topic_queue_send_feedback($event, $titles, $context = '', $strategy_version = 'wp-topic-v2') {
if (!function_exists('aismart_plugin_api_request') || !is_array($titles) || empty($titles)) {
return;
}
$event = sanitize_key((string) $event);
if ($event === '') {
return;
}
$ctx = aismart_resolve_workspace_context();
$state = aismart_onboarding_state_get();
$workspace_id = (int) ($ctx['workspace_id'] ?? 0);
$user_id = isset($state['user_id']) ? (int) $state['user_id'] : 0;
$site_domain = strtolower(trim((string) wp_parse_url(home_url('/'), PHP_URL_HOST)));
$locale = aismart_topic_queue_locale_code();
$context = aismart_topic_queue_normalize_title($context);
$max_feedback = min(25, count($titles));
for ($i = 0; $i < $max_feedback; $i++) {
$title = aismart_topic_queue_normalize_title((string) $titles[$i]);
if ($title === '') {
continue;
}
$request_id = function_exists('aismart_api_make_request_id')
? aismart_api_make_request_id('topicfb')
: ('topicfb-' . md5($title . '|' . microtime(true) . '|' . wp_rand()));
$payload = [
'workspace_id' => $workspace_id,
'site_domain' => $site_domain,
'context' => $context,
'topic_title' => $title,
'topic_signature' => aismart_topic_queue_signature($title, $locale, 'topic'),
'event' => $event,
'request_id' => $request_id,
'user_id' => $user_id,
'locale' => $locale,
'strategy_version' => $strategy_version,
];
$idempotency_key = 'wp-topic-feedback-' . md5($request_id . '|' . $title . '|' . $event);
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/topics/feedback', $payload, true, $idempotency_key);
if (is_wp_error($result)) {
// Best-effort only; queue flow should not fail on feedback write.
continue;
}
}
}
function aismart_topic_queue_generate_from_context($context, $count, $blocked_title_keys = []) {
$context = aismart_topic_queue_normalize_title($context);
$count = (int) $count;
if ($count <= 0) {
$count = 10;
}
$seed = $context !== '' ? $context : 'General Topic';
$focuses = aismart_topic_queue_context_focuses($seed);
$patterns = [
'Beginner guide to %s',
'Top 10 questions about %s',
'Step-by-step guide for %s',
'Common myths about %s and the facts',
'Best practices for %s',
'How to get started with %s',
'Practical checklist for %s',
'Frequently asked questions about %s',
'Beginner mistakes to avoid in %s',
'Real-world framework for %s',
'Key trends shaping %s this year',
'Weekly plan to improve %s',
'30-day learning plan for %s',
'How to explain %s in simple terms',
'What to do first when working on %s',
'Roadmap from basics to advanced %s',
];
$suffixes = [
'',
' for beginners',
' in 2026',
' with examples',
' step by step',
' for content planning',
];
$blocked_phrase_pattern = '/\b(paid\s*ads?|local\s*business)\b/i';
// Keep output deterministic per site/workspace while avoiding identical lists across installations.
$ctx = aismart_resolve_workspace_context();
$state = aismart_onboarding_state_get();
$site_host = wp_parse_url(home_url('/'), PHP_URL_HOST);
$workspace_id = (int) ($ctx['workspace_id'] ?? 0);
$connected_user_id = isset($state['user_id']) ? (int) $state['user_id'] : 0;
$salt = strtolower(trim((string) $site_host)) . '|' . $workspace_id . '|' . $connected_user_id . '|' . gmdate('oW');
$stable_sort = static function ($a, $b) use ($salt) {
$ha = md5($salt . '|' . (string) $a);
$hb = md5($salt . '|' . (string) $b);
return strcmp($ha, $hb);
};
usort($patterns, $stable_sort);
usort($suffixes, $stable_sort);
usort($focuses, $stable_sort);
$topics = [];
$seen = [];
$seen_token_sets = [];
if (is_array($blocked_title_keys)) {
foreach ($blocked_title_keys as $key => $value) {
if (is_int($key)) {
$normalized = aismart_topic_queue_title_key((string) $value);
if ($normalized !== '') {
$seen[$normalized] = true;
$token_set = aismart_topic_queue_title_tokens($normalized);
if (!empty($token_set)) {
$seen_token_sets[] = $token_set;
}
}
} else {
$normalized = aismart_topic_queue_title_key((string) $key);
if ($normalized !== '') {
$seen[$normalized] = true;
$token_set = aismart_topic_queue_title_tokens($normalized);
if (!empty($token_set)) {
$seen_token_sets[] = $token_set;
}
}
}
}
}
$max_attempts = max(120, $count * 40);
for ($attempt = 0; $attempt < $max_attempts && count($topics) < $count; $attempt++) {
$pattern = $patterns[$attempt % count($patterns)];
$suffix = $suffixes[(int) floor($attempt / count($patterns)) % count($suffixes)];
$focus = $focuses[$attempt % count($focuses)];
$base = ucfirst(sprintf($pattern, $focus));
if ($suffix !== '') {
$base = rtrim($base, '.') . $suffix;
}
$key = aismart_topic_queue_title_key($base);
if ($key === '' || isset($seen[$key])) {
continue;
}
if (preg_match($blocked_phrase_pattern, $base)) {
continue;
}
$candidate_tokens = aismart_topic_queue_title_tokens($base);
if (aismart_topic_queue_is_near_duplicate_tokens($candidate_tokens, $seen_token_sets)) {
continue;
}
$seen[$key] = true;
if (!empty($candidate_tokens)) {
$seen_token_sets[] = $candidate_tokens;
}
$topics[] = $base;
}
$fallback_modifiers = [
'for first-time readers',
'for non-technical audiences',
'with simple explanations',
'with practical examples',
'with implementation checklist',
'with myth-vs-fact format',
'with FAQ structure',
'with common pitfalls and fixes',
'with step-by-step actions',
'with clear next steps',
];
$fallback_attempt = 0;
$fallback_max_attempts = max(240, $count * 60);
while (count($topics) < $count && $fallback_attempt < $fallback_max_attempts) {
$modifier = $fallback_modifiers[$fallback_attempt % count($fallback_modifiers)];
$series = (int) floor($fallback_attempt / count($fallback_modifiers)) + 1;
$focus = $focuses[$fallback_attempt % count($focuses)];
$candidate = ucfirst('Practical guide: ' . $modifier . ' using ' . $focus . ' (part ' . $series . ')');
$fallback_attempt++;
$key = aismart_topic_queue_title_key($candidate);
if ($key === '' || isset($seen[$key])) {
continue;
}
if (preg_match($blocked_phrase_pattern, $candidate)) {
continue;
}
$candidate_tokens = aismart_topic_queue_title_tokens($candidate);
if (aismart_topic_queue_is_near_duplicate_tokens($candidate_tokens, $seen_token_sets)) {
continue;
}
$seen[$key] = true;
if (!empty($candidate_tokens)) {
$seen_token_sets[] = $candidate_tokens;
}
$topics[] = $candidate;
}
if (count($topics) < $count) {
$hard_guard = 0;
while (count($topics) < $count && $hard_guard < 200) {
$hard_guard++;
$focus = $focuses[$hard_guard % count($focuses)];
$candidate = ucfirst('Unique topic angle ' . $hard_guard . ': insights for ' . $focus);
$key = aismart_topic_queue_title_key($candidate);
if ($key === '' || isset($seen[$key])) {
continue;
}
if (preg_match($blocked_phrase_pattern, $candidate)) {
continue;
}
$seen[$key] = true;
$topics[] = $candidate;
}
}
return $topics;
}
function aismart_log_token_event($event, $amount, $meta = [], $message = '') {
global $wpdb;
$event = sanitize_key((string) $event);
$amount = (float) $amount;
if ($event === '' || $amount <= 0) {
return false;
}
$ctx = aismart_resolve_workspace_context();
$workspace_id = max(0, (int) ($ctx['workspace_id'] ?? 0));
$state = aismart_onboarding_state_get();
$user_id = isset($state['user_id']) ? (int) $state['user_id'] : 0;
if ($user_id <= 0) {
$user_id = (int) get_current_user_id();
}
if ($user_id <= 0) {
$user_id = 1;
}
$payload = [
'event' => $event,
'amount' => $amount,
'workspace_id' => $workspace_id,
'user_id' => $user_id,
'site_url' => rtrim((string) site_url('/'), '/') . '/',
'site_domain' => (string) wp_parse_url(home_url('/'), PHP_URL_HOST),
'meta' => is_array($meta) ? $meta : [],
'logged_at' => gmdate('c'),
'message' => $message !== '' ? sanitize_text_field($message) : ('Logged event: ' . $event),
];
$cloud_ok = aismart_log_token_event_to_cloud($payload);
$local_ok = aismart_log_token_event_to_local($payload, $event, $amount);
return $cloud_ok || $local_ok;
}
function aismart_token_log_table_name() {
$table = defined('AISMART_TOKEN_LOG_TABLE') ? (string) AISMART_TOKEN_LOG_TABLE : 'token_logs';
$table = preg_replace('/[^a-zA-Z0-9_]/', '', $table);
if ($table === '') {
$table = 'token_logs';
}
return $table;
}
function aismart_log_token_event_to_local($payload, $event, $amount) {
global $wpdb;
$table = aismart_token_log_table_name();
$charset_collate = $wpdb->get_charset_collate();
$created = $wpdb->query(
"CREATE TABLE IF NOT EXISTS {$table} (
id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
workspace_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
user_id BIGINT(20) UNSIGNED NOT NULL DEFAULT 0,
amount DECIMAL(18,4) NOT NULL DEFAULT 0,
case_log LONGTEXT NULL,
type VARCHAR(120) NOT NULL DEFAULT '',
platform VARCHAR(40) NOT NULL DEFAULT 'wordpress',
created_at DATETIME NULL,
updated_at DATETIME NULL,
PRIMARY KEY (id),
KEY idx_workspace_id (workspace_id),
KEY idx_user_id (user_id),
KEY idx_type (type),
KEY idx_created_at (created_at)
) {$charset_collate}"
);
if ($created === false) {
return false;
}
$inserted = $wpdb->insert(
$table,
[
'workspace_id' => (int) ($payload['workspace_id'] ?? 0),
'user_id' => (int) ($payload['user_id'] ?? 0),
'amount' => $amount,
'case_log' => wp_json_encode($payload),
'type' => $event,
'platform' => 'wordpress',
'created_at' => current_time('mysql', true),
'updated_at' => current_time('mysql', true),
],
['%d', '%d', '%f', '%s', '%s', '%s', '%s', '%s']
);
return $inserted !== false;
}
function aismart_usage_log_idempotency_key($request) {
$request = is_array($request) ? $request : [];
$fingerprint = [
'event' => (string) ($request['event'] ?? $request['type'] ?? ''),
'workspace_id' => (int) ($request['workspace_id'] ?? 0),
'user_id' => (int) ($request['user_id'] ?? 0),
'site_domain' => strtolower(trim((string) ($request['site_domain'] ?? ''))),
'amount' => (float) ($request['amount'] ?? 0),
'logged_at' => (string) ($request['logged_at'] ?? ''),
];
return 'wp-usage-' . md5(wp_json_encode($fingerprint));
}
function aismart_log_token_event_to_cloud($payload) {
$payload = is_array($payload) ? $payload : [];
if (empty($payload['event']) || empty($payload['amount'])) {
return false;
}
$state = aismart_onboarding_state_get();
if (empty($state['access_token'])) {
return false;
}
$request = [
'event' => (string) $payload['event'],
'type' => (string) $payload['event'],
'amount' => (float) $payload['amount'],
'workspace_id' => (int) ($payload['workspace_id'] ?? 0),
'user_id' => (int) ($payload['user_id'] ?? 0),
'site_url' => (string) ($payload['site_url'] ?? ''),
'site_domain' => strtolower(trim((string) ($payload['site_domain'] ?? ''))),
'meta' => isset($payload['meta']) && is_array($payload['meta']) ? $payload['meta'] : [],
'message' => (string) ($payload['message'] ?? ''),
'logged_at' => (string) ($payload['logged_at'] ?? gmdate('c')),
'source' => 'wordpress_plugin',
];
$idempotency_key = aismart_usage_log_idempotency_key($request);
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/usage/log', $request, true, $idempotency_key);
return !is_wp_error($result) && is_array($result);
}
add_action('wp_ajax_aismart_queue_topics', 'aismart_queue_topics');
function aismart_queue_topics() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
if (!aismart_require_positive_credit_or_json_error('Topic generation')) {
return;
}
$mode = isset($_POST['mode']) ? sanitize_key(wp_unslash($_POST['mode'])) : 'manual';
$mode = in_array($mode, ['manual', 'ai'], true) ? $mode : 'manual';
if ($mode === 'manual') {
$raw_topics = isset($_POST['manual_topics']) ? (string) wp_unslash($_POST['manual_topics']) : '';
$lines = preg_split('/\r\n|\r|\n/u', $raw_topics);
$titles = [];
if (is_array($lines)) {
foreach ($lines as $line) {
$line = aismart_topic_queue_normalize_title($line);
if ($line !== '') {
$titles[] = $line;
}
}
}
} else {
$context = isset($_POST['topic_context']) ? sanitize_text_field(wp_unslash($_POST['topic_context'])) : '';
$count = isset($_POST['topic_count']) ? (int) $_POST['topic_count'] : 10;
if (!in_array($count, [10, 25, 50, 100], true)) {
$count = 10;
}
$blocked_title_keys = aismart_topic_queue_collect_blocked_title_keys();
$titles = aismart_topic_queue_generate_from_advanced_api($context, $count, $blocked_title_keys);
if (empty($titles)) {
$titles = aismart_topic_queue_generate_from_context($context, $count, $blocked_title_keys);
}
}
if (empty($titles)) {
wp_send_json_error(['message' => 'No valid topics provided'], 422);
}
$added = aismart_topic_queue_add_titles(
$titles,
$mode === 'ai' ? 'ai' : 'manual',
$mode === 'ai' ? ($blocked_title_keys ?? []) : []
);
$added_count = count($added);
if ($mode === 'ai' && $added_count > 0) {
$feedback_context = isset($context) ? (string) $context : '';
aismart_topic_queue_send_feedback('queued', array_values($added), $feedback_context, 'wp-topic-v2');
}
if ($added_count > 0) {
aismart_log_token_event(
'topic_queue_add',
(float) $added_count,
[
'mode' => $mode,
'added' => $added_count,
'sample' => array_slice(array_values($added), 0, 10),
],
'Added topics into queue'
);
}
$all_items = aismart_topic_queue_get_items();
wp_send_json_success([
'added_count' => $added_count,
'added_topics' => $added,
'total_count' => count($all_items),
]);
}
function aismart_normalize_provider_key($provider) {
$provider = sanitize_key((string) $provider);
if ($provider === 'qtranslate-xt') {
return 'qtranslate';
}
return $provider;
}
function aismart_provider_label($provider) {
$provider = aismart_normalize_provider_key($provider);
$labels = [
'wpml' => 'WPML',
'polylang' => 'Polylang',
'translatepress' => 'TranslatePress',
'multilingualpress' => 'MultilingualPress',
'falang' => 'Falang',
'qtranslate' => 'qTranslate',
'sublanguage' => 'Sublanguage',
'wpglobus' => 'WPGlobus',
'bogo' => 'Bogo',
'legacy-falang-polylang' => 'Legacy (Falang/Polylang)',
];
return isset($labels[$provider]) ? $labels[$provider] : '';
}
function aismart_has_polylang_conveythis_conflict() {
if (!function_exists('is_plugin_active')) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$polylang_active = false;
$conveythis_active = false;
if (function_exists('is_plugin_active')) {
$polylang_active = is_plugin_active('polylang/polylang.php')
|| (function_exists('is_plugin_active_for_network') && is_plugin_active_for_network('polylang/polylang.php'));
$conveythis_active = is_plugin_active('conveythis-translate/conveythis-translate.php')
|| (function_exists('is_plugin_active_for_network') && is_plugin_active_for_network('conveythis-translate/conveythis-translate.php'));
}
if (!$polylang_active && function_exists('pll_current_language')) {
$polylang_active = true;
}
if (!$conveythis_active && class_exists('ConveyThis', false)) {
$conveythis_active = true;
}
return $polylang_active && $conveythis_active;
}
function aismart_resolve_selected_provider_key() {
$state = aismart_onboarding_state_get();
$saved = aismart_normalize_provider_key($state['multilang_provider'] ?? '');
$allowed = [
'wpml',
'polylang',
'translatepress',
'multilingualpress',
'falang',
'qtranslate',
'wpglobus',
'bogo',
];
if ($saved !== '' && in_array($saved, $allowed, true)) {
return $saved;
}
$detected = aismart_onboarding_detect_multilang_provider();
$primary = aismart_normalize_provider_key($detected['primary'] ?? '');
if ($primary !== '' && in_array($primary, $allowed, true)) {
return $primary;
}
return '';
}
function aismart_supported_provider_labels() {
return [
'wpml' => 'WPML',
'polylang' => 'Polylang',
'translatepress' => 'TranslatePress',
'multilingualpress' => 'MultilingualPress',
'falang' => 'Falang',
'qtranslate' => 'qTranslate',
'wpglobus' => 'WPGlobus',
'bogo' => 'Bogo',
];
}
function aismart_get_supported_provider_options() {
$labels = aismart_supported_provider_labels();
$items = [];
foreach ($labels as $code => $name) {
$items[] = [
'code' => (string) $code,
'name' => (string) $name,
];
}
return $items;
}
function aismart_detect_active_provider_key() {
$labels = aismart_supported_provider_labels();
$multilang = aismart_onboarding_detect_multilang_provider();
$primary = aismart_normalize_provider_key($multilang['primary'] ?? '');
if ($primary !== '' && isset($labels[$primary])) {
return $primary;
}
$selected = aismart_resolve_selected_provider_key();
if ($selected !== '' && isset($labels[$selected])) {
return $selected;
}
return '';
}
function aismart_guess_previous_provider_key($active_provider = '') {
global $wpdb;
$active_provider = aismart_normalize_provider_key($active_provider);
$labels = aismart_supported_provider_labels();
$saved = aismart_normalize_provider_key(get_option('aismart_migration_last_source_provider', ''));
if ($saved !== '' && $saved !== $active_provider && isset($labels[$saved])) {
return $saved;
}
$counts = [];
$items = aismart_topic_queue_get_items();
if (is_array($items)) {
foreach ($items as $item) {
$provider = aismart_normalize_provider_key($item['provider'] ?? '');
if ($provider === '' || $provider === $active_provider || !isset($labels[$provider])) {
continue;
}
$counts[$provider] = (int) ($counts[$provider] ?? 0) + 1;
}
}
$post_ids = $wpdb->get_col(
"SELECT ID
FROM {$wpdb->posts}
WHERE post_type='post'
AND post_status IN ('publish','draft','pending','future','private')
ORDER BY ID DESC
LIMIT 250"
);
if (is_array($post_ids)) {
foreach ($post_ids as $id_raw) {
$post_id = (int) $id_raw;
if ($post_id <= 0) {
continue;
}
$provider = aismart_detect_provider_from_post($post_id);
$provider = aismart_normalize_provider_key($provider);
if ($provider === '' || $provider === $active_provider || !isset($labels[$provider])) {
continue;
}
$counts[$provider] = (int) ($counts[$provider] ?? 0) + 1;
}
}
if (empty($counts)) {
return '';
}
arsort($counts);
$keys = array_keys($counts);
return isset($keys[0]) ? (string) $keys[0] : '';
}
function aismart_detect_provider_from_post($post_id) {
global $wpdb;
$post_id = (int) $post_id;
if ($post_id <= 0) {
return '';
}
$saved = aismart_normalize_provider_key(get_post_meta($post_id, '_aismart_created_provider', true));
if ($saved !== '') {
return $saved;
}
$saved_last = aismart_normalize_provider_key(get_post_meta($post_id, '_aismart_last_multilang_provider', true));
if ($saved_last !== '') {
return $saved_last;
}
$postmeta_table = $wpdb->postmeta;
$has_falang = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(meta_id) FROM {$postmeta_table} WHERE post_id = %d AND meta_key LIKE %s",
$post_id,
'_aismart_falang_translation_%'
)
);
if ($has_falang > 0) {
return 'falang';
}
$has_trp = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(meta_id) FROM {$postmeta_table} WHERE post_id = %d AND (meta_key = %s OR meta_key LIKE %s)",
$post_id,
'_aismart_trp_translations',
'_aismart_trp_translation_%'
)
);
if ($has_trp > 0) {
return 'translatepress';
}
$has_sublanguage = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(meta_id) FROM {$postmeta_table} WHERE post_id = %d AND meta_key LIKE %s",
$post_id,
'_aismart_sublanguage_translation_%'
)
);
if ($has_sublanguage > 0) {
return 'sublanguage';
}
$has_mlp = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(meta_id) FROM {$postmeta_table} WHERE post_id = %d AND meta_key LIKE %s",
$post_id,
'_aismart_mlp_translation_blog_%'
)
);
if ($has_mlp > 0) {
return 'multilingualpress';
}
if (function_exists('pll_get_post_translations')) {
$map = pll_get_post_translations($post_id);
if (is_array($map) && count($map) > 1) {
return 'polylang';
}
}
$content = (string) get_post_field('post_content', $post_id);
if ($content !== '') {
if (preg_match('/\[:[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\]/u', $content)) {
return 'qtranslate';
}
if (preg_match('//u', $content)) {
return 'wpglobus';
}
}
$children_count = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(pm.meta_id)
FROM {$wpdb->postmeta} pm
INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id
WHERE pm.meta_key = %s
AND pm.meta_value = %d
AND p.post_type = 'post'
AND p.post_status IN ('publish','draft','pending','future','private')",
'_aismart_source_post_id',
$post_id
)
);
if ($children_count > 0) {
return 'legacy-falang-polylang';
}
return '';
}
add_action('wp_ajax_aismart_get_topics', 'aismart_get_topics');
function aismart_get_topics() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
$items = aismart_topic_queue_get_items();
if (!is_array($items)) {
$items = [];
}
global $wpdb;
$posts_table = $wpdb->posts;
$is_non_base_language_post = static function ($post_id) {
$post_id = (int) $post_id;
if ($post_id <= 0) {
return false;
}
if (function_exists('aismart_is_non_source_translation_post') && aismart_is_non_source_translation_post($post_id)) {
return true;
}
if ((int) get_post_meta($post_id, '_aismart_source_post_id', true) > 0) {
return true;
}
return false;
};
$payload = [];
$payload_title_index = [];
$changed = false;
foreach ($items as $index => $item) {
$topic = aismart_topic_queue_normalize_title($item['title'] ?? '');
if ($topic === '') {
continue;
}
$topic_key = function_exists('mb_strtolower') ? mb_strtolower($topic) : strtolower($topic);
$payload_title_index[$topic_key] = true;
$resume_post_id = (int) ($item['written_post_id'] ?? 0);
$is_written = !empty($item['written']);
if ($resume_post_id <= 0 || !get_post($resume_post_id)) {
$post_rows = $wpdb->get_results(
$wpdb->prepare(
"SELECT ID, post_status
FROM {$posts_table}
WHERE post_type = 'post'
AND post_status IN ('publish','draft','pending','future','private')
AND post_title = %s
ORDER BY ID DESC
LIMIT 12",
$topic
)
);
if (is_array($post_rows) && !empty($post_rows)) {
foreach ($post_rows as $post_row) {
$candidate_id = isset($post_row->ID) ? (int) $post_row->ID : 0;
if ($candidate_id <= 0) {
continue;
}
if ($is_non_base_language_post($candidate_id)) {
continue;
}
$resume_post_id = $candidate_id;
if ((string) ($post_row->post_status ?? '') === 'publish') {
$is_written = true;
}
break;
}
}
}
if ((int) ($item['written_post_id'] ?? 0) !== $resume_post_id || (int) (!empty($item['written'])) !== (int) ($is_written ? 1 : 0)) {
$items[$index]['written_post_id'] = $resume_post_id;
$items[$index]['written'] = $is_written ? 1 : 0;
$changed = true;
}
// Keep Current Topic List source-only: remove translated child posts.
if ($resume_post_id > 0) {
$source_post_id = (int) get_post_meta($resume_post_id, '_aismart_source_post_id', true);
if ($source_post_id > 0) {
unset($items[$index]);
$changed = true;
continue;
}
// Safety for legacy rows where _aismart_source_post_id is missing:
// keep only base-language source posts in Current Topic List.
if ($is_non_base_language_post($resume_post_id)) {
unset($items[$index]);
$changed = true;
continue;
}
}
$provider_key = '';
if ($resume_post_id > 0) {
$provider_key = aismart_detect_provider_from_post($resume_post_id);
}
if ($provider_key === '') {
$provider_key = aismart_normalize_provider_key($item['provider'] ?? '');
}
if ($provider_key === '' && $resume_post_id > 0) {
$provider_key = 'legacy-falang-polylang';
}
$provider_label = aismart_provider_label($provider_key);
$payload[] = [
'queue_id' => count($payload) + 1,
'title' => $topic,
'exists' => $is_written ? 1 : 0,
'is_written' => $is_written ? 1 : 0,
'draft_post_id' => 0,
'resume_post_id' => $resume_post_id > 0 ? $resume_post_id : 0,
'source' => sanitize_key((string) ($item['source'] ?? 'manual')),
'provider_key' => $provider_key,
'provider_label' => $provider_label,
];
}
// Safety net: if a generated post title is missing from queue storage,
// append it so manual generation always appears in Current Topic List.
$generated_rows = $wpdb->get_results(
"SELECT p.ID, p.post_title, p.post_status
FROM {$posts_table} p
INNER JOIN {$wpdb->postmeta} pm
ON pm.post_id = p.ID
AND pm.meta_key = '_aismart_generated_post'
AND pm.meta_value = '1'
LEFT JOIN {$wpdb->postmeta} srcpm
ON srcpm.post_id = p.ID
AND srcpm.meta_key = '_aismart_source_post_id'
WHERE p.post_type = 'post'
AND p.post_status IN ('publish','draft','pending','future','private')
AND (srcpm.meta_value IS NULL OR srcpm.meta_value = '' OR srcpm.meta_value = '0')
ORDER BY p.ID DESC
LIMIT 50"
);
if (is_array($generated_rows)) {
foreach ($generated_rows as $row) {
$topic = aismart_topic_queue_normalize_title($row->post_title ?? '');
if ($topic === '') {
continue;
}
$topic_key = function_exists('mb_strtolower') ? mb_strtolower($topic) : strtolower($topic);
if (isset($payload_title_index[$topic_key])) {
continue;
}
$resume_post_id = (int) ($row->ID ?? 0);
if ($resume_post_id <= 0) {
continue;
}
// Backfill only source/default-language items; skip translated children.
if ($is_non_base_language_post($resume_post_id)) {
continue;
}
$provider_key = aismart_detect_provider_from_post($resume_post_id);
if ($provider_key === '') {
$provider_key = 'legacy-falang-polylang';
}
$items[] = [
'title' => $topic,
'source' => 'generated',
'provider' => $provider_key,
'written' => ((string) ($row->post_status ?? '') === 'publish') ? 1 : 0,
'written_post_id' => $resume_post_id,
'created_at' => gmdate('c'),
];
$changed = true;
$payload[] = [
'queue_id' => count($items),
'title' => $topic,
'exists' => ((string) ($row->post_status ?? '') === 'publish') ? 1 : 0,
'is_written' => ((string) ($row->post_status ?? '') === 'publish') ? 1 : 0,
'draft_post_id' => 0,
'resume_post_id' => $resume_post_id,
'source' => 'generated',
'provider_key' => $provider_key,
'provider_label' => aismart_provider_label($provider_key),
];
$payload_title_index[$topic_key] = true;
}
}
if ($changed) {
aismart_topic_queue_save_items($items);
}
wp_send_json_success(['topics' => $payload]);
}
// AJAX: Create blog draft via NVL API
function aismart_refresh_access_token_from_secret() {
$secret = trim((string) aismart_plugin_get_signing_secret());
if ($secret === '') {
return new WP_Error('token_refresh_missing_secret', 'Cannot refresh token: signing secret is missing.');
}
$state = aismart_onboarding_state_get();
$email = isset($state['admin_email']) ? sanitize_email((string) $state['admin_email']) : '';
if (!is_email($email)) {
$email = aismart_onboarding_resolve_connected_email($state);
}
if (!is_email($email)) {
$email = aismart_onboarding_current_admin_email();
}
if (!is_email($email)) {
return new WP_Error('token_refresh_missing_email', 'Cannot refresh token: admin email is missing.');
}
$request_id = aismart_api_make_request_id('token');
$url = untrailingslashit((string) aismart_plugin_get_api_base()) . '/api/generate-token';
$body = [
'email' => $email,
'secret' => $secret,
'request_id' => $request_id,
];
$response = wp_remote_post($url, [
'timeout' => 20,
'sslverify' => true,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Request-Id' => $request_id,
],
'body' => wp_json_encode($body),
]);
if (is_wp_error($response)) {
return $response;
}
$status = (int) wp_remote_retrieve_response_code($response);
$raw = (string) wp_remote_retrieve_body($response);
$json = json_decode($raw, true);
if ($status >= 400) {
return new WP_Error('token_refresh_failed', 'Token refresh failed', [
'status' => $status,
'raw' => $raw,
'response' => is_array($json) ? $json : null,
]);
}
if (!is_array($json)) {
return new WP_Error('token_refresh_invalid_json', 'Token refresh returned invalid JSON', [
'status' => $status,
'raw' => $raw,
]);
}
$token = isset($json['access_token']) ? trim((string) $json['access_token']) : '';
if ($token === '' && isset($json['data']) && is_array($json['data'])) {
$token = isset($json['data']['access_token']) ? trim((string) $json['data']['access_token']) : '';
}
if ($token === '') {
return new WP_Error('token_refresh_missing_token', 'Token refresh response did not include access_token', [
'status' => $status,
'response' => $json,
]);
}
$token_type = isset($json['token_type']) ? trim((string) $json['token_type']) : '';
if ($token_type === '' && isset($json['data']) && is_array($json['data'])) {
$token_type = isset($json['data']['token_type']) ? trim((string) $json['data']['token_type']) : '';
}
if ($token_type === '') {
$token_type = 'Bearer';
}
$state['access_token'] = $token;
$state['token_type'] = $token_type;
$state['admin_email'] = $email;
$state['token_refreshed_at'] = gmdate('c');
aismart_onboarding_state_set($state);
return $token;
}
function aismart_posts_api_candidates() {
$configured_base = untrailingslashit((string) aismart_plugin_get_api_base());
$candidates = [];
if ($configured_base !== '') {
$candidates[] = $configured_base . '/api/v2/aismart/posts';
}
$candidates[] = 'https://aismartcontent.co/api/v2/aismart/posts';
$normalized = [];
foreach ($candidates as $url) {
$url = trim((string) $url);
if ($url === '' || !filter_var($url, FILTER_VALIDATE_URL)) {
continue;
}
if (!in_array($url, $normalized, true)) {
$normalized[] = $url;
}
}
return $normalized;
}
function aismart_request_posts_api(array $payload, $timeout = 60) {
$timeout = max(10, (int) $timeout);
$endpoints = aismart_posts_api_candidates();
$request_started_at = microtime(true);
// Keep one create request well under common 100s gateway limits.
$max_total_budget_seconds = min(85, max(20, (int) floor($timeout * 0.8)));
$state = aismart_onboarding_state_get();
$ctx = aismart_resolve_workspace_context();
$workspace_id = (int) ($ctx['workspace_id'] ?? 0);
$connected_user_id = isset($state['user_id']) ? (int) $state['user_id'] : 0;
$access_token = isset($state['access_token']) ? trim((string) $state['access_token']) : '';
if ($workspace_id > 0 && empty($payload['workspace_id'])) {
$payload['workspace_id'] = $workspace_id;
}
if ($connected_user_id > 0 && empty($payload['user_id'])) {
$payload['user_id'] = $connected_user_id;
}
if (empty($payload['site_url'])) {
$payload['site_url'] = home_url('/');
}
if (empty($payload['site_domain'])) {
$payload['site_domain'] = (string) wp_parse_url(home_url('/'), PHP_URL_HOST);
}
$request_id = isset($payload['request_id']) ? sanitize_text_field((string) $payload['request_id']) : '';
if ($request_id === '') {
$request_id = aismart_api_make_request_id('posts');
}
$payload['request_id'] = $request_id;
$first_transport_error = '';
$last_transport_error = '';
$best_transport_error = '';
$last_http_error = '';
$last_status = 0;
$last_body = '';
$first_http_error = '';
$first_http_status = 0;
$first_http_body = '';
$attempted = [];
$token_refresh_attempted = false;
foreach ($endpoints as $endpoint) {
$attempted[] = $endpoint;
$request_timeout = $timeout;
if (strpos($endpoint, AISMART_API_SLOW_HOST) !== false) {
// Allow a larger timeout window for image-only generation so it can return a usable image.
$is_image_only_request = !empty($payload['image_only']) && (int) $payload['image_only'] === 1;
if ($is_image_only_request) {
$request_timeout = min($timeout, max(20, (int) ceil($timeout * 0.5)));
} else {
// Keep non-image requests conservative on this host.
$request_timeout = min($timeout, 35);
}
}
$endpoint_attempt = 0;
$max_endpoint_attempts = 2;
$endpoint_access_token = $access_token;
while ($endpoint_attempt < $max_endpoint_attempts) {
$elapsed = microtime(true) - $request_started_at;
if ($elapsed >= $max_total_budget_seconds) {
$last_http_error = 'Request budget exceeded before completion';
$last_status = $last_status > 0 ? $last_status : 504;
break;
}
$endpoint_attempt++;
$request_args = [
'timeout' => $request_timeout,
'headers' => [
'Accept' => 'application/json',
'Content-Type' => 'application/json',
'X-Request-Id' => $request_id,
],
'body' => wp_json_encode($payload),
];
if ($endpoint_access_token !== '') {
$request_args['headers']['Authorization'] = 'Bearer ' . $endpoint_access_token;
}
$response = wp_remote_post($endpoint, $request_args);
if (is_wp_error($response)) {
$transport_error = (string) $response->get_error_message();
if ($first_transport_error === '') {
$first_transport_error = $transport_error;
}
$last_transport_error = $transport_error;
$is_timeout_error = stripos($transport_error, 'cURL error 28') !== false
|| stripos($transport_error, 'operation timed out') !== false
|| stripos($transport_error, 'timed out') !== false;
if ($best_transport_error === '') {
$best_transport_error = $transport_error;
} else {
$best_is_timeout = stripos($best_transport_error, 'cURL error 28') !== false
|| stripos($best_transport_error, 'operation timed out') !== false
|| stripos($best_transport_error, 'timed out') !== false;
if ($best_is_timeout && !$is_timeout_error) {
$best_transport_error = $transport_error;
}
}
if ($endpoint_attempt < $max_endpoint_attempts && aismart_api_should_retry_transport_error($transport_error)) {
// Keep UX responsive for interactive create flow.
$sleep_seconds = min(1.5, (float) aismart_api_retry_after_seconds($endpoint_attempt));
usleep((int) ($sleep_seconds * 1000000));
continue;
}
error_log('AISmart API transport error at ' . $endpoint . ': ' . $last_transport_error);
break;
}
$status = (int) wp_remote_retrieve_response_code($response);
$body = (string) wp_remote_retrieve_body($response);
$json = json_decode($body, true);
if ($status === 401 && !$token_refresh_attempted) {
$token_refresh_attempted = true;
$new_token = aismart_refresh_access_token_from_secret();
if (!is_wp_error($new_token) && is_string($new_token) && $new_token !== '') {
$endpoint_access_token = $new_token;
$access_token = $new_token;
continue;
}
}
$embedded_status = 0;
if (is_array($json) && isset($json['status']) && is_numeric($json['status'])) {
$embedded_status = (int) $json['status'];
}
$embedded_code = 0;
if (is_array($json) && isset($json['code']) && is_numeric($json['code'])) {
$embedded_code = (int) $json['code'];
}
// Some upstream responses wrap rate-limit in JSON with HTTP 500.
$is_rate_limited = $status === 429
|| $embedded_status === 429
|| $embedded_code === 429
|| (is_string($body) && stripos($body, 'rate limit') !== false && $status >= 400);
if ($is_rate_limited) {
if ($endpoint_attempt < $max_endpoint_attempts) {
$retry_after = (string) wp_remote_retrieve_header($response, 'retry-after');
// Cap backoff to avoid crossing gateway timeout windows.
$sleep_seconds = min(2.0, (float) aismart_api_retry_after_seconds($endpoint_attempt, $retry_after));
usleep((int) ($sleep_seconds * 1000000));
continue;
}
$msg = 'AI generation rate limit reached. Please try again shortly.';
if (is_array($json) && !empty($json['message'])) {
$msg = (string) $json['message'];
} elseif (is_array($json) && !empty($json['error'])) {
$msg = (string) $json['error'];
}
return [
'ok' => false,
'status' => 429,
'message' => $msg,
'endpoint' => $endpoint,
'body' => $body,
'attempted' => $attempted,
];
}
if ($status >= 200 && $status < 300 && is_array($json)) {
return [
'ok' => true,
'status' => $status,
'data' => $json,
'endpoint' => $endpoint,
];
}
$last_status = $status;
$last_body = $body;
$last_http_error = 'HTTP ' . $status;
if (is_array($json) && !empty($json['message'])) {
$last_http_error .= ': ' . sanitize_text_field((string) $json['message']);
}
if ($first_http_error === '') {
$first_http_error = $last_http_error;
$first_http_status = $status;
$first_http_body = $body;
}
if ($endpoint_attempt < $max_endpoint_attempts && aismart_api_should_retry_http_status($status)) {
$retry_after = (string) wp_remote_retrieve_header($response, 'retry-after');
$sleep_seconds = min(2.0, (float) aismart_api_retry_after_seconds($endpoint_attempt, $retry_after));
usleep((int) ($sleep_seconds * 1000000));
continue;
}
error_log('AISmart API HTTP error at ' . $endpoint . ': status=' . $status . ' body=' . $body);
break;
}
}
$message = $first_http_error !== ''
? $first_http_error
: ($best_transport_error !== ''
? $best_transport_error
: ($first_transport_error !== ''
? $first_transport_error
: ($last_transport_error !== ''
? $last_transport_error
: ($last_http_error !== '' ? $last_http_error : 'Upstream API request failed'))));
$status = $first_http_status > 0
? $first_http_status
: $last_status;
$body = $first_http_body !== ''
? $first_http_body
: $last_body;
return [
'ok' => false,
'status' => $status,
'message' => $message,
'body' => $body,
'attempted' => $attempted,
];
}
function aismart_extract_posts_api_error_text(array $api_result) {
$parts = [];
if (!empty($api_result['message'])) {
$parts[] = (string) $api_result['message'];
}
if (!empty($api_result['body'])) {
$parts[] = (string) $api_result['body'];
}
return strtolower(trim(implode(' | ', $parts)));
}
function aismart_is_gemini_key_failure($error_text) {
$error_text = strtolower(trim((string) $error_text));
if ($error_text === '') {
return false;
}
$needles = [
'api key not valid',
'consumer_suspended',
'permission denied',
'generativelanguage.googleapis.com',
'missing gemini api key',
'invalid_argument',
];
foreach ($needles as $needle) {
if (strpos($error_text, $needle) !== false) {
return true;
}
}
return false;
}
function aismart_request_posts_image_api(array $payload, $timeout = 90) {
$payload['image_only'] = 1;
$result = aismart_request_posts_api($payload, $timeout);
if (!empty($result['ok'])) {
return $result;
}
$error_text = aismart_extract_posts_api_error_text($result);
if (!aismart_is_gemini_key_failure($error_text)) {
return $result;
}
$fallback_payload = $payload;
// Hint upstream services that support provider switching to try OpenAI image generation.
$fallback_payload['api_provider'] = 'openai';
$fallback_payload['provider'] = 'openai';
$fallback_payload['image_generator'] = 'gpt-image-1';
$fallback_payload['model'] = 'gpt-image-1';
$fallback_result = aismart_request_posts_api($fallback_payload, $timeout);
if (!empty($fallback_result['ok'])) {
$fallback_result['provider_fallback_attempted'] = 1;
$fallback_result['provider_fallback'] = 'openai';
return $fallback_result;
}
if (empty($fallback_result['message']) && !empty($result['message'])) {
$fallback_result['message'] = (string) $result['message'];
}
if (empty($fallback_result['status']) && !empty($result['status'])) {
$fallback_result['status'] = (int) $result['status'];
}
if (empty($fallback_result['body']) && !empty($result['body'])) {
$fallback_result['body'] = (string) $result['body'];
}
$fallback_result['provider_fallback_attempted'] = 1;
$fallback_result['provider_fallback'] = 'openai';
return $fallback_result;
}
function aismart_build_local_fallback_post_data($topic, $manual_content = '') {
$topic = aismart_normalize_post_title((string) $topic);
$manual_content = trim((string) $manual_content);
if ($manual_content !== '') {
$clean_content = wp_kses_post($manual_content);
return [
'title' => $topic,
'content' => $clean_content,
'excerpt' => wp_trim_words(wp_strip_all_tags($clean_content), 40, '...'),
'image_url' => '',
];
}
$tokens = aismart_topic_queue_title_tokens($topic);
if (empty($tokens)) {
$tokens = ['business', 'strategy'];
}
$focuses = aismart_topic_queue_context_focuses($topic);
if (empty($focuses)) {
$focuses = [implode(' ', array_slice($tokens, 0, 2))];
}
$seed = abs(crc32(strtolower($topic)));
$token_a = (string) $tokens[$seed % count($tokens)];
$token_b = (string) $tokens[($seed + 2) % count($tokens)];
$focus_a = (string) $focuses[$seed % count($focuses)];
$focus_b = (string) $focuses[($seed + 1) % count($focuses)];
if ($focus_b === '' || strcasecmp($focus_a, $focus_b) === 0) {
$focus_b = implode(' ', array_slice($tokens, -min(3, count($tokens))));
}
if ($focus_b === '' || strcasecmp($focus_a, $focus_b) === 0) {
$focus_b = (string) $token_b;
}
if ($focus_b === '' || strcasecmp($focus_a, $focus_b) === 0) {
$focus_b = 'execution outcomes';
}
$intro_templates = [
'The AI service is temporarily unavailable, so this draft is generated locally and shaped around your topic intent.',
'This article draft is built in local fallback mode and adapted from the topic context so you can keep publishing.',
'Because upstream writing is currently unavailable, this local draft focuses on practical, publish-ready structure.',
'Local draft mode is active right now, so this version emphasizes clarity, relevance, and actionability for the topic.',
];
$subtopic_templates = [
'Why %s matters in %s',
'Practical strategy for %s',
'Turning %s into measurable progress',
'Common mistakes in %s and how to avoid them',
'A weekly plan to improve %s',
'What to prioritize first in %s',
];
$angle_templates = [
'focus on audience intent and specific outcomes',
'connect content decisions to measurable KPIs',
'reduce complexity and keep execution repeatable',
'improve relevance with clearer targeting and messaging',
];
$cta_templates = [
'Start by editing examples and numbers so the article reflects your real workflow and market.',
'Refine each section with your own cases, screenshots, or data before publishing.',
'Replace generic points with your team process and recent performance observations.',
'Add concrete examples from your business context to make the draft fully publish-ready.',
];
$pick = static function ($list, $index) {
$count = count($list);
if ($count <= 0) {
return '';
}
return (string) $list[$index % $count];
};
$safe_topic = esc_html($topic);
$safe_focus_a = esc_html($focus_a);
$safe_focus_b = esc_html($focus_b);
$safe_token_a = esc_html($token_a);
$safe_token_b = esc_html($token_b);
$intro = esc_html($pick($intro_templates, $seed));
$angle = esc_html($pick($angle_templates, $seed + 1));
$cta = esc_html($pick($cta_templates, $seed + 2));
$section_titles = [];
$used_titles = [];
for ($i = 0; $i < 3; $i++) {
$tpl = $pick($subtopic_templates, $seed + $i);
$base = $i === 0 ? $focus_a : ($i === 1 ? $focus_b : $token_a . ' and ' . $token_b);
$title = trim(sprintf($tpl, ucfirst($base), gmdate('Y')));
$title_key = strtolower($title);
if ($title === '' || isset($used_titles[$title_key])) {
continue;
}
$used_titles[$title_key] = true;
$section_titles[] = $title;
}
if (count($section_titles) < 3) {
$section_titles[] = 'Execution checklist for ' . ucfirst($token_a);
}
if (count($section_titles) < 3) {
$section_titles[] = 'Key decisions around ' . ucfirst($token_b);
}
$safe_section_1 = esc_html((string) $section_titles[0]);
$safe_section_2 = esc_html((string) $section_titles[1]);
$safe_section_3 = esc_html((string) $section_titles[2]);
$content = ''
. '' . $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 . '.
'
. '' . $safe_section_1 . ' '
. '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.
'
. '' . $safe_section_2 . ' '
. '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.
'
. '' . $safe_section_3 . ' '
. '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 = '', $seed = '') {
$query = aismart_build_backup_image_query($topic, $category, $tags, $excerpt);
if ($query === '') {
$query = 'healthcare,medical,doctor,clinic';
}
// Add unique seed so we don't get the exact same fallback image repeatedly if upstream API is broken
$lock_input = $query;
if ($seed !== '') {
$lock_input .= '-' . $seed;
} else {
$lock_input .= '-' . wp_generate_password(6, false);
}
// Use a stable seed-based source. Some keyword combinations can make LoremFlickr return HTTP 500.
$lock = (string) abs(crc32((string) $lock_input));
$seed_slug = preg_replace('/[^a-z0-9]+/i', '-', (string) $query);
$seed_slug = trim((string) $seed_slug, '-');
if ($seed_slug === '') {
$seed_slug = 'aismart-fallback';
}
return 'https://picsum.photos/seed/' . rawurlencode($seed_slug . '-' . $lock) . '/1200/630.jpg';
}
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, '');
}
$parts[] = 'Style: editorial photography, clean composition, topic-related, no text, no watermark, no logo.';
return trim(implode(' ', $parts));
}
function aismart_build_image_variation_token($topic = '') {
$topic = sanitize_text_field((string) $topic);
$seed = $topic . '|' . microtime(true) . '|' . wp_rand(100000, 999999);
return substr(hash('sha256', $seed), 0, 24);
}
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_attach_body_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',
];
}
aismart_require_media_includes();
$attach_id = media_sideload_image($image_url, $post_id, $desc !== '' ? $desc : 'AISmart generated body 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-body-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 body 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',
];
}
$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_generate_advanced_body_image_urls($post_id, $topic, $category = '', $tags = '', $excerpt = '', $count = 2, $existing_urls = []) {
$post_id = (int) $post_id;
$topic = sanitize_text_field((string) $topic);
$category = sanitize_text_field((string) $category);
$tags = sanitize_text_field((string) $tags);
$excerpt = sanitize_text_field((string) wp_strip_all_tags((string) $excerpt));
$count = (int) $count;
if ($count < 1) {
$count = 1;
}
if ($count > 4) {
$count = 4;
}
$seen_urls = [];
foreach ((array) $existing_urls as $existing_url) {
$key = strtolower(trim((string) $existing_url));
if ($key !== '') {
$seen_urls[$key] = true;
}
}
$results = [];
$max_attempts = max(3, $count * 3);
$attempt = 0;
while (count($results) < $count && $attempt < $max_attempts) {
$attempt++;
$slot = count($results) + 1;
$variation_token = aismart_build_image_variation_token($topic . '|advanced-body|' . $slot . '|' . $attempt);
$prompt = aismart_build_image_prompt($topic, $category, $tags, $excerpt);
$prompt .= ' Additional in-article visual for advanced section ' . $slot . '. Keep composition different from other generated images.';
$candidate = '';
$image_result = aismart_request_posts_image_api([
'site' => 'site2',
'topic' => $topic,
'image_only' => 1,
'variation_token' => $variation_token,
'image_prompt' => $prompt,
], 90);
if (!empty($image_result['ok'])) {
$image_data = (array) ($image_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)) {
$candidate = aismart_build_backup_image_url($topic . ' section ' . $slot, $category, $tags, $excerpt, $variation_token);
}
if ($candidate === '' || !filter_var($candidate, FILTER_VALIDATE_URL)) {
continue;
}
$candidate_key = strtolower(trim((string) $candidate));
if ($candidate_key === '' || isset($seen_urls[$candidate_key])) {
continue;
}
$attach_result = aismart_attach_body_image_from_url($post_id, $candidate, 'AISmart advanced body image ' . $slot . ' for ' . $topic);
if (empty($attach_result['saved'])) {
continue;
}
$saved_url = isset($attach_result['image_url']) ? (string) $attach_result['image_url'] : '';
if ($saved_url === '' || !filter_var($saved_url, FILTER_VALIDATE_URL)) {
$saved_url = $candidate;
}
$saved_key = strtolower(trim((string) $saved_url));
if ($saved_key === '' || isset($seen_urls[$saved_key])) {
continue;
}
$seen_urls[$saved_key] = true;
$results[] = $saved_url;
}
return $results;
}
function aismart_inject_advanced_body_images($content, $image_urls, $topic = '') {
$content = (string) $content;
if ($content === '') {
return '';
}
if (!is_array($image_urls) || empty($image_urls)) {
return $content;
}
// Prevent duplicate insertion when the same post is regenerated multiple times.
if (stripos($content, 'aismart-inline-visual') !== false) {
return $content;
}
$clean_urls = [];
foreach ($image_urls as $image_url) {
$url = trim((string) $image_url);
if ($url !== '' && filter_var($url, FILTER_VALIDATE_URL)) {
$clean_urls[] = $url;
}
}
if (empty($clean_urls)) {
return $content;
}
$topic_text = trim((string) wp_strip_all_tags((string) $topic));
$blocks = [];
foreach ($clean_urls as $index => $url) {
$number = $index + 1;
$alt_text = ($topic_text !== '' ? $topic_text : 'Article') . ' supporting visual ' . $number;
$caption = 'Supporting visual ' . $number;
$blocks[] =
''
. ' '
. '' . esc_html($caption) . ' '
. ' ';
}
// Advanced template is section-oriented; prefer H3 anchors when available.
$heading_matches = [];
preg_match_all('/]*>/i', $content, $heading_matches, PREG_OFFSET_CAPTURE);
$heading_offsets = [];
if (!empty($heading_matches[0]) && is_array($heading_matches[0])) {
foreach ($heading_matches[0] as $match) {
if (!is_array($match) || !isset($match[1])) {
continue;
}
$heading_offsets[] = (int) $match[1];
}
}
if (count($heading_offsets) >= count($blocks)) {
$enhanced = $content;
$position_count = count($heading_offsets);
$block_count = count($blocks);
$offsets = [];
for ($i = 0; $i < $block_count; $i++) {
$ratio = ($i + 1) / ($block_count + 1);
$position_index = (int) floor($ratio * $position_count);
if ($position_index >= $position_count) {
$position_index = $position_count - 1;
}
if ($position_index < 0) {
$position_index = 0;
}
$offsets[] = (int) $heading_offsets[$position_index];
}
$delta = 0;
foreach ($blocks as $i => $block_html) {
$insert_at = (int) ($offsets[$i] ?? 0) + $delta;
$insertion = "\n\n" . $block_html . "\n\n";
$enhanced = substr($enhanced, 0, $insert_at) . $insertion . substr($enhanced, $insert_at);
$delta += strlen($insertion);
}
return $enhanced;
}
$paragraph_matches = [];
preg_match_all('/<\/p>/i', $content, $paragraph_matches, PREG_OFFSET_CAPTURE);
$paragraph_end_offsets = [];
if (!empty($paragraph_matches[0]) && is_array($paragraph_matches[0])) {
foreach ($paragraph_matches[0] as $match) {
if (!is_array($match) || !isset($match[1])) {
continue;
}
$paragraph_end_offsets[] = (int) $match[1] + 4;
}
}
if (empty($paragraph_end_offsets)) {
return rtrim($content) . "\n\n" . implode("\n\n", $blocks);
}
$enhanced = $content;
$position_count = count($paragraph_end_offsets);
$block_count = count($blocks);
$offsets = [];
for ($i = 0; $i < $block_count; $i++) {
$ratio = ($i + 1) / ($block_count + 1);
$position_index = (int) floor($ratio * $position_count);
if ($position_index >= $position_count) {
$position_index = $position_count - 1;
}
if ($position_index < 0) {
$position_index = 0;
}
$offsets[] = (int) $paragraph_end_offsets[$position_index];
}
$delta = 0;
foreach ($blocks as $i => $block_html) {
$insert_at = (int) ($offsets[$i] ?? 0) + $delta;
$insertion = "\n\n" . $block_html . "\n\n";
$enhanced = substr($enhanced, 0, $insert_at) . $insertion . substr($enhanced, $insert_at);
$delta += strlen($insertion);
}
return $enhanced;
}
function aismart_phase2_body_image_count_for_type($type_blog) {
$type_blog = aismart_normalize_type_blog($type_blog);
$mapping = [
2 => 2, // Advanced Blog Template
3 => 2, // Design Case Study
4 => 3, // Fully Article
5 => 3, // Premium Hybrid AI + SEO
];
return isset($mapping[$type_blog]) ? (int) $mapping[$type_blog] : 0;
}
function aismart_inject_template_body_images($content, $image_urls, $topic = '', $type_blog = 2) {
$type_blog = aismart_normalize_type_blog($type_blog);
if ($type_blog === 2) {
return aismart_inject_advanced_body_images($content, $image_urls, $topic);
}
$content = (string) $content;
if ($content === '') {
return '';
}
if (!is_array($image_urls) || empty($image_urls)) {
return $content;
}
// Prevent duplicate insertion when the same post is regenerated multiple times.
if (stripos($content, 'aismart-inline-visual') !== false) {
return $content;
}
$clean_urls = [];
foreach ($image_urls as $image_url) {
$url = trim((string) $image_url);
if ($url !== '' && filter_var($url, FILTER_VALIDATE_URL)) {
$clean_urls[] = $url;
}
}
if (empty($clean_urls)) {
return $content;
}
$topic_text = trim((string) wp_strip_all_tags((string) $topic));
$template_label = aismart_type_blog_label($type_blog);
$caption_prefix = $template_label . ' visual';
$heading_patterns = ['/]*>/i'];
if ($type_blog === 3) {
$caption_prefix = 'Case study visual';
$heading_patterns = [
'/]*>/i',
'/]*>/i',
];
} elseif ($type_blog === 4) {
$caption_prefix = 'SEO section visual';
$heading_patterns = [
'/]*>/i',
'/]*>/i',
];
} elseif ($type_blog === 5) {
$caption_prefix = 'Hybrid SEO visual';
$heading_patterns = [
'/]*>/i',
'/]*>/i',
];
}
$blocks = [];
foreach ($clean_urls as $index => $url) {
$number = $index + 1;
$alt_text = ($topic_text !== '' ? $topic_text : 'Article') . ' supporting visual ' . $number;
$caption = $caption_prefix . ' ' . $number;
$blocks[] =
''
. ' '
. '' . esc_html($caption) . ' '
. ' ';
}
// Types 3/4/5 are section-heavy templates; anchor images around section headings.
$heading_offsets = [];
foreach ($heading_patterns as $heading_pattern) {
$heading_matches = [];
preg_match_all($heading_pattern, $content, $heading_matches, PREG_OFFSET_CAPTURE);
if (!empty($heading_matches[0]) && is_array($heading_matches[0])) {
foreach ($heading_matches[0] as $match) {
if (!is_array($match) || !isset($match[1])) {
continue;
}
$heading_offsets[] = (int) $match[1];
}
}
if (!empty($heading_offsets)) {
break;
}
}
if (empty($heading_offsets)) {
return aismart_inject_advanced_body_images($content, $clean_urls, $topic);
}
$enhanced = $content;
$position_count = count($heading_offsets);
$block_count = count($blocks);
$offsets = [];
for ($i = 0; $i < $block_count; $i++) {
$ratio = ($i + 1) / ($block_count + 1);
$position_index = (int) floor($ratio * $position_count);
if ($position_index >= $position_count) {
$position_index = $position_count - 1;
}
if ($position_index < 0) {
$position_index = 0;
}
$offsets[] = (int) $heading_offsets[$position_index];
}
$delta = 0;
foreach ($blocks as $i => $block_html) {
$insert_at = (int) ($offsets[$i] ?? 0) + $delta;
$insertion = "\n\n" . $block_html . "\n\n";
$enhanced = substr($enhanced, 0, $insert_at) . $insertion . substr($enhanced, $insert_at);
$delta += strlen($insertion);
}
return $enhanced;
}
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';
}
}
function aismart_normalize_type_blog($value) {
$type_blog = (int) $value;
if ($type_blog < 1 || $type_blog > 9) {
$type_blog = 1;
}
return $type_blog;
}
function aismart_type_blog_label($type_blog) {
$type_blog = aismart_normalize_type_blog($type_blog);
$labels = [
1 => 'Simple Blog Post',
2 => 'Advanced Blog Template',
3 => 'Design Case Study',
4 => 'Fully Article',
5 => 'Premium Hybrid AI + SEO',
6 => 'Pillar + Cluster',
7 => 'Expert Interview',
8 => 'Product Comparison',
9 => 'Story + Data Narrative',
];
return isset($labels[$type_blog]) ? $labels[$type_blog] : $labels[1];
}
add_action('wp_ajax_aismart_create_post', 'aismart_create_post');
function aismart_create_post() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['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;
$type_blog = isset($_POST['type_blog']) ? aismart_normalize_type_blog($_POST['type_blog']) : 1;
$type_blog_label = aismart_type_blog_label($type_blog);
$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 === '') {
wp_send_json_error(['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;
// Keep Draft action authoritative even when auto-translate is enabled.
$draft_first_mode = !$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';
}
}
wp_send_json_error([
'message' => 'Schedule time must be in the future (site timezone: ' . sanitize_text_field($site_tz_name) . ').',
], 422);
}
$existing_type_blog = $is_update
? aismart_normalize_type_blog(get_post_meta((int) $target_post_id, '_aismart_type_blog', true))
: 0;
$type_blog_changed = $is_update && $existing_type_blog > 0 && $existing_type_blog !== (int) $type_blog;
if ($is_update && trim((string) $manual_content) !== '' && !$type_blog_changed) {
$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 {
if ($type_blog_changed) {
error_log('AISmart Create Post: template changed from type_blog=' . $existing_type_blog . ' to ' . (int) $type_blog . '; regenerating content for post_id=' . $target_post_id);
}
error_log("AISmart Create Post: topic={$topic}, post_id={$post_id}, target_post_id={$target_post_id}, is_update=" . ($is_update ? '1' : '0'));
$request_payload = [
'site' => 'site2',
'topic' => $topic,
'type_blog' => $type_blog,
'skip_image' => $is_update ? 1 : 0,
'image_prompt' => aismart_build_image_prompt($topic, $category_raw, $tags_raw, $manual_content),
];
$api_result = aismart_request_posts_api($request_payload, 60);
if (empty($api_result['ok'])) {
$first_code = isset($api_result['status']) ? (int) $api_result['status'] : 0;
$first_msg = !empty($api_result['message']) ? (string) $api_result['message'] : 'unknown-error';
error_log('AISmart Create Post: text API first attempt failed; retrying once. status=' . $first_code . ' message=' . $first_msg);
$retry_payload = $request_payload;
$retry_payload['retry_attempt'] = 2;
$retry_payload['retry_reason'] = 'first_attempt_failed';
$api_retry_result = aismart_request_posts_api($retry_payload, 60);
if (!empty($api_retry_result['ok'])) {
error_log('AISmart Create Post: text API retry succeeded for topic=' . $topic);
$api_result = $api_retry_result;
} else {
$retry_code = isset($api_retry_result['status']) ? (int) $api_retry_result['status'] : 0;
$retry_msg = !empty($api_retry_result['message']) ? (string) $api_retry_result['message'] : 'unknown-error';
error_log('AISmart Create Post: text API retry failed. status=' . $retry_code . ' message=' . $retry_msg);
$api_result = $api_retry_result;
}
}
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());
wp_send_json_error(['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)) {
wp_send_json_error(['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);
}
// --- 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'));
update_post_meta((int) $post_id, '_aismart_type_blog', (int) $type_blog);
update_post_meta((int) $post_id, '_aismart_type_blog_label', sanitize_text_field($type_blog_label));
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);
$phase2_body_images_applied = false;
$phase2_body_images_count = 0;
$phase2_body_image_urls = [];
// 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_image_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') ?: '';
}
// Phase2: non-simple templates (2-5) include extra in-body visuals.
$phase2_target_count = aismart_phase2_body_image_count_for_type((int) $type_blog);
if ($phase2_target_count > 0 && !$manual_update_mode && stripos($resolved_content, 'aismart-inline-visual') === false) {
$existing_image_urls = [];
if ($image_url !== '' && filter_var($image_url, FILTER_VALIDATE_URL)) {
$existing_image_urls[] = $image_url;
}
$phase2_body_image_urls = aismart_generate_advanced_body_image_urls(
(int) $post_id,
$topic,
$category_raw,
$tags_raw,
$resolved_excerpt,
(int) $phase2_target_count,
$existing_image_urls
);
if (!empty($phase2_body_image_urls)) {
$phase2_content = aismart_inject_template_body_images($resolved_content, $phase2_body_image_urls, $resolved_title, (int) $type_blog);
if ($phase2_content !== '' && $phase2_content !== $resolved_content) {
$phase2_excerpt = wp_trim_words(wp_strip_all_tags($phase2_content), 40, '...');
$phase2_update = wp_update_post([
'ID' => (int) $post_id,
'post_content' => $phase2_content,
'post_excerpt' => $phase2_excerpt,
], true);
if (!is_wp_error($phase2_update)) {
$resolved_content = $phase2_content;
$resolved_excerpt = $phase2_excerpt;
$phase2_body_images_applied = true;
$phase2_body_images_count = count($phase2_body_image_urls);
update_post_meta((int) $post_id, '_aismart_phase2_body_images_count', (int) $phase2_body_images_count);
update_post_meta((int) $post_id, '_aismart_phase2_body_images_urls', wp_json_encode(array_values($phase2_body_image_urls)));
} else {
error_log('AISmart Phase2 body image injection failed: ' . $phase2_update->get_error_message());
}
}
}
}
if (!$phase2_body_images_applied) {
if (stripos($resolved_content, 'aismart-inline-visual') !== false) {
$existing_phase2_urls = [];
$existing_url_matches = [];
preg_match_all('/]*aismart-inline-visual[^>]*>.*? ]*\bsrc=["\']([^"\']+)["\']/is', $resolved_content, $existing_url_matches);
if (!empty($existing_url_matches[1]) && is_array($existing_url_matches[1])) {
foreach ($existing_url_matches[1] as $match_url) {
$url = esc_url_raw((string) $match_url);
if ($url !== '' && filter_var($url, FILTER_VALIDATE_URL)) {
$existing_phase2_urls[$url] = true;
}
}
}
if (!empty($existing_phase2_urls)) {
$phase2_body_image_urls = array_values(array_keys($existing_phase2_urls));
$phase2_body_images_count = count($phase2_body_image_urls);
update_post_meta((int) $post_id, '_aismart_phase2_body_images_count', (int) $phase2_body_images_count);
update_post_meta((int) $post_id, '_aismart_phase2_body_images_urls', wp_json_encode(array_values($phase2_body_image_urls)));
} else {
$existing_marker_matches = [];
preg_match_all('/aismart-inline-visual-[0-9]+/i', $resolved_content, $existing_marker_matches);
$existing_marker_count = 0;
if (!empty($existing_marker_matches[0]) && is_array($existing_marker_matches[0])) {
$existing_marker_count = count(array_unique($existing_marker_matches[0]));
}
if ($existing_marker_count > 0) {
$phase2_body_images_count = (int) $existing_marker_count;
update_post_meta((int) $post_id, '_aismart_phase2_body_images_count', (int) $phase2_body_images_count);
}
}
} else {
delete_post_meta((int) $post_id, '_aismart_phase2_body_images_count');
delete_post_meta((int) $post_id, '_aismart_phase2_body_images_urls');
}
}
// 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', 'translatepress', 'polylang'], 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 === '') {
if ($queue_engine_for_background === 'wpml') {
$translation_warning = 'Translation queued in background for WPML.';
} elseif ($queue_engine_for_background === 'bogo') {
$translation_warning = 'Translation queued in background for Bogo.';
} elseif ($queue_engine_for_background === 'translatepress') {
$translation_warning = 'Translation queued in background for TranslatePress.';
} elseif ($queue_engine_for_background === 'polylang') {
$translation_warning = 'Translation queued in background for Polylang.';
} else {
$translation_warning = 'Translation queued in 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;
if ($phase2_body_images_count > 0) {
$wp_api_units_image += (int) $phase2_body_images_count;
}
$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 : 1;
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,
'phase2_body_images_applied' => $phase2_body_images_applied ? 1 : 0,
'phase2_body_images_count' => (int) $phase2_body_images_count,
'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'
);
wp_send_json_success([
'post_id' => $post_id,
'queue_id' => $queue_id > 0 ? $queue_id : 0,
'title' => $resolved_title,
'content' => $resolved_content,
'excerpt' => $resolved_excerpt,
'type_blog' => (int) $type_blog,
'type_blog_label' => $type_blog_label,
'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,
'phase2_body_images_applied' => $phase2_body_images_applied ? 1 : 0,
'phase2_body_images_count' => (int) $phase2_body_images_count,
'phase2_body_image_urls' => array_values($phase2_body_image_urls),
'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')) {
wp_send_json_error(['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'])) : '';
$variation_token = isset($_POST['variation_token']) ? sanitize_text_field(wp_unslash($_POST['variation_token'])) : '';
if ($topic === '') {
wp_send_json_error(['message' => 'Topic required'], 422);
}
if ($variation_token === '') {
$variation_token = aismart_build_image_variation_token($topic);
}
error_log('AISmart Regenerate Image: topic=' . $topic);
$image_fallback_used = false;
$image_fallback_reason = '';
$existing_image_url = '';
if ($post_id > 0) {
$existing_image_url = (string) get_the_post_thumbnail_url($post_id, 'full');
if (!filter_var($existing_image_url, FILTER_VALIDATE_URL)) {
$existing_image_url = '';
}
}
$api_payload = [
'site' => 'site2',
'topic' => $topic,
'image_only' => 1,
'image_prompt' => aismart_build_image_prompt($topic, $category_raw, $tags_raw),
'variation_token' => $variation_token,
'variation' => $variation_token,
'force_new' => 1,
];
if ($existing_image_url !== '') {
$api_payload['existing_image_url'] = $existing_image_url;
}
$api_result = aismart_request_posts_image_api($api_payload, 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');
$existing_thumbnail = get_the_post_thumbnail_url($post_id, 'full');
if (is_string($existing_thumbnail) && $existing_thumbnail !== '') {
$image_url = $existing_thumbnail;
}
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'
);
wp_send_json_success([
'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')) {
wp_send_json_error(['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'])) : '';
$variation_token = isset($_POST['variation_token']) ? sanitize_text_field(wp_unslash($_POST['variation_token'])) : '';
if ($topic === '' || $post_id <= 0) {
wp_send_json_error(['message' => 'Topic and post_id are required'], 422);
}
if ($variation_token === '') {
$variation_token = aismart_build_image_variation_token($topic);
}
$image_fallback_used = false;
$image_fallback_reason = '';
$existing_image_url = (string) get_the_post_thumbnail_url($post_id, 'full');
if (!filter_var($existing_image_url, FILTER_VALIDATE_URL)) {
$existing_image_url = '';
}
$api_payload = [
'site' => 'site2',
'topic' => $topic,
'image_only' => 1,
'image_prompt' => aismart_build_image_prompt($topic, $category_raw, $tags_raw),
'variation_token' => $variation_token,
'variation' => $variation_token,
'force_new' => 1,
];
if ($existing_image_url !== '') {
$api_payload['existing_image_url'] = $existing_image_url;
}
$api_result = aismart_request_posts_image_api($api_payload, 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') ?: '';
wp_send_json_success([
'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');
$existing_image = get_the_post_thumbnail_url($post_id, 'full') ?: '';
if (is_string($existing_image) && $existing_image !== '') {
$image_url = $existing_image;
}
wp_send_json_success([
'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'
);
wp_send_json_success([
'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,
];
}
function aismart_plugin_get_version() {
static $version = null;
if ($version !== null) {
return $version;
}
$version = '2.9.9';
if (function_exists('get_plugin_data')) {
$plugin_data = get_plugin_data(__FILE__, false, false);
if (is_array($plugin_data) && !empty($plugin_data['Version'])) {
$version = sanitize_text_field((string) $plugin_data['Version']);
}
}
return $version;
}
function aismart_bonus_site_domain() {
$domain = strtolower(trim((string) wp_parse_url(home_url('/'), PHP_URL_HOST)));
if ($domain === '') {
$domain = strtolower(trim((string) wp_parse_url(site_url('/'), PHP_URL_HOST)));
}
return sanitize_text_field($domain);
}
function aismart_bonus_admin_email_hash() {
$email = strtolower(trim((string) aismart_onboarding_current_admin_email()));
if (!is_email($email)) {
return '';
}
return hash('sha256', $email);
}
function aismart_bonus_demo_mode_enabled() {
if ((int) get_option('aismart_bonus_demo_mode', 0) === 1) {
return true;
}
$domain = aismart_bonus_site_domain();
return $domain === 'boonragsa.mytelemed.co';
}
function aismart_bonus_demo_campaign_id() {
return 'WELCOME-DEMO-2026Q3';
}
function aismart_bonus_demo_credits() {
return 100;
}
function aismart_bonus_demo_claim_option_key($workspace_id, $site_domain, $field = 'claimed') {
$workspace_id = max(0, (int) $workspace_id);
$site_domain = strtolower(trim((string) $site_domain));
$field = sanitize_key((string) $field);
return 'aismart_bonus_demo_' . $field . '_' . $workspace_id . '_' . md5($site_domain);
}
function aismart_bonus_demo_snapshot($workspace_id, $site_domain) {
$workspace_id = max(0, (int) $workspace_id);
$site_domain = sanitize_text_field((string) $site_domain);
$snapshot = aismart_bonus_default_snapshot($workspace_id, $site_domain);
$claimed_key = aismart_bonus_demo_claim_option_key($workspace_id, $site_domain, 'claimed');
$claimed_at_key = aismart_bonus_demo_claim_option_key($workspace_id, $site_domain, 'claimed_at');
$claimed = (int) get_option($claimed_key, 0) === 1;
$claimed_at = (string) get_option($claimed_at_key, '');
$snapshot['campaign_id'] = aismart_bonus_demo_campaign_id();
$snapshot['offer_title'] = 'Welcome Bonus';
$snapshot['bonus_credits'] = (int) aismart_bonus_demo_credits();
$snapshot['expires_at'] = gmdate('c', strtotime('+30 days'));
$snapshot['eligible'] = $claimed ? 0 : 1;
$snapshot['claimed'] = $claimed ? 1 : 0;
$snapshot['status'] = $claimed ? 'claimed' : 'unclaimed';
$snapshot['reason'] = $claimed ? 'already_claimed' : 'eligible';
if ($claimed && $claimed_at !== '') {
$snapshot['claimed_at'] = sanitize_text_field($claimed_at);
}
return $snapshot;
}
function aismart_bonus_demo_mark_claimed($workspace_id, $site_domain, $campaign_id = '') {
$workspace_id = max(0, (int) $workspace_id);
$site_domain = sanitize_text_field((string) $site_domain);
$bonus_credits = (float) aismart_bonus_demo_credits();
$claimed_key = aismart_bonus_demo_claim_option_key($workspace_id, $site_domain, 'claimed');
$claimed_at_key = aismart_bonus_demo_claim_option_key($workspace_id, $site_domain, 'claimed_at');
update_option($claimed_key, 1, false);
$claimed_at = gmdate('c');
update_option($claimed_at_key, $claimed_at, false);
$current_balance = 0.0;
if ($workspace_id > 0) {
$current_balance = (float) get_option('aismart_workspace_credits_' . $workspace_id, 0);
}
if ($current_balance <= 0) {
$current_balance = (float) get_option('aismart_credit_balance', 0);
}
$new_balance = $current_balance + $bonus_credits;
if ($workspace_id > 0) {
update_option('aismart_workspace_credits_' . $workspace_id, $new_balance, false);
}
update_option('aismart_credit_balance', $new_balance, false);
update_option('aismart_credits_available', $new_balance, false);
$snapshot = aismart_bonus_demo_snapshot($workspace_id, $site_domain);
if ($campaign_id !== '') {
$snapshot['campaign_id'] = sanitize_text_field((string) $campaign_id);
}
$snapshot['claimed'] = 1;
$snapshot['eligible'] = 0;
$snapshot['status'] = 'claimed';
$snapshot['reason'] = 'claimed';
$snapshot['claimed_at'] = $claimed_at;
$snapshot['credited_wp_api_units'] = $bonus_credits;
$snapshot['new_balance_wp_api_units'] = $new_balance;
return aismart_bonus_store_snapshot($snapshot);
}
function aismart_bonus_default_snapshot($workspace_id = 0, $site_domain = '') {
return [
'workspace_id' => max(0, (int) $workspace_id),
'site_domain' => sanitize_text_field((string) $site_domain),
'status' => 'unknown',
'reason' => '',
'eligible' => 0,
'claimed' => 0,
'campaign_id' => '',
'offer_title' => '',
'bonus_credits' => 0,
'expires_at' => '',
'claimed_at' => '',
'credited_wp_api_units' => 0,
'new_balance_wp_api_units' => 0,
'updated_at' => gmdate('c'),
];
}
function aismart_bonus_cache_key($workspace_id, $site_domain) {
$workspace_id = max(0, (int) $workspace_id);
$site_domain = strtolower(trim((string) $site_domain));
return $workspace_id . '|' . $site_domain;
}
function aismart_bonus_state_get() {
$state = aismart_onboarding_state_get();
$bonus = isset($state['bonus']) && is_array($state['bonus']) ? $state['bonus'] : [];
if (!isset($bonus['snapshots']) || !is_array($bonus['snapshots'])) {
$bonus['snapshots'] = [];
}
return $bonus;
}
function aismart_bonus_state_set($bonus_state) {
$state = aismart_onboarding_state_get();
$state['bonus'] = is_array($bonus_state) ? $bonus_state : [];
aismart_onboarding_state_set($state);
}
function aismart_bonus_merge_snapshot($response, $fallback = []) {
$workspace_id = isset($fallback['workspace_id']) ? (int) $fallback['workspace_id'] : 0;
$site_domain = isset($fallback['site_domain']) ? sanitize_text_field((string) $fallback['site_domain']) : '';
$snapshot = aismart_bonus_default_snapshot($workspace_id, $site_domain);
if (!is_array($response)) {
return $snapshot;
}
$payload = (isset($response['data']) && is_array($response['data'])) ? $response['data'] : $response;
if (!is_array($payload)) {
return $snapshot;
}
if (isset($payload['workspace_id']) && is_numeric($payload['workspace_id'])) {
$snapshot['workspace_id'] = (int) $payload['workspace_id'];
}
if (!empty($payload['site_domain'])) {
$snapshot['site_domain'] = sanitize_text_field((string) $payload['site_domain']);
}
$reason = isset($payload['reason']) ? sanitize_key((string) $payload['reason']) : '';
$snapshot['reason'] = $reason;
if (array_key_exists('eligible', $payload)) {
$snapshot['eligible'] = !empty($payload['eligible']) ? 1 : 0;
}
if (array_key_exists('claimed', $payload)) {
$snapshot['claimed'] = !empty($payload['claimed']) ? 1 : 0;
}
if (!empty($payload['status'])) {
$snapshot['status'] = sanitize_key((string) $payload['status']);
}
// Some API responses use transport status=ok plus semantic state=unclaimed/claimed.
if (!empty($payload['state'])) {
$api_state = sanitize_key((string) $payload['state']);
if ($api_state !== '') {
$snapshot['status'] = $api_state;
}
}
if (!empty($payload['campaign_id'])) {
$snapshot['campaign_id'] = sanitize_text_field((string) $payload['campaign_id']);
}
if (isset($payload['offer']) && is_array($payload['offer'])) {
if (!empty($payload['offer']['campaign_id'])) {
$snapshot['campaign_id'] = sanitize_text_field((string) $payload['offer']['campaign_id']);
}
if (!empty($payload['offer']['title'])) {
$snapshot['offer_title'] = sanitize_text_field((string) $payload['offer']['title']);
}
if (isset($payload['offer']['bonus_credits']) && is_numeric($payload['offer']['bonus_credits'])) {
$snapshot['bonus_credits'] = (int) $payload['offer']['bonus_credits'];
}
if (!empty($payload['offer']['expires_at'])) {
$snapshot['expires_at'] = sanitize_text_field((string) $payload['offer']['expires_at']);
}
}
if (!empty($payload['expires_at'])) {
$snapshot['expires_at'] = sanitize_text_field((string) $payload['expires_at']);
}
if (!empty($payload['claimed_at'])) {
$snapshot['claimed_at'] = sanitize_text_field((string) $payload['claimed_at']);
}
if (isset($payload['credited']) && is_array($payload['credited']) && isset($payload['credited']['wp_api_units']) && is_numeric($payload['credited']['wp_api_units'])) {
$snapshot['credited_wp_api_units'] = (float) $payload['credited']['wp_api_units'];
}
if (isset($payload['new_balance']) && is_array($payload['new_balance']) && isset($payload['new_balance']['wp_api_units']) && is_numeric($payload['new_balance']['wp_api_units'])) {
$snapshot['new_balance_wp_api_units'] = (float) $payload['new_balance']['wp_api_units'];
}
if ($snapshot['status'] === 'unknown' || $snapshot['status'] === 'ok') {
if ($snapshot['claimed'] === 1 || in_array($reason, ['claimed', 'already_claimed', 'duplicate_claim'], true)) {
$snapshot['status'] = 'claimed';
} elseif ($snapshot['eligible'] === 1 || $reason === 'eligible') {
$snapshot['status'] = 'unclaimed';
} elseif (in_array($reason, ['expired', 'campaign_closed'], true)) {
$snapshot['status'] = 'expired';
} elseif ($reason !== '') {
$snapshot['status'] = 'ineligible';
}
}
// Keep flags aligned with normalized status for frontend checks.
if ($snapshot['status'] === 'unclaimed') {
$snapshot['eligible'] = 1;
$snapshot['claimed'] = 0;
} elseif ($snapshot['status'] === 'claimed') {
$snapshot['claimed'] = 1;
$snapshot['eligible'] = 0;
} elseif (in_array($snapshot['status'], ['expired', 'ineligible'], true)) {
$snapshot['eligible'] = 0;
}
if ($snapshot['status'] === 'claimed') {
$snapshot['claimed'] = 1;
}
$snapshot['updated_at'] = gmdate('c');
return $snapshot;
}
function aismart_bonus_store_snapshot($snapshot) {
$snapshot = is_array($snapshot) ? $snapshot : [];
$workspace_id = isset($snapshot['workspace_id']) ? (int) $snapshot['workspace_id'] : 0;
$site_domain = isset($snapshot['site_domain']) ? sanitize_text_field((string) $snapshot['site_domain']) : '';
$key = aismart_bonus_cache_key($workspace_id, $site_domain);
$bonus_state = aismart_bonus_state_get();
$bonus_state['snapshots'][$key] = $snapshot;
$bonus_state['last_snapshot_key'] = $key;
$bonus_state['last_updated_at'] = gmdate('c');
if (!empty($snapshot['campaign_id'])) {
$bonus_state['last_campaign_id'] = sanitize_text_field((string) $snapshot['campaign_id']);
}
aismart_bonus_state_set($bonus_state);
return $snapshot;
}
function aismart_bonus_get_cached_snapshot($workspace_id, $site_domain = '') {
$workspace_id = max(0, (int) $workspace_id);
$site_domain = $site_domain !== '' ? sanitize_text_field((string) $site_domain) : aismart_bonus_site_domain();
$bonus_state = aismart_bonus_state_get();
$key = aismart_bonus_cache_key($workspace_id, $site_domain);
if (isset($bonus_state['snapshots'][$key]) && is_array($bonus_state['snapshots'][$key])) {
return $bonus_state['snapshots'][$key];
}
$last_key = isset($bonus_state['last_snapshot_key']) ? (string) $bonus_state['last_snapshot_key'] : '';
if ($last_key !== '' && isset($bonus_state['snapshots'][$last_key]) && is_array($bonus_state['snapshots'][$last_key])) {
return $bonus_state['snapshots'][$last_key];
}
return null;
}
function aismart_bonus_snapshot_is_fresh($snapshot, $ttl_seconds = 60) {
if (!is_array($snapshot) || empty($snapshot['updated_at'])) {
return false;
}
$updated_at = strtotime((string) $snapshot['updated_at']);
if (!$updated_at) {
return false;
}
return (time() - $updated_at) <= max(1, (int) $ttl_seconds);
}
function aismart_bonus_extract_error($error, $fallback_message = 'Bonus request failed') {
$status = 500;
$message = $fallback_message;
if ($error instanceof WP_Error) {
$status_data = $error->get_error_data('status');
if (is_numeric($status_data)) {
$status = (int) $status_data;
}
$data = $error->get_error_data();
if (is_array($data)) {
if (isset($data['status']) && is_numeric($data['status'])) {
$status = (int) $data['status'];
}
if (isset($data['response']) && is_array($data['response'])) {
if (!empty($data['response']['message'])) {
$message = sanitize_text_field((string) $data['response']['message']);
} elseif (!empty($data['response']['reason'])) {
$message = sanitize_text_field((string) $data['response']['reason']);
}
}
}
if ($message === $fallback_message) {
$raw = trim((string) $error->get_error_message());
if ($raw !== '') {
$message = sanitize_text_field($raw);
}
}
}
return [
'status' => $status > 0 ? $status : 500,
'message' => $message,
];
}
function aismart_bonus_get_snapshot_for_workspace($workspace_id = 0, $force_refresh = false) {
$ctx = aismart_resolve_workspace_context();
$workspace_id = $workspace_id > 0 ? (int) $workspace_id : (int) ($ctx['workspace_id'] ?? 0);
$site_domain = aismart_bonus_site_domain();
if ($workspace_id <= 0) {
return aismart_bonus_default_snapshot(0, $site_domain);
}
$cached = aismart_bonus_get_cached_snapshot($workspace_id, $site_domain);
if (!$force_refresh && aismart_bonus_snapshot_is_fresh($cached, 60)) {
return $cached;
}
$status_result = aismart_plugin_api_request('GET', 'api/plugin/v1/bonus/status', [
'workspace_id' => $workspace_id,
'site_domain' => $site_domain,
], false);
if (is_wp_error($status_result)) {
if (aismart_bonus_demo_mode_enabled()) {
$demo_snapshot = aismart_bonus_demo_snapshot($workspace_id, $site_domain);
return aismart_bonus_store_snapshot($demo_snapshot);
}
if (is_array($cached)) {
return $cached;
}
return aismart_bonus_default_snapshot($workspace_id, $site_domain);
}
$snapshot = aismart_bonus_merge_snapshot($status_result, [
'workspace_id' => $workspace_id,
'site_domain' => $site_domain,
]);
return aismart_bonus_store_snapshot($snapshot);
}
function aismart_bonus_claim_idempotency_key($workspace_id, $site_domain, $campaign_id) {
$seed = implode('|', [
(string) get_current_blog_id(),
(int) $workspace_id,
strtolower(trim((string) $site_domain)),
sanitize_text_field((string) $campaign_id),
'claim',
]);
return 'wp-bonus-claim-' . md5($seed);
}
function aismart_bonus_event_idempotency_key($event_name, $workspace_id, $site_domain) {
$seed = implode('|', [
sanitize_key((string) $event_name),
(int) $workspace_id,
strtolower(trim((string) $site_domain)),
microtime(true),
wp_rand(),
]);
return 'wp-bonus-event-' . md5($seed);
}
function aismart_bonus_collect_meta_from_request() {
if (!isset($_POST['meta'])) {
return [];
}
$raw_meta = wp_unslash($_POST['meta']);
if (is_array($raw_meta)) {
return $raw_meta;
}
if (!is_string($raw_meta) || trim($raw_meta) === '') {
return [];
}
$decoded = json_decode((string) $raw_meta, true);
return is_array($decoded) ? $decoded : [];
}
function aismart_bonus_update_workspace_credit_from_snapshot($workspace_id, $snapshot) {
if (!is_array($snapshot)) {
return;
}
$new_balance = null;
if (isset($snapshot['new_balance_wp_api_units']) && is_numeric($snapshot['new_balance_wp_api_units'])) {
$new_balance = (float) $snapshot['new_balance_wp_api_units'];
}
// Some claim responses only return credited amount; derive a safe fallback balance.
if ($new_balance === null) {
$credited = 0.0;
if (isset($snapshot['credited_wp_api_units']) && is_numeric($snapshot['credited_wp_api_units'])) {
$credited = (float) $snapshot['credited_wp_api_units'];
} elseif (isset($snapshot['bonus_credits']) && is_numeric($snapshot['bonus_credits'])) {
$credited = (float) $snapshot['bonus_credits'];
}
$is_claimed = (!empty($snapshot['claimed']) || (isset($snapshot['status']) && sanitize_key((string) $snapshot['status']) === 'claimed'));
if ($is_claimed && $credited > 0) {
$current_balance = 0.0;
if ($workspace_id > 0) {
$current_balance = (float) get_option('aismart_workspace_credits_' . (int) $workspace_id, 0);
}
if ($current_balance <= 0) {
$current_balance = (float) get_option('aismart_credit_balance', 0);
}
if ($current_balance <= 0) {
$current_balance = (float) get_option('aismart_credits_available', 0);
}
$new_balance = $current_balance + $credited;
}
}
if ($new_balance === null) {
return;
}
if ($workspace_id > 0) {
update_option('aismart_workspace_credits_' . (int) $workspace_id, $new_balance, false);
}
update_option('aismart_credit_balance', $new_balance, false);
update_option('aismart_credits_available', $new_balance, false);
}
add_action('wp_ajax_aismart_bonus_status', 'aismart_bonus_status');
function aismart_bonus_status() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
$ctx = aismart_resolve_workspace_context();
$workspace_id = isset($_POST['workspace_id']) ? (int) wp_unslash($_POST['workspace_id']) : (int) ($ctx['workspace_id'] ?? 0);
$force_refresh = isset($_POST['force']) && (int) wp_unslash($_POST['force']) === 1;
if ($workspace_id <= 0) {
wp_send_json_error(['message' => 'Invalid workspace id'], 422);
}
$snapshot = aismart_bonus_get_snapshot_for_workspace($workspace_id, $force_refresh);
wp_send_json_success($snapshot);
}
add_action('wp_ajax_aismart_bonus_eligibility', 'aismart_bonus_eligibility');
function aismart_bonus_eligibility() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
$ctx = aismart_resolve_workspace_context();
$workspace_id = isset($_POST['workspace_id']) ? (int) wp_unslash($_POST['workspace_id']) : (int) ($ctx['workspace_id'] ?? 0);
if ($workspace_id <= 0) {
wp_send_json_error(['message' => 'Invalid workspace id'], 422);
}
$site_domain = aismart_bonus_site_domain();
$admin_email_hash = aismart_bonus_admin_email_hash();
if ($admin_email_hash === '') {
wp_send_json_error(['message' => 'Missing admin email hash'], 422);
}
$payload = [
'workspace_id' => $workspace_id,
'site_domain' => $site_domain,
'admin_email_hash' => $admin_email_hash,
'plugin_version' => aismart_plugin_get_version(),
];
foreach (['campaign', 'utm_source', 'utm_medium', 'utm_campaign'] as $optional_key) {
if (isset($_POST[$optional_key])) {
$value = sanitize_text_field((string) wp_unslash($_POST[$optional_key]));
if ($value !== '') {
$payload[$optional_key] = $value;
}
}
}
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/bonus/eligibility', $payload, false);
if (is_wp_error($result)) {
if (aismart_bonus_demo_mode_enabled()) {
$snapshot = aismart_bonus_demo_snapshot($workspace_id, $site_domain);
if (!empty($payload['campaign'])) {
$snapshot['campaign_id'] = sanitize_text_field((string) $payload['campaign']);
}
$snapshot = aismart_bonus_store_snapshot($snapshot);
wp_send_json_success($snapshot);
}
$error = aismart_bonus_extract_error($result, 'Eligibility check failed');
if (function_exists('aismart_queue_log')) {
aismart_queue_log('Bonus eligibility failed workspace=' . $workspace_id . ' domain=' . $site_domain . ' message=' . $error['message']);
}
wp_send_json_error(['message' => $error['message']], $error['status']);
}
$snapshot = aismart_bonus_merge_snapshot($result, [
'workspace_id' => $workspace_id,
'site_domain' => $site_domain,
]);
$snapshot = aismart_bonus_store_snapshot($snapshot);
if (function_exists('aismart_queue_log')) {
aismart_queue_log('Bonus eligibility workspace=' . $workspace_id . ' domain=' . $site_domain . ' status=' . sanitize_key((string) ($snapshot['status'] ?? 'unknown')) . ' reason=' . sanitize_key((string) ($snapshot['reason'] ?? '')));
}
wp_send_json_success($snapshot);
}
add_action('wp_ajax_aismart_bonus_claim', 'aismart_bonus_claim');
function aismart_bonus_claim() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
$ctx = aismart_resolve_workspace_context();
$workspace_id = isset($_POST['workspace_id']) ? (int) wp_unslash($_POST['workspace_id']) : (int) ($ctx['workspace_id'] ?? 0);
if ($workspace_id <= 0) {
wp_send_json_error(['message' => 'Invalid workspace id'], 422);
}
$site_domain = aismart_bonus_site_domain();
$cached_snapshot = aismart_bonus_get_cached_snapshot($workspace_id, $site_domain);
$campaign_id = isset($_POST['bonus_campaign_id'])
? sanitize_text_field((string) wp_unslash($_POST['bonus_campaign_id']))
: '';
if ($campaign_id === '' && is_array($cached_snapshot) && !empty($cached_snapshot['campaign_id'])) {
$campaign_id = sanitize_text_field((string) $cached_snapshot['campaign_id']);
}
if ($campaign_id === '') {
$bonus_state = aismart_bonus_state_get();
if (!empty($bonus_state['last_campaign_id'])) {
$campaign_id = sanitize_text_field((string) $bonus_state['last_campaign_id']);
}
}
if ($campaign_id === '') {
if (aismart_bonus_demo_mode_enabled()) {
$campaign_id = aismart_bonus_demo_campaign_id();
}
}
if ($campaign_id === '') {
wp_send_json_error(['message' => 'Missing bonus campaign id'], 422);
}
$lock_key = 'aismart_bonus_claim_lock_' . md5($workspace_id . '|' . $site_domain);
if (get_transient($lock_key)) {
wp_send_json_error(['message' => 'Bonus claim is already being processed. Please wait a moment.'], 409);
}
set_transient($lock_key, 1, 45);
try {
$idempotency_key = aismart_bonus_claim_idempotency_key($workspace_id, $site_domain, $campaign_id);
$payload = [
'workspace_id' => $workspace_id,
'site_domain' => $site_domain,
'bonus_campaign_id' => $campaign_id,
'idempotency_key' => $idempotency_key,
'plugin_version' => aismart_plugin_get_version(),
];
$meta = aismart_bonus_collect_meta_from_request();
if (!empty($meta)) {
$payload['meta'] = $meta;
}
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/bonus/claim', $payload, true, $idempotency_key);
if (is_wp_error($result)) {
if (aismart_bonus_demo_mode_enabled()) {
$snapshot = aismart_bonus_demo_mark_claimed($workspace_id, $site_domain, $campaign_id);
aismart_bonus_update_workspace_credit_from_snapshot($workspace_id, $snapshot);
wp_send_json_success($snapshot);
}
$error = aismart_bonus_extract_error($result, 'Bonus claim failed');
$status_snapshot = aismart_bonus_get_snapshot_for_workspace($workspace_id, true);
if (function_exists('aismart_queue_log')) {
aismart_queue_log('Bonus claim failed workspace=' . $workspace_id . ' domain=' . $site_domain . ' status=' . (int) $error['status'] . ' message=' . $error['message']);
}
wp_send_json_error([
'message' => $error['message'],
'bonus' => $status_snapshot,
], $error['status']);
}
$snapshot = aismart_bonus_merge_snapshot($result, [
'workspace_id' => $workspace_id,
'site_domain' => $site_domain,
]);
if (empty($snapshot['campaign_id'])) {
$snapshot['campaign_id'] = $campaign_id;
}
$snapshot = aismart_bonus_store_snapshot($snapshot);
aismart_bonus_update_workspace_credit_from_snapshot($workspace_id, $snapshot);
if (function_exists('aismart_queue_log')) {
aismart_queue_log('Bonus claim workspace=' . $workspace_id . ' domain=' . $site_domain . ' status=' . sanitize_key((string) ($snapshot['status'] ?? 'unknown')) . ' reason=' . sanitize_key((string) ($snapshot['reason'] ?? '')));
}
wp_send_json_success($snapshot);
} finally {
delete_transient($lock_key);
}
}
add_action('wp_ajax_aismart_bonus_event', 'aismart_bonus_event');
function aismart_bonus_event() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
$event_name = isset($_POST['event_name']) ? sanitize_key((string) wp_unslash($_POST['event_name'])) : '';
if ($event_name === '' || !preg_match('/^[a-z0-9_\-]{3,64}$/', $event_name)) {
wp_send_json_error(['message' => 'Invalid event name'], 422);
}
$ctx = aismart_resolve_workspace_context();
$workspace_id = isset($_POST['workspace_id']) ? (int) wp_unslash($_POST['workspace_id']) : (int) ($ctx['workspace_id'] ?? 0);
if ($workspace_id <= 0) {
wp_send_json_error(['message' => 'Invalid workspace id'], 422);
}
$site_domain = aismart_bonus_site_domain();
$payload = [
'event_name' => $event_name,
'workspace_id' => $workspace_id,
'site_domain' => $site_domain,
];
foreach (['campaign', 'utm_source', 'utm_medium', 'utm_campaign'] as $optional_key) {
if (isset($_POST[$optional_key])) {
$value = sanitize_text_field((string) wp_unslash($_POST[$optional_key]));
if ($value !== '') {
$payload[$optional_key] = $value;
}
}
}
if (empty($payload['campaign'])) {
$cached_snapshot = aismart_bonus_get_cached_snapshot($workspace_id, $site_domain);
if (is_array($cached_snapshot) && !empty($cached_snapshot['campaign_id'])) {
$payload['campaign'] = sanitize_text_field((string) $cached_snapshot['campaign_id']);
}
}
$meta = aismart_bonus_collect_meta_from_request();
if (!empty($meta)) {
$payload['meta'] = $meta;
}
$idempotency_key = aismart_bonus_event_idempotency_key($event_name, $workspace_id, $site_domain);
$result = aismart_plugin_api_request('POST', 'api/plugin/v1/bonus/event', $payload, true, $idempotency_key);
if (is_wp_error($result)) {
$error = aismart_bonus_extract_error($result, 'Bonus event failed');
if (function_exists('aismart_queue_log')) {
aismart_queue_log('Bonus event failed event=' . $event_name . ' workspace=' . $workspace_id . ' domain=' . $site_domain . ' message=' . $error['message']);
}
wp_send_json_success([
'sent' => 0,
'message' => $error['message'],
]);
}
wp_send_json_success([
'sent' => 1,
'event_name' => $event_name,
]);
}
add_action('wp_ajax_aismart_get_credit_snapshot', 'aismart_get_credit_snapshot');
function aismart_get_credit_snapshot() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['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));
}
$bonus_snapshot = aismart_bonus_get_snapshot_for_workspace((int) ($ctx['workspace_id'] ?? 0), false);
wp_send_json_success([
'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,
'bonus' => $bonus_snapshot,
]);
}
add_action('wp_ajax_aismart_get_settings', 'aismart_get_settings');
function aismart_get_settings() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['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'));
if (!in_array($schedule, ['1-per-day', '3-per-day', '5-per-week', 'every-n-hours'], true)) {
$schedule = '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);
$schedule_time_weekly = aismart_normalize_schedule_clock((string) get_option('aismart_schedule_time_weekly', $schedule_time_daily), $schedule_time_daily);
$schedule_days_weekly = aismart_parse_schedule_weekly_days((string) get_option('aismart_schedule_days_weekly', '1,2,3,4,5'));
$schedule_interval_hours = aismart_normalize_schedule_interval_hours(get_option('aismart_schedule_interval_hours', 6), 6);
$schedule_time_interval_start = aismart_normalize_schedule_clock((string) get_option('aismart_schedule_time_interval_start', '06:00'), '06:00');
$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;
$bonus_snapshot = aismart_bonus_get_snapshot_for_workspace($workspace_id, false);
$multilang = aismart_onboarding_detect_multilang_provider();
$provider = sanitize_key((string) ($multilang['primary'] ?? 'none'));
$active_url_mode = aismart_detect_provider_default_lang_url_mode($provider);
$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'), '.') . ')';
}
wp_send_json_success([
'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,
'schedule_time_weekly' => $schedule_time_weekly,
'schedule_days_weekly' => $schedule_days_weekly,
'schedule_interval_hours' => (int) $schedule_interval_hours,
'schedule_time_interval_start' => $schedule_time_interval_start,
'default_lang_url_mode' => $default_lang_url_mode,
'active_default_lang_url_mode' => sanitize_key((string) ($active_url_mode['mode'] ?? 'unknown')),
'active_default_lang_url_mode_detail' => sanitize_text_field((string) ($active_url_mode['detail'] ?? '')),
'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,
'bonus' => $bonus_snapshot,
]);
}
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_parse_schedule_weekly_days($raw) {
$defaults = [1, 2, 3, 4, 5];
$days = [];
if (is_array($raw)) {
foreach ($raw as $entry) {
$days[] = (int) $entry;
}
} else {
$raw = sanitize_text_field((string) $raw);
$parts = array_map('trim', explode(',', $raw));
foreach ($parts as $entry) {
if ($entry !== '') {
$days[] = (int) $entry;
}
}
}
$normalized = [];
foreach ($days as $day) {
$day = (int) $day;
if ($day >= 1 && $day <= 7) {
$normalized[] = $day;
}
}
$normalized = array_values(array_unique($normalized));
sort($normalized, SORT_NUMERIC);
if (empty($normalized)) {
return $defaults;
}
return $normalized;
}
function aismart_normalize_schedule_interval_hours($value, $fallback = 6) {
$fallback = (int) $fallback;
if ($fallback < 1 || $fallback > 24) {
$fallback = 6;
}
$interval = (int) $value;
if ($interval < 1 || $interval > 24) {
$interval = $fallback;
}
return $interval;
}
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', 'every-n-hours'], 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'));
$weekly_time = aismart_normalize_schedule_clock((string) get_option('aismart_schedule_time_weekly', $daily_time), $daily_time);
$weekly_days = aismart_parse_schedule_weekly_days((string) get_option('aismart_schedule_days_weekly', '1,2,3,4,5'));
$interval_hours = aismart_normalize_schedule_interval_hours(get_option('aismart_schedule_interval_hours', 6), 6);
$interval_start = aismart_normalize_schedule_clock((string) get_option('aismart_schedule_time_interval_start', '06:00'), '06:00');
$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');
if ($schedule === 'every-n-hours') {
$interval_minutes = max(60, (int) $interval_hours * 60);
$anchor = DateTime::createFromFormat('Y-m-d H:i', '2000-01-03 ' . $interval_start, $tz);
if (!($anchor instanceof DateTime)) {
$anchor = clone $now;
[$anchor_h, $anchor_m] = array_pad(explode(':', $interval_start), 2, '00');
$anchor->setTime((int) $anchor_h, (int) $anchor_m, 0);
}
$now_minute_index = (int) floor(((int) $now->format('U')) / 60);
$anchor_minute_index = (int) floor(((int) $anchor->format('U')) / 60);
$delta_minutes = $now_minute_index - $anchor_minute_index;
if ($delta_minutes < 0 || ($delta_minutes % $interval_minutes) !== 0) {
return null;
}
return [
'slot_key' => $date_key . '|' . $schedule . '|' . $hhmm . '|q' . $interval_hours . '@' . $interval_start,
'date' => $date_key,
'time' => $hhmm,
'schedule' => $schedule,
];
}
$allowed_times = [];
if ($schedule === '1-per-day') {
$allowed_times[] = $daily_time;
} elseif ($schedule === '3-per-day') {
$allowed_times = $times_three;
} elseif ($schedule === '5-per-week') {
if (in_array($weekday, $weekly_days, true)) {
$allowed_times[] = $weekly_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')) {
wp_send_json_error(['message' => 'Unauthorized'], 403);
}
$workspace_id = isset($_POST['workspace_id']) ? (int) $_POST['workspace_id'] : 0;
if ($workspace_id <= 0) {
wp_send_json_error(['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) {
wp_send_json_error(['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();
$bonus_snapshot = aismart_bonus_get_snapshot_for_workspace((int) ($ctx['workspace_id'] ?? 0), true);
wp_send_json_success([
'message' => 'Workspace switched',
'workspace_id' => (int) $ctx['workspace_id'],
'workspace_label' => (string) $ctx['workspace_label'],
'credit_balance' => (float) $ctx['credit_balance'],
'workspaces' => $workspaces,
'bonus' => $bonus_snapshot,
]);
}
// 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');
wp_send_json_error(['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'];
$schedule_time_weekly_val = isset($_POST['schedule_time_weekly']) ? wp_unslash($_POST['schedule_time_weekly']) : '09:30';
$schedule_days_weekly_val = isset($_POST['schedule_days_weekly']) ? wp_unslash($_POST['schedule_days_weekly']) : ['1', '2', '3', '4', '5'];
$schedule_interval_hours_val = isset($_POST['schedule_interval_hours']) ? wp_unslash($_POST['schedule_interval_hours']) : 6;
$schedule_time_interval_start_val = isset($_POST['schedule_time_interval_start']) ? wp_unslash($_POST['schedule_time_interval_start']) : '06:00';
$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));
if (!in_array($schedule, ['1-per-day', '3-per-day', '5-per-week', 'every-n-hours'], true)) {
$schedule = '3-per-day';
}
$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)
);
$schedule_time_weekly = aismart_normalize_schedule_clock(wp_unslash((string) $schedule_time_weekly_val), $schedule_time_daily);
$schedule_days_weekly = aismart_parse_schedule_weekly_days(
is_array($schedule_days_weekly_val) ? wp_unslash($schedule_days_weekly_val) : wp_unslash((string) $schedule_days_weekly_val)
);
$schedule_interval_hours = aismart_normalize_schedule_interval_hours(wp_unslash((string) $schedule_interval_hours_val), 6);
$schedule_time_interval_start = aismart_normalize_schedule_clock(wp_unslash((string) $schedule_time_interval_start_val), '06:00');
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);
update_option('aismart_schedule_time_weekly', $schedule_time_weekly, false);
update_option('aismart_schedule_days_weekly', implode(',', $schedule_days_weekly), false);
update_option('aismart_schedule_interval_hours', (int) $schedule_interval_hours, false);
update_option('aismart_schedule_time_interval_start', $schedule_time_interval_start, false);
error_log("AISmart Saved: Blog=$auto_blog, Trans=$auto_translate, Sched=$schedule");
wp_send_json_success([
'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')) {
wp_send_json_error(['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])) {
wp_send_json_error(['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])) {
wp_send_json_error(['message' => 'Source plugin not resolved. Please choose source plugin.'], 422);
}
if ($source === $target) {
wp_send_json_error(['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 {
wp_send_json_error([
'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.');
wp_send_json_success([
'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');
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_translation_panel_title($title, $base_language = 'en') {
$raw = trim((string) $title);
if ($raw === '') {
return '';
}
$preferred_langs = [];
$base_language = strtolower(sanitize_key((string) $base_language));
if ($base_language !== '') {
$preferred_langs[] = $base_language;
}
$site_locale = sanitize_text_field((string) get_locale());
if ($site_locale !== '') {
$site_lang = strtolower(substr(str_replace('_', '-', $site_locale), 0, 2));
if ($site_lang !== '') {
$preferred_langs[] = $site_lang;
}
}
$preferred_langs[] = 'en';
$preferred_langs = array_values(array_unique(array_filter($preferred_langs)));
$resolved = $raw;
if (function_exists('aismart_qtranslate_extract_segment')) {
foreach ($preferred_langs as $lang_code) {
$segment = trim((string) aismart_qtranslate_extract_segment($raw, (string) $lang_code));
if ($segment !== '') {
$resolved = $segment;
break;
}
}
}
// Some legacy datasets store simplified markers like [en]title[de]... without colons.
if (preg_match_all('/\[([a-z]{2}(?:[-_][a-zA-Z]{2,5})?)\](.*?)(?=\[[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\]|\[\]|$)/su', $raw, $matches, PREG_SET_ORDER)) {
$segments = [];
foreach ($matches as $match) {
$lang = isset($match[1]) ? strtolower(sanitize_key((string) $match[1])) : '';
$text = isset($match[2]) ? trim(wp_strip_all_tags((string) $match[2])) : '';
if ($lang !== '' && $text !== '') {
$segments[$lang] = $text;
}
}
foreach ($preferred_langs as $lang_code) {
if (!empty($segments[$lang_code])) {
$resolved = (string) $segments[$lang_code];
break;
}
}
if ($resolved === $raw && !empty($segments)) {
$resolved = (string) reset($segments);
}
}
$resolved = preg_replace('/\[:[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\]/u', ' ', (string) $resolved);
$resolved = preg_replace('/\{:[a-z]{2}(?:[-_][a-zA-Z]{2,5})?\}/u', ' ', (string) $resolved);
$resolved = str_replace(['[:]', '{:}', '[]'], ' ', (string) $resolved);
$resolved = preg_replace('/\s+/u', ' ', (string) $resolved);
$resolved = sanitize_text_field((string) $resolved);
$resolved = aismart_normalize_post_title((string) $resolved);
return trim((string) $resolved);
}
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_should_keep_generated_source_post($post_id) {
$post_id = (int) $post_id;
if ($post_id <= 0) {
return false;
}
$source_post_id = (int) get_post_meta($post_id, '_aismart_source_post_id', true);
if ($source_post_id > 0) {
return false;
}
return !empty(get_post_meta($post_id, '_aismart_generated_post', true));
}
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'] ?? '');
$element_post_type = sanitize_key((string) get_post_type($post_id));
if (!in_array($element_post_type, ['post', 'page'], true)) {
$element_post_type = 'post';
}
$wpml_element_type = $element_post_type === 'page' ? 'post_page' : 'post_post';
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' => $wpml_element_type,
]);
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, $wpml_element_type));
if (!empty($source_code)) {
return true;
}
}
return false;
}
function aismart_get_translation_status() {
if (!current_user_can('manage_options')) {
wp_send_json_error(['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;
$post_type = isset($_POST['post_type']) ? sanitize_key((string) wp_unslash($_POST['post_type'])) : 'post';
if (!in_array($post_type, ['post', 'page'], true)) {
$post_type = 'post';
}
$is_page_request = $post_type === 'page';
$wpml_element_type = $post_type === 'page' ? 'post_page' : 'post_post';
$selected_provider = aismart_normalize_provider_key(aismart_resolve_selected_provider_key());
$detected = (array) aismart_onboarding_detect_multilang_provider();
$active_primary_provider = aismart_normalize_provider_key($detected['primary'] ?? '');
$active_provider_list = array_values(array_unique(array_filter(array_map('aismart_normalize_provider_key', (array) ($detected['detected'] ?? [])))));
if ($selected_provider === '' || $selected_provider === 'none') {
$selected_provider = $active_primary_provider;
} elseif (!empty($active_provider_list) && !in_array($selected_provider, $active_provider_list, true)) {
// If saved provider is no longer active, use active runtime provider for status checks.
$selected_provider = $active_primary_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]));
$posts = get_posts([
'post_type' => $post_type,
'post_status' => ['publish', 'draft'],
'posts_per_page' => $limit,
's' => $search,
'orderby' => 'date',
'order' => 'DESC',
'lang' => '',
]);
if (!$posts) {
wp_send_json_success(['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_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) : '';
$post_lang = function_exists('pll_get_post_language') ? (string) pll_get_post_language($post_id, 'slug') : '';
if ($post_lang === '') {
$post_lang = $base_language;
}
$post_missing_languages = array_values(array_diff($active_codes, [$post_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) aismart_translation_panel_title((string) $display_title, (string) $base_language),
'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,
'fixdemo_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_fixdemo_locked', true) === 1 || in_array((string) get_post_meta($post_id, '_aismart_page_refine_mode', true), ['fixdemo', 'rewrite'], true)) ? 1 : 0)) : 0,
'rewrite_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_rewrite_locked', true) === 1 || (string) get_post_meta($post_id, '_aismart_page_refine_mode', true) === 'rewrite') ? 1 : 0)) : 0,
'quality_issue' => $quality_issue,
'quality_label' => $quality_label,
'progress_percent' => $progress_percent,
];
}
wp_send_json_success([
'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]));
$posts = get_posts([
'post_type' => $post_type,
'post_status' => ['publish', 'draft'],
'posts_per_page' => $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) {
wp_send_json_success(['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_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);
$source_is_publish = ((string) $post->post_status === 'publish');
$missing = [];
if (!$queued) {
foreach ($missing_languages as $lang_code) {
$missing[] = $language_map[$lang_code] ?? $lang_code;
}
}
$is_complete = count($missing) === 0;
$row_status = $source_is_publish
? ($is_complete ? 'complete' : 'missing')
: 'draft';
if ($filter === 'complete' && !$is_complete) {
continue;
}
if ($filter === 'missing' && $is_complete) {
continue;
}
$output[] = [
'id' => $post_id,
'title' => (string) aismart_translation_panel_title((string) $post->post_title, (string) $base_language),
'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,
'fixdemo_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_fixdemo_locked', true) === 1 || in_array((string) get_post_meta($post_id, '_aismart_page_refine_mode', true), ['fixdemo', 'rewrite'], true)) ? 1 : 0)) : 0,
'rewrite_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_rewrite_locked', true) === 1 || (string) get_post_meta($post_id, '_aismart_page_refine_mode', true) === 'rewrite') ? 1 : 0)) : 0,
'quality_issue' => '',
'quality_label' => '',
'progress_percent' => null,
];
}
wp_send_json_success([
'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_type,
'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) {
wp_send_json_success(['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;
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 ($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' => (string) aismart_translation_panel_title((string) $post->post_title, (string) $base_language),
'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,
];
}
wp_send_json_success([
'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_type,
'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) {
wp_send_json_success(['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_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 ($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) aismart_translation_panel_title((string) $display_title, (string) $base_language),
'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,
'fixdemo_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_fixdemo_locked', true) === 1 || in_array((string) get_post_meta($post_id, '_aismart_page_refine_mode', true), ['fixdemo', 'rewrite'], true)) ? 1 : 0)) : 0,
'rewrite_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_rewrite_locked', true) === 1 || (string) get_post_meta($post_id, '_aismart_page_refine_mode', true) === 'rewrite') ? 1 : 0)) : 0,
'quality_issue' => '',
'quality_label' => '',
'progress_percent' => null,
];
if (count($output) >= $limit) {
break;
}
}
wp_send_json_success([
'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_type,
'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) {
wp_send_json_success(['posts' => [], 'languages' => $language_map, 'base_language' => $base_language]);
}
$output = [];
foreach ($posts as $post) {
$post_id = (int) $post->ID;
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_type,
'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 ($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) aismart_translation_panel_title((string) $post->post_title, (string) $base_language),
'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,
'fixdemo_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_fixdemo_locked', true) === 1 || in_array((string) get_post_meta($post_id, '_aismart_page_refine_mode', true), ['fixdemo', 'rewrite'], true)) ? 1 : 0)) : 0,
'rewrite_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_rewrite_locked', true) === 1 || (string) get_post_meta($post_id, '_aismart_page_refine_mode', true) === 'rewrite') ? 1 : 0)) : 0,
'quality_issue' => '',
'quality_label' => '',
'progress_percent' => null,
];
if (count($output) >= $limit) {
break;
}
}
wp_send_json_success([
'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)),
])));
$posts = get_posts([
'post_type' => $post_type,
'post_status' => ['publish', 'draft'],
'posts_per_page' => $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) {
wp_send_json_success(['posts' => [], 'languages' => $language_map, 'base_language' => $base_slug]);
}
$output = [];
foreach ($posts as $post) {
$post_id = (int) $post->ID;
$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,
'fixdemo_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_fixdemo_locked', true) === 1 || in_array((string) get_post_meta($post_id, '_aismart_page_refine_mode', true), ['fixdemo', 'rewrite'], true)) ? 1 : 0)) : 0,
'rewrite_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_rewrite_locked', true) === 1 || (string) get_post_meta($post_id, '_aismart_page_refine_mode', true) === 'rewrite') ? 1 : 0)) : 0,
'quality_issue' => '',
'quality_label' => '',
'progress_percent' => null,
];
if (count($output) >= $limit) {
break;
}
}
wp_send_json_success([
'posts' => $output,
'languages' => $language_map,
'base_language' => $base_slug,
]);
}
if (!$provider_matches('wpml')) {
wp_send_json_success(['posts' => [], 'languages' => []]);
}
$translations_table = $wpdb->prefix . 'icl_translations';
$languages_table = $wpdb->prefix . 'icl_languages';
$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));
if (!$translations_exists || !$languages_exists) {
wp_send_json_success(['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, t.language_code as root_language_code
FROM {$posts_table} p
LEFT JOIN {$translations_table} t
ON t.element_id = p.ID
AND t.element_type = %s
WHERE p.post_status = 'publish'
AND p.post_type = %s
AND p.post_title LIKE %s
AND (t.source_language_code IS NULL OR t.source_language_code = '')
ORDER BY p.post_date DESC
LIMIT %d",
$wpml_element_type,
$post_type,
$search_like,
$limit
));
} else {
$posts = $wpdb->get_results($wpdb->prepare(
"SELECT p.ID, p.post_title, p.post_date, p.post_content, t.trid, t.language_code as root_language_code
FROM {$posts_table} p
LEFT JOIN {$translations_table} t
ON t.element_id = p.ID
AND t.element_type = %s
WHERE p.post_status = 'publish'
AND p.post_type = %s
AND (t.source_language_code IS NULL OR t.source_language_code = '')
ORDER BY p.post_date DESC
LIMIT %d",
$wpml_element_type,
$post_type,
$limit
));
}
if (!$posts) {
wp_send_json_success(['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'));
$translation_rows = $wpdb->get_results($wpdb->prepare(
"SELECT t.trid, t.language_code, t.Translated, p.post_status, p.post_content
FROM {$translations_table} t
LEFT JOIN {$posts_table} p ON p.ID = t.element_id
WHERE t.element_type = %s AND t.trid IN ({$placeholders})",
array_merge([$wpml_element_type], $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,
];
}
}
$output = [];
foreach ($posts as $post) {
$post_id = (int) $post->ID;
$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;
$root_lang = !empty($post->root_language_code) ? sanitize_key((string)$post->root_language_code) : $base_language;
$post_missing_languages = array_values(array_diff($active_codes, [$root_lang]));
foreach ($post_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 ($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 ($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',
'post_status' => 'publish',
'can_translate' => 1,
'fixdemo_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_fixdemo_locked', true) === 1 || in_array((string) get_post_meta($post_id, '_aismart_page_refine_mode', true), ['fixdemo', 'rewrite'], true)) ? 1 : 0)) : 0,
'rewrite_locked' => $is_page_request ? ((((int) get_post_meta($post_id, '_aismart_page_rewrite_locked', true) === 1 || (string) get_post_meta($post_id, '_aismart_page_refine_mode', true) === 'rewrite') ? 1 : 0)) : 0,
'quality_issue' => $quality_issue,
'quality_label' => $quality_label,
'progress_percent' => $progress_percent,
];
}
wp_send_json_success([
'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')) {
wp_send_json_error(['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) {
wp_send_json_error(['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);
wp_send_json_success([
'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;
}
wp_send_json_error(['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');
wp_send_json_success([
'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;
}
$route = aismart_detect_translation_command();
$queue_engine = isset($route['engine']) ? sanitize_key((string) $route['engine']) : 'unknown';
$queued = false;
if (in_array($queue_engine, ['bogo', 'wpml', 'translatepress', 'polylang'], true)) {
$args = [(int) $post->ID, $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);
$queued = true;
aismart_queue_log('Auto-translate on publish queued in background post_id=' . (int) $post->ID . ' engine=' . $queue_engine);
} else {
$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;
}
// In CLI contexts or environments that cannot detach the response,
// rely on scheduled cron jobs and avoid inline heavy work at shutdown.
if ((defined('WP_CLI') && WP_CLI) || PHP_SAPI === 'cli') {
aismart_queue_log('Deferred translation worker skip inline run (CLI context); relying on scheduled cron jobs.');
return;
}
if (!function_exists('fastcgi_finish_request')) {
aismart_queue_log('Deferred translation worker skip inline run (no fastcgi_finish_request); relying on scheduled cron 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 === '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;
}
if ($engine === 'translatepress') {
// Reuse the unified queue path so TranslatePress keeps native + fallback behavior.
$ok = aismart_queue_translation_for_post($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 gemini 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_translation_supported_post_type($post_type) {
$post_type = sanitize_key((string) $post_type);
return in_array($post_type, ['post', 'page'], true);
}
function aismart_translation_sync_standard_taxonomies($source_post_id, $target_post_id, $post_type) {
$source_post_id = (int) $source_post_id;
$target_post_id = (int) $target_post_id;
$post_type = sanitize_key((string) $post_type);
if ($source_post_id <= 0 || $target_post_id <= 0 || $post_type !== 'post') {
return;
}
$category_ids = wp_get_post_categories($source_post_id);
if (!empty($category_ids)) {
wp_set_post_categories($target_post_id, $category_ids, false);
}
$tags = wp_get_post_tags($source_post_id, ['fields' => 'names']);
if (!empty($tags)) {
wp_set_post_tags($target_post_id, $tags, false);
}
}
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 || !aismart_translation_supported_post_type($post->post_type) || $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 || !aismart_translation_supported_post_type($post->post_type) || $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 || !aismart_translation_supported_post_type($post->post_type) || $post->post_status !== 'publish') {
aismart_queue_log('Native Bogo skip: post not eligible post_id=' . $post_id);
return false;
}
$content_type = sanitize_key((string) $post->post_type);
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' => $content_type,
'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);
aismart_translation_sync_standard_taxonomies($post_id, $target_id, $content_type);
$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 || !aismart_translation_supported_post_type($source_post->post_type) || $source_post->post_status !== 'publish') {
aismart_queue_log('Native MultilingualPress skip: post not eligible post_id=' . $post_id);
return false;
}
$content_type = sanitize_key((string) $source_post->post_type);
$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' => $content_type,
'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' => $content_type,
'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 || !aismart_translation_supported_post_type($input_post->post_type) || $input_post->post_status !== 'publish') {
aismart_queue_log('Native WPML skip: post not eligible post_id=' . $post_id);
return false;
}
$content_type = sanitize_key((string) $input_post->post_type);
$wpml_element_type = $content_type === 'page' ? 'post_page' : 'post_post';
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, $wpml_element_type);
if ($trid) {
$translations = apply_filters('wpml_get_element_translations', null, $trid, $wpml_element_type);
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 || !aismart_translation_supported_post_type($source_post->post_type)) {
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' => $wpml_element_type]));
}
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, $content_type, 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);
aismart_translation_sync_standard_taxonomies($source_post_id, $child_id, $content_type);
$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 === '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 gemini 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 gemini 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 gemini 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 gemini 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 === '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 || !aismart_translation_supported_post_type($post->post_type)) {
return;
}
$content_type = sanitize_key((string) $post->post_type);
$wpml_element_type = $content_type === 'page' ? 'post_page' : 'post_post';
$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' => $content_type,
'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' => $content_type,
'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);
}
aismart_sync_theme_meta_to_post((int) $post_id, (int) $new_post_id);
aismart_translation_sync_standard_taxonomies((int) $post_id, (int) $new_post_id, $content_type);
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, $content_type, 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',
]);
aismart_sync_theme_meta_to_post((int) $post_id, (int) $child_id);
// Keep taxonomy baseline aligned before translation.
aismart_translation_sync_standard_taxonomies($post_id, $child_id, $content_type);
}
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' => 'gemini',
'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 || !aismart_translation_supported_post_type($post->post_type) || $post->post_status !== 'publish') {
aismart_queue_log('Native Falang fallback skip: post not eligible post_id=' . $post_id);
return false;
}
$content_type = sanitize_key((string) $post->post_type);
$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' => $content_type,
'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' => $content_type,
'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' => $content_type,
'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);
aismart_translation_sync_standard_taxonomies($post_id, $legacy_target_id, $content_type);
}
$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 || !aismart_translation_supported_post_type($post->post_type) || $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 || !aismart_translation_supported_post_type($post->post_type) || $post->post_status !== 'publish') {
aismart_queue_log('Native Polylang fallback skip: post not eligible post_id=' . $post_id);
return false;
}
$content_type = sanitize_key((string) $post->post_type);
$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' => $content_type,
'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' => $content_type,
'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,
]);
if ($source_thumbnail_id > 0) {
set_post_thumbnail((int) $target_id, $source_thumbnail_id);
}
aismart_sync_theme_meta_to_post($post_id, (int) $target_id);
aismart_translation_sync_standard_taxonomies($post_id, (int) $target_id, $content_type);
$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;
}
}
}
$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 '';
}
$has_complex_markup =
preg_match('/\[(?:\/)?[a-zA-Z0-9_:-]+(?:\s[^\]]*)?\]/u', $content)
|| strpos($content, '/u', $protect_segment, $protected);
$protected = preg_replace_callback('/\[(?:\/)?[a-zA-Z0-9_:-]+(?:\s[^\]]*)?\]/u', $protect_segment, $protected);
$translated_guarded = aismart_translate_text_google($protected, $target_lang, $source_lang, true);
if (!is_string($translated_guarded) || trim($translated_guarded) === '') {
$translated_guarded = $protected;
}
foreach ($sensitive_segments as $token => $segment) {
if (strpos($translated_guarded, $token) === false) {
// If a protected token is lost, keep original content to avoid malformed builder markup.
return $content;
}
$translated_guarded = str_replace($token, $segment, $translated_guarded);
}
$translated_guarded = aismart_cleanup_translated_post_content($translated_guarded);
return $translated_guarded !== '' ? $translated_guarded : $content;
}
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('/