$info) { $normalized = strtolower(trim((string) $slug)); if (self::provider_is_eu($normalized)) { $filtered[$normalized] = $info; } } return $filtered; } private static function normalize_provider_region(?string $region): string { $normalized = strtolower(trim((string) $region)); return $normalized === 'eu' ? 'eu' : 'global'; } private static function enforce_provider_policy(string $provider): string { $normalized = strtolower(trim($provider)); if (! self::eu_only_providers_enabled()) { return $normalized; } if (self::provider_is_eu($normalized)) { return $normalized; } $meta = self::get_translation_providers_metadata(); $fallback = self::get_default_eu_provider(); if (!isset($meta[$fallback])) { foreach ($meta as $slug => $_info) { if (self::provider_is_eu($slug)) { $fallback = $slug; break; } } } if (isset($meta[$fallback]) && self::provider_is_eu($fallback)) { update_option('aigude_translation_provider', $fallback); return $fallback; } return $normalized; } /** * Describe the site's locale in a way the Credits API understands. */ public static function describe_site_language(?string $provider = null): array { return AIGUDE_Translation_Service::describe_site_language($provider); } /** * Return the built-in default prompt template (non-erasable). */ public static function get_builtin_prompt_template(): array { $title = __('Default Prompt', 'aigude-tools'); // Prefer AiGude's model-optimized native prompt for the site language; // fall back to English, then to the bundled translated string. $native_langs = AIGUDE_Native_Language_Service::get_languages(); $site_base = AIGUDE_Native_Language_Service::site_base_code(); $prompt_lang = AIGUDE_Native_Language_Service::default_prompt_lang(); $prompt_text = ''; foreach ([$site_base, 'en'] as $candidate) { if ($candidate !== '' && !empty($native_langs[$candidate]['default_prompt'])) { $prompt_text = (string) $native_langs[$candidate]['default_prompt']; $prompt_lang = $candidate; break; } } if ($prompt_text === '') { // The bundled string is localized through the active .mo, so its language is the plugin locale. $prompt_text = __('Describe the essential content of the picture briefly and concisely. Limit the text to a very short sentence', 'aigude-tools'); } $deepl_langs = self::get_translation_languages('deepl'); $google_langs = self::get_translation_languages('google'); $site_lang = self::describe_site_language('deepl'); $site_code = strtoupper($site_lang['code'] ?? ''); $provider = 'deepl'; $lang = 'EN'; if ($site_code !== '') { if (isset($deepl_langs[$site_code])) { $provider = 'deepl'; $lang = $site_code; } elseif (isset($google_langs[$site_code])) { $provider = 'google'; $lang = $site_code; } } $provider_langs = $provider === 'google' ? $google_langs : $deepl_langs; if ($lang === '' || !isset($provider_langs[$lang])) { if (isset($deepl_langs['EN'])) { $provider = 'deepl'; $lang = 'EN'; } elseif (!empty($deepl_langs)) { $provider = 'deepl'; $lang = (string) array_key_first($deepl_langs); } elseif (!empty($google_langs)) { $provider = 'google'; $lang = (string) array_key_first($google_langs); } } $recents = []; if ($provider && $lang) { $recents[$provider] = [$lang]; } return [ 'id' => self::BUILTIN_PROMPT_ID, 'title' => $title, 'prompt' => $prompt_text, 'src_lang' => 'auto', 'prompt_lang' => $prompt_lang, 'target_provider' => $provider, 'target_lang' => $lang, 'eu_only_providers' => '0', 'recent_target_langs' => $recents, 'builtin' => '1', ]; } /** * Resolve the default prompt template without any browser involvement. * * Read-only on purpose: the self-healing bootstrap in list-view.php and * grid-view.php writes options, which an AJAX request should not do. */ public static function get_default_prompt_template(): array { $templates = get_option('aigude_prompt_templates', []); if (!is_array($templates)) { $templates = []; } $default_id = (string) get_option('aigude_prompt_default_id', ''); if ($default_id !== '') { foreach ($templates as $tpl) { if (is_array($tpl) && ($tpl['id'] ?? '') === $default_id) { return $tpl; } } } return self::get_builtin_prompt_template(); } /** * Provide a human-friendly description of the user's preferred translation language. */ public static function describe_language_preference(?string $provider = null): array { return AIGUDE_Translation_Service::describe_language_preference($provider); } /** * Normalize arbitrary language input into the provider's target language code. */ public static function resolve_target_lang_code(string $code, ?string $provider = null): string { return AIGUDE_Translation_Service::resolve_target_lang_code($code, $provider); } /** * Describe a provider/language pair for display purposes. * * @return array{provider:string,provider_label:string,code:string,label:string,display:string,available:bool} */ public static function describe_target_language_choice(?string $provider, ?string $code): array { $provider = is_string($provider) ? AIGUDE_Translation_Service::normalize_translation_provider($provider) : ''; $meta = self::get_translation_providers_metadata(); if ($provider === '' || !isset($meta[$provider])) { return [ 'provider' => '', 'provider_label' => '', 'code' => '', 'label' => '', 'display' => '', 'available' => false, ]; } $languages = self::get_translation_languages($provider); $normalized = ''; if (is_string($code) && $code !== '') { if ($provider === 'deepl') { $normalized = AIGUDE_Translation_Service::normalize_deepl_lang_code($code, 'target'); } else { $normalized = AIGUDE_Translation_Service::normalize_provider_lang_code_generic($code, $provider, 'target'); } } if ($normalized === '') { $normalized = is_string($code) ? strtoupper(trim($code)) : ''; } $label = ''; $available = false; if ($normalized !== '') { if (isset($languages[$normalized])) { $label = $languages[$normalized]; $available = true; } else { $label = strtoupper($normalized); } } $provider_label = self::get_translation_provider_label($provider); $display = ''; if ($label !== '' && $provider_label !== '') { $display = sprintf('%s — %s', $label, $provider_label); } else { $display = $label; } return [ 'provider' => $provider, 'provider_label' => $provider_label, 'code' => $normalized, 'label' => $label, 'display' => $display, 'available' => $available, ]; } /** Provide the cached API key so the translation service can talk to AiGude. */ public static function get_translation_api_key(): string { [$apiKey] = self::media_controller()->get_active_server_credentials(); return $apiKey; } /** * Proxy to the translation service for DeepL-specific normalization. */ private static function normalize_deepl_lang_code(string $code, string $context = 'target'): string { return AIGUDE_Translation_Service::normalize_deepl_lang_code($code, $context); } /** * Map a WordPress locale string into the format DeepL expects. */ private static function map_wp_locale_to_deepl(string $locale): string { return AIGUDE_Translation_Service::map_wp_locale_to_deepl($locale); } /** * Map a WordPress locale to whatever representation a given provider uses. */ private static function map_wp_locale_to_provider(string $locale, ?string $provider = null): string { return AIGUDE_Translation_Service::map_wp_locale_to_provider($locale, $provider); } /** * Normalize a provider-specific language code when we do not know the provider in advance. */ private static function normalize_provider_lang_code_generic(string $code, string $provider, string $context = 'target'): string { return AIGUDE_Translation_Service::normalize_provider_lang_code_generic($code, $provider, $context); } /** * Resolve the Img2Desc endpoint, honoring overrides via constants/env/filters. */ public static function get_img2desc_url(): string { if (defined('AIGUDE_IMG2DESC_URL') && is_string(AIGUDE_IMG2DESC_URL) && AIGUDE_IMG2DESC_URL !== '') { return trim(AIGUDE_IMG2DESC_URL); } $env = getenv('AIGUDE_IMG2DESC_URL'); if (is_string($env) && trim($env) !== '') { return trim($env); } if (function_exists('apply_filters')) { $filtered = apply_filters('aigude_img2desc_url', self::API_URL_IMG2DESC); if (is_string($filtered) && trim($filtered) !== '') { return trim($filtered); } } return self::API_URL_IMG2DESC; } /** * Resolve the Img2Desc batch endpoint, honoring overrides via constants/env/filters. */ public static function get_img2desc_batch_url(): string { if (defined('AIGUDE_IMG2DESC_BATCH_URL') && is_string(AIGUDE_IMG2DESC_BATCH_URL) && AIGUDE_IMG2DESC_BATCH_URL !== '') { return trim(AIGUDE_IMG2DESC_BATCH_URL); } $env = getenv('AIGUDE_IMG2DESC_BATCH_URL'); if (is_string($env) && trim($env) !== '') { return trim($env); } if (function_exists('apply_filters')) { $filtered = apply_filters('aigude_img2desc_batch_url', self::API_URL_IMG2DESC_BATCH); if (is_string($filtered) && trim($filtered) !== '') { return trim($filtered); } } return self::API_URL_IMG2DESC_BATCH; } /** * Resolve the credits endpoint using the same override cascade as the Img2Desc URL. */ public static function get_credits_url(): string { if (defined('AIGUDE_REMAINING_CREDITS_URL') && is_string(AIGUDE_REMAINING_CREDITS_URL) && AIGUDE_REMAINING_CREDITS_URL !== '') { return trim(AIGUDE_REMAINING_CREDITS_URL); } $env = getenv('AIGUDE_REMAINING_CREDITS_URL'); if (is_string($env) && trim($env) !== '') { return trim($env); } if (function_exists('apply_filters')) { $filtered = apply_filters('aigude_remaining_credits_url', self::API_URL_CREDITS); if (is_string($filtered) && trim($filtered) !== '') { return trim($filtered); } } return self::API_URL_CREDITS; } /** * Resolve the native-languages endpoint, honoring overrides via constants/env/filters. */ public static function get_native_languages_url(): string { if (defined('AIGUDE_NATIVE_LANGUAGES_URL') && is_string(AIGUDE_NATIVE_LANGUAGES_URL) && AIGUDE_NATIVE_LANGUAGES_URL !== '') { return trim(AIGUDE_NATIVE_LANGUAGES_URL); } $env = getenv('AIGUDE_NATIVE_LANGUAGES_URL'); if (is_string($env) && trim($env) !== '') { return trim($env); } if (function_exists('apply_filters')) { $filtered = apply_filters('aigude_native_languages_url', self::API_URL_NATIVE_LANGUAGES); if (is_string($filtered) && trim($filtered) !== '') { return trim($filtered); } } return self::API_URL_NATIVE_LANGUAGES; } /*** Bootstrap ***/ /** * Wire up all plugin modules (UI, media, query integrations). */ public static function init(): void { add_action('init', [__CLASS__, 'load_textdomain']); self::admin_ui()->register(); self::media_controller()->register(); self::media_query()->register(); // The plugin's only front-end hook; everything above is admin, AJAX, or query-var gated. self::content_alt_filter()->register(); self::content_rewriter()->register(); self::attachment_button()->register(); } /** * Load the plugin's bundled translations. */ public static function load_textdomain(): void { $domain = 'aigude-tools'; $locale = function_exists('determine_locale') ? determine_locale() : get_locale(); // Load GlotPress/WordPress language packs first (wp-content/languages/plugins). if (defined('WP_LANG_DIR')) { $global_mofile = trailingslashit(WP_LANG_DIR) . 'plugins/' . $domain . '-' . $locale . '.mo'; if (file_exists($global_mofile)) { load_textdomain($domain, $global_mofile); } } // Always allow the bundled translations to override the global ones. $mofile = plugin_dir_path(__FILE__) . 'languages/' . $domain . '-' . $locale . '.mo'; if (file_exists($mofile)) { load_textdomain($domain, $mofile); } } /** * Get last used languages (max 5) for a given type: 'target' | 'prompt' | 'placeholder'. * Optionally scoped to a provider (recent per provider). */ public static function get_recent_langs(string $type, ?string $provider = null): array { return self::media_query()->get_recent_langs($type, $provider); } /** * Push one language code into the recent list for a type (dedup, keep 5). * Optionally scope to a provider. */ public static function push_recent_lang(string $type, string $code, ?string $provider = null): void { self::media_query()->push_recent_lang($type, $code, $provider); } /** * Lazily instantiate and return the admin UI helper. */ private static function admin_ui(): AIGUDE_Admin_UI { if (!self::$admin_ui) { self::$admin_ui = new AIGUDE_Admin_UI(); } return self::$admin_ui; } /** * Lazily instantiate and return the media controller helper. */ private static function media_controller(): AIGUDE_Media_Controller { if (!self::$media_controller) { self::$media_controller = new AIGUDE_Media_Controller(); } return self::$media_controller; } /** * Lazily instantiate and return the media/query helper. */ private static function media_query(): AIGUDE_Media_Query { if (!self::$media_query) { self::$media_query = new AIGUDE_Media_Query(); } return self::$media_query; } /** * Lazily instantiate and return the front-end content alt filter. */ private static function content_alt_filter(): AIGUDE_Content_Alt_Filter { if (!self::$content_alt_filter) { self::$content_alt_filter = new AIGUDE_Content_Alt_Filter(); } return self::$content_alt_filter; } /** * Lazily instantiate and return the stored-content rewriter. */ private static function content_rewriter(): AIGUDE_Content_Rewriter { if (!self::$content_rewriter) { self::$content_rewriter = new AIGUDE_Content_Rewriter(); } return self::$content_rewriter; } /** * Lazily instantiate and return the Attachment details panel button. */ private static function attachment_button(): AIGUDE_Attachment_Button { if (!self::$attachment_button) { self::$attachment_button = new AIGUDE_Attachment_Button(); } return self::$attachment_button; } /** * Determine whether debug logging features should be enabled (WP_DEBUG, SCRIPT_DEBUG, or env flag). */ public static function debug_enabled(): bool { // Standard WordPress flags $wp_debug = defined('WP_DEBUG') && WP_DEBUG; $script_debug = defined('SCRIPT_DEBUG') && SCRIPT_DEBUG; // Environment variable (e.g., Docker compose WORDPRESS_DEBUG=1) $env = getenv('WORDPRESS_DEBUG'); $env_debug = is_string($env) ? filter_var($env, FILTER_VALIDATE_BOOLEAN) : false; return ($wp_debug || $script_debug || $env_debug) === true; } /** * Write one line to the WordPress debug log, prefixed and gated. * * Uses error_log(), which is where WP_DEBUG_LOG routes wp-content/debug.log. * The previous call sites used wp_debug_log(), which does not exist in * WordPress, so all of them were silent no-ops. */ public static function log(string $message): void { if (!self::debug_enabled()) { return; } error_log('[AiGude Tools] ' . $message); } } // Kick off AIGUDE_Tools_Plugin::init();