'array', 'sanitize_callback' => array($this, 'sanitize_api_settings'), 'default' => array(), ) ); } /** * Clean and sanitize incoming API engine settings array. */ public function sanitize_api_settings($input) { $sanitized = array(); if (isset($input['active_provider'])) { $sanitized['active_provider'] = sanitize_key($input['active_provider']); } if (isset($input['microsoft_region'])) { $sanitized['microsoft_region'] = sanitize_text_field($input['microsoft_region']); } $prompt_keys = array('openai_custom_prompt', 'anthropic_custom_prompt', 'grok_custom_prompt', 'gemini_custom_prompt'); foreach ($prompt_keys as $key) { if (isset($input[$key])) { $sanitized[$key] = sanitize_textarea_field($input[$key]); } } $key_fields = array('deepl_key', 'google_key', 'lecto_key', 'microsoft_key', 'openai_key', 'anthropic_key', 'grok_key', 'gemini_key', 'openai_model', 'anthropic_model', 'grok_model', 'gemini_model'); foreach ($key_fields as $field) { if (isset($input[$field])) { $sanitized[$field] = sanitize_text_field(trim($input[$field])); } } return $sanitized; } /** * Registers structural parent node space alongside synchronized submenus. * Re-ordered based on user priority and simplified for intermediate clarity. */ public function register_admin_workspace() { $capability = 'manage_options'; // Main Parent Menu Node add_menu_page( esc_html__('AF Translate – AI Automated Translation Studio for WordPress', 'af-translate'), esc_html__('AF Translate', 'af-translate'), $capability, 'af-translate', array($this, 'render_workspace_router'), 'dashicons-translation', 65 ); // 1. Dashboard (The central hub, matches parent slug to avoid duplicates) add_submenu_page( 'af-translate', esc_html__('AF Translate Dashboard', 'af-translate'), esc_html__('Dashboard', 'af-translate'), $capability, 'af-translate', array($this, 'render_workspace_router') ); // 2. Translate Plugins (Highest priority search intent) add_submenu_page( 'af-translate', esc_html__('Translate Installed Plugins', 'af-translate'), esc_html__('Translate Plugins', 'af-translate'), $capability, 'af-translate-plugins', array($this, 'render_workspace_router') ); // 3. Translate Themes (Second highest priority search intent) add_submenu_page( 'af-translate', esc_html__('Translate Installed Themes', 'af-translate'), esc_html__('Translate Themes', 'af-translate'), $capability, 'af-translate-themes', array($this, 'render_workspace_router') ); // 4. Translate WordPress Core (Lower frequency utility task) add_submenu_page( 'af-translate', esc_html__('Translate WordPress Core System', 'af-translate'), esc_html__('WordPress Core', 'af-translate'), $capability, 'af-translate-core', array($this, 'render_workspace_router') ); // 5. Site Languages (Informational configuration screen) add_submenu_page( 'af-translate', esc_html__('Manage Localized Site Languages', 'af-translate'), esc_html__('Site Languages', 'af-translate'), $capability, 'af-translate-languages', array($this, 'render_workspace_router') ); // 6. AI Settings (Setup/Configuration - standard placement at the bottom) add_submenu_page( 'af-translate', esc_html__('AI Engine Settings & API Keys', 'af-translate'), esc_html__('AI Settings', 'af-translate'), $capability, 'af-translate-ai-connecter', array($this, 'render_workspace_router') ); } /** * Register and Enqueue Dashboard Assets * Fully synchronized with chunked UI pagination constraints. * * @param string $hook The current admin page hook suffix. */ public function af_translate_enqueue_bulk_engine_assets($hook) { if (false === strpos($hook, 'af-translate')) { return; } $settings = get_option('af_translate_api_settings', array()); $default_provider = isset($settings['active_provider']) ? sanitize_key($settings['active_provider']) : 'none'; $engine_manifest = array( 'deepl' => __('DeepL Pro / Free Translator', 'af-translate'), 'google' => __('Google Translate Cloud v2 API', 'af-translate'), 'lecto' => __('Lecto AI Translation Engine', 'af-translate'), 'microsoft' => __('Microsoft Cognitive Translator v3', 'af-translate'), 'openai' => __('OpenAI Core Engines (GPT-4o / Mini)', 'af-translate'), 'anthropic' => __('Anthropic LLM Array (Claude 3.5 Sonnet)', 'af-translate'), 'grok' => __('xAI Enterprise Core (Grok 4)', 'af-translate'), 'gemini' => __('Google Gemini Flash / Pro Multimodal', 'af-translate'), ); $available_providers = array(); foreach ($engine_manifest as $slug => $label) { $key_field = ('microsoft' === $slug) ? 'microsoft_key' : $slug . '_key'; if (! empty($settings[$key_field])) { $available_providers[] = array( 'slug' => $slug, 'label' => $label ); } } // Prepare the baseline configuration array for Free version $bulk_settings = array( 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('af_translate_secure_action'), 'available_providers' => $available_providers, 'default_provider' => $default_provider, 'settings_url' => admin_url('admin.php?page=af-translate-ai-connecter'), 'strings' => array( 'cancel' => __('Cancel', 'af-translate'), 'run_batch' => __('Execute Run', 'af-translate'), 'initiating' => __('Analyzing translation matrix blocks...', 'af-translate'), 'processing' => __('Translating chunk packages via AI engine...', 'af-translate'), 'select_engine' => __('Select AI Provider', 'af-translate'), 'preview_loaded' => __('Bulk Translation Preview Loaded!', 'af-translate'), 'configure_title' => __('Configure AI Bulk Engine Run', 'af-translate'), 'filter_constraints' => __('Target Filter Constraints', 'af-translate'), 'translate_empty' => __('Translate Empty Tracks Only (Recommended)', 'af-translate'), 'translate_all' => __('Translate All Strings (Overwrite Existing)', 'af-translate'), 'no_keys_title' => __('AI API Keys Required', 'af-translate'), 'no_keys_desc' => __('No active AI translation engines were found. Please configure your API keys in the setup dashboard to unlock bulk translations.', 'af-translate'), 'go_to_setup' => __('Go to AI Setup', 'af-translate'), 'configure_btn' => __('Configure API Keys', 'af-translate'), 'consulting_ai' => __('Consulting AI models...', 'af-translate'), ) ); /** * EXTENSIBILITY BRIDGE HOOK * Allows the Pro version to intercept the configurations and inject calculation trees or toggle enable_preflight. */ $bulk_settings = apply_filters('af_translate_bulk_settings', $bulk_settings); wp_enqueue_script( 'af-translate-bulk-engine', AF_TRANSLATE_URL . 'assets/js/ai-translate-bulk.js', array('jquery'), AF_TRANSLATE_VERSION, true ); wp_localize_script('af-translate-bulk-engine', 'afTranslateBulkSettings', $bulk_settings); wp_enqueue_script( 'af-translate-workbench-engine', AF_TRANSLATE_URL . 'assets/js/ai-translate-workbench.js', array('jquery', 'af-translate-bulk-engine'), AF_TRANSLATE_VERSION, true ); } /** * Directs administrative action requests and maps layout states. */ public function render_workspace_router() { if (! current_user_can('manage_options')) { wp_die(esc_html__('Security Exception: You do not have sufficient permissions to access this page.', 'af-translate')); } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $current_page = isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : 'af-translate'; $tab = 'overview'; if ('af-translate-plugins' === $current_page) { $tab = 'plugins'; } elseif ('af-translate-themes' === $current_page) { $tab = 'themes'; } elseif ('af-translate-core' === $current_page) { $tab = 'wordpress'; } elseif ('af-translate-languages' === $current_page) { $tab = 'languages'; } elseif ('af-translate-ai-connecter' === $current_page) { $tab = 'settings'; } else { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $tab = isset($_GET['tab']) ? sanitize_key(wp_unslash($_GET['tab'])) : 'overview'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended $view = isset($_GET['view']) ? sanitize_key(wp_unslash($_GET['view'])) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended $type = isset($_GET['type']) ? sanitize_key(wp_unslash($_GET['type'])) : 'theme'; echo '
'; if (! empty($view)) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $action = isset($_GET['action']) ? sanitize_key(wp_unslash($_GET['action'])) : 'dashboard'; switch ($action) { case 'edit': // phpcs:ignore WordPress.Security.NonceVerification.Recommended $lang_target = isset($_GET['lang']) ? sanitize_key(wp_unslash($_GET['lang'])) : ''; AF_Translate_Editor_View::render($view, $type, $lang_target); break; case 'setup': AF_Translate_Project_Config::render_setup_panel($view, $type); break; case 'new-lang': AF_Translate_Project_Config::render_new_language_screen($view, $type); break; case 'dashboard': default: AF_Translate_Project_Config::render_project_dashboard($view, $type); break; } } else { $this->render_global_header(); $this->render_nav_tabs($tab); $view_map = array( 'overview' => 'overview-matrix.php', 'languages' => 'languages-matrix.php', 'wordpress' => 'wordpress-core-matrix.php', 'settings' => 'ai-setting-panel.php', ); if (isset($view_map[$tab])) { $resolved_view_file = ''; // Smart Path Containment Lookups $potential_paths = array( plugin_dir_path(__FILE__) . 'views/' . $view_map[$tab], // admin/views/ plugin_dir_path(dirname(__FILE__)) . 'views/' . $view_map[$tab], // root views/ plugin_dir_path(__FILE__) . $view_map[$tab] // direct file fallback ); foreach ($potential_paths as $path) { if (file_exists($path)) { $resolved_view_file = $path; break; } } if (! empty($resolved_view_file)) { include_once $resolved_view_file; } else { printf( '

%s %s

', esc_html__('Requested dashboard view file could not be found.', 'af-translate'), esc_html('views/' . $view_map[$tab]) ); } } elseif ('plugins' === $tab) { $this->render_all_extensions_matrix('plugin'); } elseif ('themes' === $tab) { $this->render_all_extensions_matrix('theme'); } } echo '
'; } /** * Renders workspace branding interface. */ private function render_global_header() { ?>

$data) { $slug = ('plugin' === $type) ? dirname($key) : $key; if ('.' === $slug) { $slug = basename($key, '.php'); } $name = ('plugin' === $type) ? $data['Name'] : $data->get('Name'); $is_active = ('plugin' === $type) ? is_plugin_active($key) : ($key === wp_get_theme()->get_stylesheet()); $domain = ('plugin' === $type) ? (! empty($data['TextDomain']) ? $data['TextDomain'] : $slug) : ($data->get('TextDomain') ? $data->get('TextDomain') : $slug); $target_menu_page = ('plugin' === $type) ? 'af-translate-plugins' : 'af-translate-themes'; ?>
' . esc_html__('Active', 'af-translate') . '' : '' . esc_html__('Inactive', 'af-translate') . ''; ?>
current_time('mysql'), 'message' => sanitize_text_field($message), 'context' => sanitize_key($context), 'user' => get_current_user_id() ); // Prepend new trace to the top of logs stack array_unshift($logs, $new_entry); // Threshold boundary engine - retain only the last 100 system entries if (count($logs) > 20) { $logs = array_slice($logs, 0, 20); } update_option('af_translate_activity_logs', $logs, false); } }