'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 */ 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 ); } } wp_enqueue_script( 'af-translate-bulk-engine', AF_TRANSLATE_URL . 'assets/js/ai-translate-bulk.js', array( 'jquery' ), '1.0.0', true ); wp_localize_script( 'af-translate-bulk-engine', 'afTranslateBulkSettings', 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( 'initiating' => __( 'Analyzing translation matrix blocks...', 'af-translate' ), 'processing' => __( 'Translating empty nodes via AI engine...', 'af-translate' ), 'complete' => __( 'Catalog mapping completed successfully! Syncing view...', 'af-translate' ), 'error' => __( 'Automation process interrupted. Check engine logs.', 'af-translate' ), 'select_engine' => __( 'Select Translation Engine Context', 'af-translate' ), 'run_batch' => __( 'Run AI Batch Process', 'af-translate' ), 'no_keys_title' => __( 'No Connected Translation Engines Found', 'af-translate' ), 'no_keys_desc' => __( 'You need to configure at least one active API provider key before running translation engines.', 'af-translate' ), 'configure_btn' => __( 'Configure AI API →', 'af-translate' ), 'cancel' => __( 'Cancel', 'af-translate' ), 'preview_loaded' => __( 'AI bulk translations loaded.', 'af-translate' ), 'writing_disk' => __( 'Writing updates to disk...', 'af-translate' ), ) ) ); wp_enqueue_script( 'af-translate-workbench-engine', AF_TRANSLATE_URL . 'assets/js/ai-translate-workbench.js', array( 'jquery', 'af-translate-bulk-engine' ), '1.0.0', 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 ); } }