false, 'auto_translate' => false, 'advanced_cache' => false, 'freemius' => false )); } // Exit if accessed directly if (!defined('ABSPATH')) { exit; } // Activation redirect to dashboard function eatp_activation_redirect( $plugin ) { if ( $plugin == plugin_basename( __FILE__ ) ) { // Set flag to show welcome popup on first visit update_option( 'aitrfoel_show_welcome_popup', 'yes' ); // For PRO: Only redirect if Freemius opt-in is already completed // For FREE: Always redirect (no Freemius opt-in screen) if ( defined('AITRFOEL_VERSION_TYPE') && AITRFOEL_VERSION_TYPE === 'pro' ) { // PRO version - let Freemius show opt-in screen if not registered if ( eatp_fs()->is_registered() ) { wp_safe_redirect( admin_url( 'admin.php?page=ai-translator-for-elementor-and-polylang&welcome=1' ) ); exit; } // Otherwise, Freemius will show its opt-in screen and redirect after } else { // FREE version - always redirect to plugin page wp_safe_redirect( admin_url( 'admin.php?page=ai-translator-for-elementor-and-polylang&welcome=1' ) ); exit; } } } add_action( 'activated_plugin', 'eatp_activation_redirect' ); /** * Main Plugin Class */ class AITRFOEL_Main { /** * Instance */ private static $instance = null; /** * Get instance */ public static function get_instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } /** * Constructor */ private function __construct() { $this->init_hooks(); } /** * Initialize hooks */ private function init_hooks() { // Check dependencies add_action('plugins_loaded', [$this, 'check_dependencies']); // Load plugin add_action('plugins_loaded', [$this, 'load_plugin']); // Activation/Deactivation hooks register_activation_hook(__FILE__, [$this, 'activate']); register_deactivation_hook(__FILE__, [$this, 'deactivate']); // Note: load_plugin_textdomain() removed - WordPress 4.6+ automatically loads // translations for plugins hosted on WordPress.org // Enqueue scripts add_action('admin_enqueue_scripts', [$this, 'enqueue_admin_scripts']); add_action('admin_enqueue_scripts', [$this, 'enqueue_deactivation_survey']); // Enqueue editor inline translations script (Elementor & Gutenberg) add_action('admin_enqueue_scripts', [$this, 'enqueue_editor_inline_scripts']); add_action('elementor/editor/after_enqueue_scripts', [$this, 'enqueue_editor_inline_scripts']); add_action('enqueue_block_editor_assets', [$this, 'enqueue_editor_inline_scripts']); // Plugin list row actions add_filter('plugin_action_links_' . AITRFOEL_PLUGIN_BASENAME, [$this, 'add_plugin_action_links']); // Hide Elementor admin elements on our plugin pages add_action('admin_init', [$this, 'disable_elementor_admin_ui']); // Welcome popup (shown once after activation) add_action('admin_footer', [$this, 'render_welcome_popup']); add_action('wp_ajax_aitrfoel_dismiss_welcome', [$this, 'dismiss_welcome_popup']); // Auto-repair previously translated Elementor templates missing taxonomy terms add_action('admin_init', [$this, 'repair_translated_elementor_templates']); // Removed: Background processing (using direct processing now) // add_action('aitrfoel_process_bulk_translation', [$this, 'process_bulk_translation_background'], 10, 4); } /** * Self-healing repair: automatically assigns missing elementor_library_type taxonomy terms * and regenerates CSS for any previously translated Elementor templates that were created * without taxonomy terms (restoring them from Theme Builder back to Saved Templates). */ public function repair_translated_elementor_templates() { if (!is_admin() || !post_type_exists('elementor_library')) { return; } $repair_flag = 'aitrfoel_tpl_tax_repaired_v315'; if (get_option($repair_flag, false)) { return; } if (!class_exists('AITRFOEL_Translation_Engine')) { return; } $engine = AITRFOEL_Translation_Engine::get_instance(); $translated_templates = get_posts([ 'post_type' => 'elementor_library', 'post_status' => 'any', 'posts_per_page' => -1, 'fields' => 'ids', 'meta_query' => [ [ 'key' => '_aitrfoel_source_page', 'compare' => 'EXISTS', ], ], ]); if (!empty($translated_templates)) { foreach ($translated_templates as $tpl_id) { $source_id = (int) get_post_meta($tpl_id, '_aitrfoel_source_page', true); if ($source_id > 0 && $source_id !== (int) $tpl_id) { $engine->sync_elementor_template_meta_and_taxonomies($source_id, $tpl_id); } else { $tpl_type = get_post_meta($tpl_id, '_elementor_template_type', true); $existing_terms = wp_get_object_terms($tpl_id, 'elementor_library_type', ['fields' => 'slugs']); if (empty($existing_terms) && !empty($tpl_type)) { wp_set_object_terms($tpl_id, (string) $tpl_type, 'elementor_library_type'); } } } } update_option($repair_flag, true); } /** * Add plugin row action links on the Plugins screen. * * @param array $links Existing action links. * @return array */ public function add_plugin_action_links($links) { $settings_link = '' . esc_html__('Settings', 'ai-translator-for-elementor-and-polylang') . ''; array_unshift($links, $settings_link); return $links; } /** * Check plugin dependencies * @param bool $show_notices Whether to show admin notices * @return array Array with 'status' (bool) and 'missing' (array of missing dependencies) */ public function check_dependencies($show_notices = true) { $missing = []; // Check PHP version (hard requirement) if (version_compare(PHP_VERSION, '7.4', '<')) { $missing[] = 'php_version'; if ($show_notices) { add_action('admin_notices', [$this, 'admin_notice_php_version']); } } return [ 'status' => empty($missing), 'missing' => $missing ]; } /** * Load plugin components */ public function load_plugin() { $deps = $this->check_dependencies(true); // Store dependencies status early for the dashboard update_option('aitrfoel_dependencies_status', $deps); // Always load admin menu so plugin is visible, even when dependencies aren't met if (is_admin()) { // Load classes required for admin pages to work require_once AITRFOEL_PLUGIN_DIR . 'includes/class-license-manager-server.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-license-manager.php'; require_once AITRFOEL_PLUGIN_DIR . 'admin/class-admin-menu.php'; require_once AITRFOEL_PLUGIN_DIR . 'admin/class-settings.php'; // Wizard AJAX must work even when deps are missing (it IS the onboarding path) require_once AITRFOEL_PLUGIN_DIR . 'admin/class-wizard-controller.php'; // Initialize admin menu directly (not in a hook since we're already in plugins_loaded) AITRFOEL_Admin_Menu::get_instance(); AITRFOEL_Wizard_Controller::get_instance(); } // If dependencies are not met, don't load the rest of the plugin if (!$deps['status']) { return; } // Load core classes $this->load_includes(); // Initialize components $this->init_components(); } /** * Load include files */ private function load_includes() { // Core includes - Load logger FIRST (used by many classes) require_once AITRFOEL_PLUGIN_DIR . 'includes/class-logger.php'; // Load language mapper and widget parser require_once AITRFOEL_PLUGIN_DIR . 'includes/class-language-mapper.php'; $widget_parser_file = AITRFOEL_PLUGIN_DIR . 'includes/class-widget-parser.php'; if (!file_exists($widget_parser_file)) { error_log('AITRFOEL Fatal: Widget parser file not found at: ' . $widget_parser_file); throw new Exception('Widget parser file not found'); } require_once $widget_parser_file; // Verify class was loaded if (!class_exists('AITRFOEL_Widget_Parser')) { error_log('AITRFOEL Fatal: AITRFOEL_Widget_Parser class not defined after loading file'); throw new Exception('AITRFOEL_Widget_Parser class not defined'); } require_once AITRFOEL_PLUGIN_DIR . 'includes/class-translation-memory.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-openai-api.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-post-processor.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-gap-filler.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-translation-engine.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-theme-compatibility.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-cache-manager.php'; // Content type detection and parsers for different editors require_once AITRFOEL_PLUGIN_DIR . 'includes/class-content-detector.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-classic-parser.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-gutenberg-parser.php'; // Load debug logger if it exists if (file_exists(AITRFOEL_PLUGIN_DIR . 'includes/class-debug-logger.php')) { require_once AITRFOEL_PLUGIN_DIR . 'includes/class-debug-logger.php'; } require_once AITRFOEL_PLUGIN_DIR . 'includes/class-usage-tracker.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-bulk-translation-queue.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-license-manager-server.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-license-manager.php'; // Backward compatibility alias require_once AITRFOEL_PLUGIN_DIR . 'includes/class-api-client.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-background-sync.php'; // Admin includes (may already be loaded in load_plugin for dependency-failed cases) if (is_admin()) { if (!class_exists('AITRFOEL_Admin_Menu')) { require_once AITRFOEL_PLUGIN_DIR . 'admin/class-admin-menu.php'; } if (!class_exists('AITRFOEL_Settings')) { require_once AITRFOEL_PLUGIN_DIR . 'admin/class-settings.php'; } if (!class_exists('AITRFOEL_Translate_All')) { require_once AITRFOEL_PLUGIN_DIR . 'admin/class-translate-all.php'; } if (!class_exists('AITRFOEL_Translation_History')) { require_once AITRFOEL_PLUGIN_DIR . 'admin/class-translation-history.php'; } if (!class_exists('AITRFOEL_Review_Editor')) { require_once AITRFOEL_PLUGIN_DIR . 'admin/class-review-editor.php'; } if (!class_exists('AITRFOEL_Wizard_Controller')) { require_once AITRFOEL_PLUGIN_DIR . 'admin/class-wizard-controller.php'; } } // Translation Metabox & Admin Top Bar menu must be loaded for both admin and frontend if (!class_exists('AITRFOEL_Translation_Metabox')) { require_once AITRFOEL_PLUGIN_DIR . 'admin/class-translation-metabox.php'; } if (!class_exists('AITRFOEL_Admin_Bar')) { require_once AITRFOEL_PLUGIN_DIR . 'includes/class-admin-bar.php'; } if (!class_exists('AITRFOEL_WP_List_Translator')) { require_once AITRFOEL_PLUGIN_DIR . 'includes/class-wp-list-translator.php'; } // Translate All must also be loaded for WP-Cron (non-admin context) if (!is_admin() && !class_exists('AITRFOEL_Translate_All')) { require_once AITRFOEL_PLUGIN_DIR . 'admin/class-translate-all.php'; } // AJAX handler must be loaded for both admin and frontend AJAX requests require_once AITRFOEL_PLUGIN_DIR . 'admin/class-ajax-handler.php'; // Load multilingual integrations (abstract interface, manager, and implementations) require_once AITRFOEL_PLUGIN_DIR . 'includes/class-multilingual-interface.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-polylang-integration.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-wpml-integration.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-builtin-integration.php'; require_once AITRFOEL_PLUGIN_DIR . 'includes/class-multilingual-manager.php'; // Load analytics (PostHog integration) require_once AITRFOEL_PLUGIN_DIR . 'includes/class-analytics.php'; // Debug tools removed - plugin simplified } /** * Initialize plugin components */ private function init_components() { try { // Run database migration if version upgraded $db_version = get_option('aitrfoel_db_version', '0.0.0'); if (version_compare($db_version, AITRFOEL_VERSION, '<')) { $this->create_tables(); update_option('aitrfoel_db_version', AITRFOEL_VERSION); } // PERFORMANCE: Disable Freemius background sync during AJAX translation requests $this->maybe_disable_freemius_sync(); // Initialize core components AITRFOEL_Content_Detector::get_instance(); AITRFOEL_Widget_Parser::get_instance(); AITRFOEL_License_Manager_Server::get_instance(); // Initialize server-based license manager AITRFOEL_OpenAI_API::get_instance(); AITRFOEL_Translation_Engine::get_instance(); AITRFOEL_Cache_Manager::get_instance(); AITRFOEL_Logger::get_instance(); // Initialize debug logger if available if (class_exists('AITRFOEL_Debug_Logger')) { AITRFOEL_Debug_Logger::get_instance(); } AITRFOEL_Usage_Tracker::get_instance(); // Initialize usage tracker AITRFOEL_Bulk_Translation_Queue::get_instance(); // Initialize bulk translation queue AITRFOEL_Background_Sync::get_instance(); // Initialize background auto-sync manager // Initialize translation metabox & admin bar menu (active on frontend & admin) if (class_exists('AITRFOEL_Translation_Metabox')) { AITRFOEL_Translation_Metabox::get_instance(); } if (class_exists('AITRFOEL_Admin_Bar')) { AITRFOEL_Admin_Bar::get_instance(); } if (class_exists('AITRFOEL_WP_List_Translator')) { AITRFOEL_WP_List_Translator::get_instance(); } // Initialize admin with error checking if (is_admin()) { AITRFOEL_Admin_Menu::get_instance(); AITRFOEL_Settings::init(); // Initialize Translate All feature if (class_exists('AITRFOEL_Translate_All')) { AITRFOEL_Translate_All::get_instance(); } } // Register Translate All cron hook (must be available outside admin for WP-Cron) add_action('aitrfoel_translate_all_cron_process', ['AITRFOEL_Translate_All', 'process_cron_batch']); // Register custom cron interval (5 minutes) add_filter('cron_schedules', function($schedules) { if (!isset($schedules['aitrfoel_five_minutes'])) { $schedules['aitrfoel_five_minutes'] = [ 'interval' => 300, 'display' => __('Every 5 Minutes (Meep Translator)', 'ai-translator-for-elementor-and-polylang'), ]; } return $schedules; }); // Initialize AJAX handler (needed for both admin and frontend AJAX requests) if (class_exists('AITRFOEL_Ajax_Handler')) { AITRFOEL_Ajax_Handler::get_instance(); } else { error_log('EAT Error: AITRFOEL_Ajax_Handler class not found - using fallback'); } // Initialize Translation History (registers AJAX handler for clear history) if (class_exists('AITRFOEL_Translation_History')) { AITRFOEL_Translation_History::get_instance(); } // Initialize Review Editor (hidden subpage + save-segment AJAX) if (class_exists('AITRFOEL_Review_Editor')) { AITRFOEL_Review_Editor::get_instance(); } // Initialize Wizard Controller (wizard AJAX actions) if (class_exists('AITRFOEL_Wizard_Controller')) { AITRFOEL_Wizard_Controller::get_instance(); } // Initialize multilingual integration manager (auto-detects Polylang or WPML) AITRFOEL_Multilingual_Manager::get_instance(); // Register language switcher shortcode for all multilingual integrations // (built-in integration registers its own, but Polylang/WPML users need this too) add_action('init', function() { if (!shortcode_exists('aitrfoel_language_switcher')) { add_shortcode('aitrfoel_language_switcher', function($atts) { if (!get_option('aitrfoel_switcher_enabled', true)) { return ''; } $multilingual = AITRFOEL_Multilingual_Manager::get_instance(); if (!$multilingual->is_active()) { return ''; } $atts = shortcode_atts([ 'style' => get_option('aitrfoel_switcher_style', 'flags-text'), 'show_names' => get_option('aitrfoel_switcher_show_names', true) ? 'true' : 'false', 'show_flags' => get_option('aitrfoel_switcher_show_flags', true) ? 'true' : 'false', ], $atts); $switcher_data = $multilingual->get_language_switcher_data(); if (empty($switcher_data)) { return ''; } // Use builtin renderer if available (it has the full rendering with CSS) if (class_exists('AITRFOEL_Builtin_Integration') && method_exists(AITRFOEL_Builtin_Integration::get_instance(), 'render_language_switcher_shortcode')) { return AITRFOEL_Builtin_Integration::get_instance()->render_language_switcher_shortcode($atts); } // Fallback generic renderer for Polylang/WPML return aitrfoel_render_language_switcher($switcher_data, $atts); }); } }); // Initialize analytics (PostHog) AITRFOEL_Analytics::get_instance(); } catch (Exception $e) { error_log('EAT Fatal Error during component initialization: ' . $e->getMessage()); add_action('admin_notices', function() use ($e) { echo '

Meep AI Translator Error: ' . esc_html($e->getMessage()) . '

'; }); } catch (Error $e) { error_log('EAT Fatal PHP Error during component initialization: ' . $e->getMessage()); add_action('admin_notices', function() use ($e) { echo '

Meep AI Translator Fatal Error: ' . esc_html($e->getMessage()) . '

'; }); } } /** * Disable Elementor admin UI elements on our plugin pages */ public function disable_elementor_admin_ui() { // Check if we're on one of our plugin pages $screen = get_current_screen(); if (!$screen) { return; } // Check if current page is ANY of our plugin pages (including Freemius pages) $page = isset($_GET['page']) ? $_GET['page'] : ''; $is_our_page = ( strpos($screen->id, 'ai-translator-for-elementor-and-polylang') !== false || strpos($page, 'ai-translator-for-elementor-and-polylang') !== false || $screen->id === 'admin_page_aitrfoel-debug' ); if ($is_our_page) { // Add immediate CSS to prevent flashing add_action('admin_head', [$this, 'add_immediate_elementor_hiding'], 1); // Remove Elementor admin top bar actions remove_action('admin_notices', '\Elementor\Core\Admin\Admin_Notices::admin_notices'); remove_action('admin_bar_menu', '\Elementor\Core\Admin\Menu\Admin_Menu_Manager::add_menu', 200); // Remove Elementor footer text add_filter('admin_footer_text', [$this, 'restore_admin_footer'], 1000); // Prevent Elementor from loading its admin styles/scripts on our pages remove_action('admin_enqueue_scripts', '\Elementor\Core\Admin\UI\Components\Form_Manager::enqueue_form_manager_scripts'); // More aggressive: Prevent Elementor admin completely on our pages add_action('admin_enqueue_scripts', [$this, 'prevent_elementor_admin_scripts'], 1); // Add custom CSS to hide any remaining Elementor elements add_action('admin_head', [$this, 'hide_elementor_admin_elements']); } } /** * PERFORMANCE: Disable Freemius background sync during AJAX translation requests */ private function maybe_disable_freemius_sync() { // Check if this is a translation AJAX request if ( defined('DOING_AJAX') && DOING_AJAX && isset($_REQUEST['action']) ) { $translation_actions = [ 'aitrfoel_translate_page', 'aitrfoel_bulk_translate', 'aitrfoel_get_translation_preview', 'aitrfoel_detect_language', 'aitrfoel_get_page_strings' ]; if ( in_array($_REQUEST['action'], $translation_actions) ) { // Disable Freemius background sync during translation add_filter( 'fs_is_long_term_user', '__return_false', 999 ); add_filter( 'fs_is_submenu_visible', '__return_false', 999 ); // Prevent any Freemius API calls during translation if ( function_exists('eatp_fs') && isset($GLOBALS['eatp_fs']) ) { // Skip connectivity tests add_filter( 'fs_skip_connectivity_test__20522', '__return_true', 999 ); } } } } /** * Prevent Elementor from loading admin scripts on our pages */ public function prevent_elementor_admin_scripts() { // Get current screen $screen = get_current_screen(); $page = isset($_GET['page']) ? $_GET['page'] : ''; $is_our_page = ( strpos($screen->id, 'ai-translator-for-elementor-and-polylang') !== false || strpos($page, 'ai-translator-for-elementor-and-polylang') !== false || $screen->id === 'admin_page_aitrfoel-debug' ); if ($is_our_page) { // Dequeue Elementor admin scripts and styles wp_dequeue_script('elementor-admin-top-bar'); wp_dequeue_script('elementor-admin'); wp_dequeue_script('elementor-admin-feedback'); wp_dequeue_script('elementor-admin-notifications'); wp_dequeue_style('elementor-admin-top-bar'); wp_dequeue_style('elementor-admin'); // Prevent Elementor admin bar from initializing if (class_exists('\Elementor\Core\Admin\Admin_Notices')) { remove_action('admin_notices', [\Elementor\Core\Admin\Admin_Notices::instance(), 'admin_notices']); } // Remove Elementor admin menu if (class_exists('\Elementor\Core\Admin\Menu\Admin_Menu_Manager')) { remove_action('admin_bar_menu', [\Elementor\Core\Admin\Menu\Admin_Menu_Manager::instance(), 'add_menu'], 200); } } } /** * Add immediate CSS to hide Elementor elements before they render */ public function add_immediate_elementor_hiding() { // Enqueue a minimal inline style early wp_register_style('aitrfoel-elementor-hiding-early', false); wp_enqueue_style('aitrfoel-elementor-hiding-early'); $css = ' /* Hide Elementor admin top bar immediately */ #e-admin-top-bar-root, .e-admin-top-bar, .e-admin-top-bar--active, [id*="e-admin-top-bar"], [class*="e-admin-top-bar"] { display: none !important; visibility: hidden !important; opacity: 0 !important; height: 0 !important; overflow: hidden !important; } /* Hide Elementor admin notices */ .e-admin-notice { display: none !important; } /* Prevent body class interference */ body.e-admin-top-bar--active { padding-top: 0 !important; } '; wp_add_inline_style('aitrfoel-elementor-hiding-early', $css); } /** * Hide Elementor admin elements with additional CSS */ public function hide_elementor_admin_elements() { // Enqueue additional hiding rules wp_register_style('aitrfoel-elementor-hiding-additional', false); wp_enqueue_style('aitrfoel-elementor-hiding-additional'); $css = ' /* Additional Elementor hiding rules */ #e-admin-top-bar-root, .e-admin-top-bar, .e-admin-top-bar--active, .e-admin-top-bar__container, .e-admin-top-bar__main-area, [id*="e-admin-top-bar"], [class*="e-admin-top-bar"] { display: none !important; visibility: hidden !important; opacity: 0 !important; height: 0 !important; width: 0 !important; overflow: hidden !important; position: absolute !important; left: -9999px !important; } /* Hide Elementor admin notices and components */ .e-admin-notice, .e-admin-components-wrapper, .e-admin-feedback, .e-admin-announcement { display: none !important; } /* Reset body padding that Elementor might have added */ body { padding-top: 0 !important; } /* Hide any Elementor React root elements */ [data-reactroot], [data-react-helmet] { display: none !important; } '; wp_add_inline_style('aitrfoel-elementor-hiding-additional', $css); } /** * Restore admin footer text */ public function restore_admin_footer($text) { $text = (string) $text; // Remove Elementor's custom footer text if (strpos($text, 'Elementor') !== false) { return ''; } return $text; } /** * Enqueue admin scripts and styles */ public function enqueue_admin_scripts($hook) { $hook = (string) $hook; $page = isset($_GET['page']) ? (string) $_GET['page'] : ''; // Only load on our plugin pages - use NEW plugin slug // Be more inclusive to catch Freemius pages and any related admin pages $is_our_page = ( strpos($hook, 'ai-translator-for-elementor-and-polylang') !== false || strpos($hook, 'eatp') !== false || // Our plugin prefix strpos($hook, 'freemius') !== false || // Freemius pages $hook === 'admin_page_aitrfoel-debug' || // Also check for any admin page that might be related strpos($page, 'ai-translator-for-elementor-and-polylang') !== false ); if (!$is_our_page) { return; } // Enqueue unified admin stylesheet (covers Elementor-bar hiding, design tokens, all page styles) wp_enqueue_style( 'aitrfoel-meep-admin', AITRFOEL_PLUGIN_URL . 'assets/css/meep-admin.css', [], AITRFOEL_VERSION ); // Runtime DOM enhancements for Phase B UI (icons, eyebrows, hero, counters). wp_enqueue_script( 'aitrfoel-meep-admin', AITRFOEL_PLUGIN_URL . 'assets/js/meep-admin.js', [], AITRFOEL_VERSION, true ); wp_localize_script( 'aitrfoel-meep-admin', 'meepAdmin', [ 'pluginName' => __( 'Meep AI Translator', 'ai-translator-for-elementor-and-polylang' ), 'logoUrl' => AITRFOEL_PLUGIN_URL . 'assets/img/meep-ai-translator.png', ] ); // Enqueue scripts wp_enqueue_script( 'aitrfoel-debug-monitor', AITRFOEL_PLUGIN_URL . 'assets/js/debug-monitor.js', ['jquery'], AITRFOEL_VERSION, true ); wp_enqueue_script( 'aitrfoel-admin-script', AITRFOEL_PLUGIN_URL . 'assets/js/admin.js', ['jquery', 'aitrfoel-debug-monitor'], AITRFOEL_VERSION, true ); // Localize script wp_localize_script('aitrfoel-admin-script', 'aitrfoel_ajax', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aitrfoel_ajax_nonce'), 'diagnose_nonce' => wp_create_nonce('aitrfoel_diagnose_theme_nonce'), 'validate_word_count_nonce' => wp_create_nonce('aitrfoel_validate_word_count'), 'save_cooldown_nonce' => wp_create_nonce('aitrfoel_save_cooldown'), 'clear_usage_nonce' => wp_create_nonce('aitrfoel_clear_usage'), 'debug' => true, // Add debug flag 'strings' => [ // Basic translations 'plugin_title' => __('Meep AI Translator', 'ai-translator-for-elementor-and-polylang'), 'translating' => __('Translating...', 'ai-translator-for-elementor-and-polylang'), 'success' => __('Translation completed successfully!', 'ai-translator-for-elementor-and-polylang'), 'error' => __('Translation failed. Please try again.', 'ai-translator-for-elementor-and-polylang'), 'confirm_translate' => __('Are you sure you want to translate this content?', 'ai-translator-for-elementor-and-polylang'), // Page loading 'loading_pages' => __('Loading pages...', 'ai-translator-for-elementor-and-polylang'), 'select_page' => __('Select a page to translate', 'ai-translator-for-elementor-and-polylang'), 'error_loading_pages' => __('Error loading pages', 'ai-translator-for-elementor-and-polylang'), 'select_page_first' => __('Please select a page first', 'ai-translator-for-elementor-and-polylang'), // Cost estimation 'calculating' => __('Calculating...', 'ai-translator-for-elementor-and-polylang'), 'estimated_cost' => __('Estimated Cost', 'ai-translator-for-elementor-and-polylang'), 'words' => __('Words', 'ai-translator-for-elementor-and-polylang'), 'tokens' => __('Tokens', 'ai-translator-for-elementor-and-polylang'), 'provider' => __('Provider', 'ai-translator-for-elementor-and-polylang'), 'model' => __('Model', 'ai-translator-for-elementor-and-polylang'), 'large_page_warning' => __('This page contains a lot of content and may take longer to translate.', 'ai-translator-for-elementor-and-polylang'), 'error_calculating' => __('Error calculating cost', 'ai-translator-for-elementor-and-polylang'), 'estimate_cost' => __('Estimate Cost', 'ai-translator-for-elementor-and-polylang'), // Translation process 'select_language' => __('Please select a target language', 'ai-translator-for-elementor-and-polylang'), 'time_taken' => __('Time taken', 'ai-translator-for-elementor-and-polylang'), 'words_translated' => __('Words translated', 'ai-translator-for-elementor-and-polylang'), 'buy_more_tokens' => __('Buy More Tokens', 'ai-translator-for-elementor-and-polylang'), 'buy_more' => __('Buy More', 'ai-translator-for-elementor-and-polylang'), 'buy_more_words' => __('Buy More Words', 'ai-translator-for-elementor-and-polylang'), 'upgrade_plan' => __('Upgrade Plan', 'ai-translator-for-elementor-and-polylang'), 'total_words' => __('Total Words', 'ai-translator-for-elementor-and-polylang'), 'words_left' => __('Words Left', 'ai-translator-for-elementor-and-polylang'), 'license' => __('License', 'ai-translator-for-elementor-and-polylang'), 'pages_left' => __('Pages Left', 'ai-translator-for-elementor-and-polylang'), // Tooltip strings /* translators: 1: Plan name, 2: Number of words */ 'plan_tooltip' => __('%1$s Plan: %2$s words included', 'ai-translator-for-elementor-and-polylang'), /* translators: 1: Words used, 2: Total words available */ 'usage_tooltip' => __('Used: %1$s of %2$s words', 'ai-translator-for-elementor-and-polylang'), 'tokens_left' => __('Tokens Left', 'ai-translator-for-elementor-and-polylang'), 'actual_cost' => __('Actual cost', 'ai-translator-for-elementor-and-polylang'), 'preview_translation' => __('Preview Translation', 'ai-translator-for-elementor-and-polylang'), 'edit_translation' => __('Edit Translation', 'ai-translator-for-elementor-and-polylang'), 'publish_now' => __('Publish Now', 'ai-translator-for-elementor-and-polylang'), 'translate_page' => __('Translate Page', 'ai-translator-for-elementor-and-polylang'), 'confirm_publish' => __('Are you sure you want to publish this page?', 'ai-translator-for-elementor-and-polylang'), 'page_published' => __('Page published successfully!', 'ai-translator-for-elementor-and-polylang'), // Errors 'timeout_error' => __('Request timed out. Please try again.', 'ai-translator-for-elementor-and-polylang'), 'network_error' => __('Network error:', 'ai-translator-for-elementor-and-polylang'), // Cache management 'confirm_clear_cache' => __('Are you sure you want to clear the translation cache?', 'ai-translator-for-elementor-and-polylang'), 'clearing_cache' => __('Clearing translation cache...', 'ai-translator-for-elementor-and-polylang'), 'cache_error' => __('Failed to clear cache:', 'ai-translator-for-elementor-and-polylang'), // UI messages 'found_strings' => __('Found', 'ai-translator-for-elementor-and-polylang'), 'translatable_strings' => __('translatable strings.', 'ai-translator-for-elementor-and-polylang'), 'no_content' => __('No translatable content found on this page.', 'ai-translator-for-elementor-and-polylang'), 'select_strings' => __('Please select at least one string to translate.', 'ai-translator-for-elementor-and-polylang'), 'ready' => __('Ready to translate another page.', 'ai-translator-for-elementor-and-polylang'), 'cost_complete' => __('Cost estimation completed successfully!', 'ai-translator-for-elementor-and-polylang'), // Usage statistics 'pages_translated' => __('Pages Translated', 'ai-translator-for-elementor-and-polylang'), 'words_processed' => __('Words Processed', 'ai-translator-for-elementor-and-polylang'), 'total_cost' => __('Total Cost', 'ai-translator-for-elementor-and-polylang'), 'daily_average' => __('Daily Average', 'ai-translator-for-elementor-and-polylang'), 'breakdown_by_provider' => __('Breakdown by Provider', 'ai-translator-for-elementor-and-polylang'), 'uses' => __('Uses', 'ai-translator-for-elementor-and-polylang'), 'cost' => __('Cost', 'ai-translator-for-elementor-and-polylang'), 'error_loading_stats' => __('Error loading statistics', 'ai-translator-for-elementor-and-polylang'), // Theme compatibility 'checking' => __('Checking...', 'ai-translator-for-elementor-and-polylang'), 'theme_check_complete' => __('Theme check complete', 'ai-translator-for-elementor-and-polylang'), 'recheck_compatibility' => __('Re-check Compatibility', 'ai-translator-for-elementor-and-polylang'), 'theme' => __('Theme', 'ai-translator-for-elementor-and-polylang'), 'status' => __('Status', 'ai-translator-for-elementor-and-polylang'), 'fields_found' => __('Fields found', 'ai-translator-for-elementor-and-polylang'), 'view_fields' => __('View fields', 'ai-translator-for-elementor-and-polylang'), 'detected_fields' => __('Detected Fields', 'ai-translator-for-elementor-and-polylang'), 'copy_all' => __('Copy all fields', 'ai-translator-for-elementor-and-polylang'), 'no_fields_found' => __('No fields found', 'ai-translator-for-elementor-and-polylang'), 'fields_copied' => __('Fields copied to clipboard', 'ai-translator-for-elementor-and-polylang'), 'click_to_check_compatibility' => __('Click the button to check theme compatibility.', 'ai-translator-for-elementor-and-polylang'), ], 'is_pro' => (defined('AITRFOEL_VERSION_TYPE') && AITRFOEL_VERSION_TYPE === 'pro'), ]); // Add inline script for immediate Elementor removal wp_add_inline_script('aitrfoel-admin-script', ' (function() { // Immediate Elementor removal - runs before DOM ready function removeElementor() { var selectors = [ "#e-admin-top-bar-root", ".e-admin-top-bar", ".e-admin-top-bar--active", "[id*=\"e-admin-top-bar\"]", "[class*=\"e-admin-top-bar\"]" ]; selectors.forEach(function(selector) { var elements = document.querySelectorAll(selector); elements.forEach(function(el) { if (el) el.remove(); }); }); // Remove body classes document.body.classList.remove("e-admin-top-bar--active"); document.body.className = document.body.className.replace(/e-admin-top-bar[^\s]*/g, ""); } // Run immediately removeElementor(); // Run when DOM is ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", removeElementor); } else { removeElementor(); } // Continue watching for changes if (window.MutationObserver) { var observer = new MutationObserver(function(mutations) { var shouldRemove = false; mutations.forEach(function(mutation) { if (mutation.type === "childList") { mutation.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if ((node.id && node.id.includes("e-admin-top-bar")) || (node.className && node.className.includes && node.className.includes("e-admin-top-bar"))) { shouldRemove = true; } } }); } }); if (shouldRemove) removeElementor(); }); observer.observe(document.body, { childList: true, subtree: true }); } })(); ', 'before'); } /** * Enqueue deactivation survey script and style on plugins.php page */ public function enqueue_deactivation_survey($hook) { if ($hook !== 'plugins.php') { return; } // Only load in the FREE version (Freemius handles PRO version surveys) if (defined('AITRFOEL_VERSION_TYPE') && AITRFOEL_VERSION_TYPE !== 'free') { return; } wp_enqueue_style( 'aitrfoel-deactivation-survey-style', AITRFOEL_PLUGIN_URL . 'assets/css/deactivation-survey.css', [], AITRFOEL_VERSION ); wp_enqueue_script( 'aitrfoel-deactivation-survey', AITRFOEL_PLUGIN_URL . 'assets/js/deactivation-survey.js', ['jquery'], AITRFOEL_VERSION, true ); wp_localize_script('aitrfoel-deactivation-survey', 'aitrfoelDeactivation', [ 'siteUrl' => esc_url(home_url()), ]); } /** * Enqueue inline translation scripts in Gutenberg and Elementor editor sessions */ public function enqueue_editor_inline_scripts() { global $pagenow; $is_editor_page = ($pagenow === 'post.php' || $pagenow === 'post-new.php' || (isset($_GET['action']) && $_GET['action'] === 'elementor')); $is_hook_call = current_action() === 'elementor/editor/after_enqueue_scripts' || current_action() === 'enqueue_block_editor_assets'; if ( ! $is_editor_page && ! $is_hook_call ) { return; } wp_enqueue_script( 'aitrfoel-editor-inline-translation', AITRFOEL_PLUGIN_URL . 'assets/js/editor-inline-translation.js', ['jquery'], AITRFOEL_VERSION, true ); $post_id = 0; if (isset($_GET['post'])) { $post_id = intval($_GET['post']); } elseif (isset($_POST['post_ID'])) { $post_id = intval($_POST['post_ID']); } $current_lang = 'en'; if ($post_id > 0 && class_exists('AITRFOEL_Multilingual_Manager')) { $current_lang = AITRFOEL_Multilingual_Manager::get_instance()->get_post_language($post_id); } if (empty($current_lang) && class_exists('AITRFOEL_Multilingual_Manager')) { $current_lang = AITRFOEL_Multilingual_Manager::get_instance()->get_default_language(); } if (empty($current_lang)) { $current_lang = 'en'; } $target_lang_name = 'English'; if (class_exists('AITRFOEL_Language_Mapper')) { $target_lang_name = AITRFOEL_Language_Mapper::get_language_name($current_lang); } // Pass REST API information to the script wp_localize_script('aitrfoel-editor-inline-translation', 'aitrfoelEditorInline', [ 'rest_url' => esc_url_raw(rest_url('meep-translator/v1/translate-string')), 'nonce' => wp_create_nonce('wp_rest'), 'current_lang' => $current_lang, 'current_lang_name' => $target_lang_name, 'strings' => [ 'translate' => sprintf(__('Translate to %s', 'ai-translator-for-elementor-and-polylang'), $target_lang_name), 'translating' => __('Translating...', 'ai-translator-for-elementor-and-polylang'), 'success' => __('Translated successfully!', 'ai-translator-for-elementor-and-polylang'), 'failed' => __('Translation failed', 'ai-translator-for-elementor-and-polylang'), ] ]); } /** * Plugin activation */ public function activate() { // Create database tables if needed $this->create_tables(); // Set default options $this->set_default_options(); // Create cache directory $this->create_cache_directory(); // Log activation if (class_exists('AITRFOEL_Logger')) { AITRFOEL_Logger::log('Plugin activated', 'info'); } // Immediately register activation with remote server if (class_exists('AITRFOEL_License_Manager_Server')) { try { $license_server = AITRFOEL_License_Manager_Server::get_instance(); $license_key = $license_server->get_license_key(); if (empty($license_key)) { $license_server->initialize_user_in_remote_db('Free', null); } } catch (Exception $e) { // Silent fail on activation hook if network is unreachable } } // Clear rewrite rules flush_rewrite_rules(); } /** * Plugin deactivation */ public function deactivate() { // Clean up scheduled tasks wp_clear_scheduled_hook('aitrfoel_cleanup_cache'); // Log deactivation if (class_exists('AITRFOEL_Logger')) { AITRFOEL_Logger::log('Plugin deactivated', 'info'); } // Clear rewrite rules flush_rewrite_rules(); } /** * Create database tables */ private function create_tables() { global $wpdb; $charset_collate = $wpdb->get_charset_collate(); // Translation cache table $table_name = $wpdb->prefix . 'aitrfoel_translation_cache'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, source_hash varchar(32) NOT NULL, source_text longtext NOT NULL, translated_text longtext NOT NULL, source_lang varchar(10) NOT NULL, target_lang varchar(10) NOT NULL, model varchar(50) NOT NULL, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY source_hash_langs (source_hash, source_lang, target_lang), KEY target_lang (target_lang), KEY created_at (created_at) ) $charset_collate;"; require_once(ABSPATH . 'wp-admin/includes/upgrade.php'); dbDelta($sql); // Usage statistics table $table_name = $wpdb->prefix . 'aitrfoel_usage_stats'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, user_id bigint(20) NOT NULL, provider varchar(20) NOT NULL, model varchar(50) NOT NULL, input_tokens int DEFAULT 0, output_tokens int DEFAULT 0, word_count int DEFAULT 0, page_count int DEFAULT 0, estimated_cost decimal(10,6) DEFAULT 0, created_at datetime DEFAULT CURRENT_TIMESTAMP, month_year varchar(7) NOT NULL, PRIMARY KEY (id), KEY user_month (user_id, month_year), KEY provider_model (provider, model), KEY created_at (created_at) ) $charset_collate;"; dbDelta($sql); // Translation history table $table_name = $wpdb->prefix . 'aitrfoel_translation_history'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, original_post_id bigint(20) NOT NULL, translated_post_id bigint(20) NOT NULL, source_lang varchar(10) NOT NULL DEFAULT '', target_lang varchar(10) NOT NULL, content_type varchar(20) NOT NULL DEFAULT 'elementor', word_count int NOT NULL DEFAULT 0, status varchar(20) NOT NULL DEFAULT 'success', duration decimal(8,2) DEFAULT 0, user_id bigint(20) NOT NULL DEFAULT 0, created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY original_post (original_post_id), KEY target_lang (target_lang), KEY created_at (created_at), KEY user_id (user_id) ) $charset_collate;"; dbDelta($sql); // Glossary table $table_name = $wpdb->prefix . 'aitrfoel_glossary'; $sql = "CREATE TABLE IF NOT EXISTS $table_name ( id bigint(20) NOT NULL AUTO_INCREMENT, source_text varchar(255) NOT NULL, target_text varchar(255) NOT NULL, lang_code varchar(10) NOT NULL, action varchar(20) NOT NULL DEFAULT 'replace', created_at datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY lang_code (lang_code) ) $charset_collate;"; dbDelta($sql); } /** * Set default plugin options */ private function set_default_options() { // Provider selection - use the correct option name that settings page uses add_option('aitrfoel_api_provider', 'openai'); // Default to OpenAI // Legacy option for backwards compatibility add_option('aitrfoel_translation_provider', 'openai'); // Default to OpenAI // Model settings (used on MeepTech server) add_option('aitrfoel_model', 'gpt-4o-mini'); add_option('aitrfoel_temperature', 0.3); add_option('aitrfoel_max_tokens', 4000); // Translation settings add_option('aitrfoel_cache_enabled', true); add_option('aitrfoel_cache_duration', 30); // days add_option('aitrfoel_batch_size', 5); add_option('aitrfoel_rate_limit', 10); // requests per minute // UI settings add_option('aitrfoel_preview_enabled', true); add_option('aitrfoel_auto_save', false); add_option('aitrfoel_debug_mode', false); // License add_option('aitrfoel_license_key', ''); add_option('aitrfoel_license_status', 'inactive'); // Gap-Filler defaults add_option('aitrfoel_gap_enabled', true); add_option('aitrfoel_gap_max_retry', 1); add_option('aitrfoel_gap_lev_threshold', 3); add_option('aitrfoel_gap_residue_patterns', '/\\b(Read more|Číst dál|Читать далее)\\b/ui'); add_option('aitrfoel_gap_enable_postprocessor', true); } /** * Create cache directory in uploads folder */ private function create_cache_directory() { // Use uploads directory instead of wp-content/cache $upload_dir = wp_upload_dir(); $cache_dir = $upload_dir['basedir'] . '/ai-translator-cache'; if (!file_exists($cache_dir)) { wp_mkdir_p($cache_dir); // Add .htaccess for security $htaccess = $cache_dir . '/.htaccess'; if (!file_exists($htaccess)) { file_put_contents($htaccess, 'Deny from all'); } // Add index.php for additional security $index = $cache_dir . '/index.php'; if (!file_exists($index)) { file_put_contents($index, 'admin_notice_multilingual_missing(); } /** * Admin notice - No multilingual plugin (Polylang or WPML) */ public function admin_notice_multilingual_missing() { // Don't show this notice on our own plugin page (the onboarding screen handles it) $screen = get_current_screen(); if ($screen && strpos($screen->id, 'ai-translator-for-elementor') !== false) { return; } ?>

:

Polylang WPML

prefix . 'aitrfoel_bulk_jobs'; $pending_job = $wpdb->get_row(" SELECT * FROM {$table_name} WHERE status = 'pending' ORDER BY created_at ASC LIMIT 1 "); if (!$pending_job) { return false; } // Decode job data $job_data = json_decode($pending_job->job_data, true); if (!$job_data) { error_log("EAT Error: Could not decode job data for {$pending_job->job_id}"); return false; } // Extract parameters $page_ids = $job_data['page_ids'] ?? []; $target_languages = $job_data['target_languages'] ?? []; $source_language = $job_data['source_language'] ?? 'auto'; // Note: Background processing was removed // Jobs are now processed directly via AJAX handlers // This method is kept for compatibility but does nothing return true; } /** * Render welcome popup (shown once after activation) */ public function render_welcome_popup() { // Only show on our plugin pages $screen = get_current_screen(); if (!$screen || strpos($screen->id, 'ai-translator-for-elementor-and-polylang') === false) { return; } // Allow forcing welcome popup with URL parameter for testing: ?show_welcome=1 $force_show = isset($_GET['show_welcome']) && $_GET['show_welcome'] === '1'; // Legacy popup is superseded by the onboarding wizard. Keep it only as an // explicit test surface for ?show_welcome=1. if (!$force_show) { return; } // Check if we should show the popup $show_popup = get_option('aitrfoel_show_welcome_popup', 'no'); if ($show_popup !== 'yes' && !$force_show) { return; } // URLs $polylang_url = admin_url('admin.php?page=mlang'); $wpml_url = admin_url('admin.php?page=sitepress-multilingual-cms/menu/languages.php'); $polylang_install_url = admin_url('plugin-install.php?s=polylang&tab=search&type=term'); $docs_url = 'https://meeptech.com/products/ai-translator-for-elementor-polylang/documentation'; // Detect multilingual plugin status $multilingual = class_exists('AITRFOEL_Multilingual_Manager') ? AITRFOEL_Multilingual_Manager::get_instance() : null; $has_multilingual = $multilingual && $multilingual->has_multilingual_plugin(); $has_languages = $multilingual && method_exists($multilingual, 'has_languages_configured') && $multilingual->has_languages_configured(); $language_count = $multilingual && method_exists($multilingual, 'get_language_count') ? $multilingual->get_language_count() : 0; $detected_plugin = $multilingual ? $multilingual->get_detected_plugin() : null; $plugin_display_name = $multilingual && $has_multilingual ? $multilingual->get_plugin_display_name() : null; ?>

1 Polylang WPML Polylang WPML → 2 → 3
.aitrfoel-language-switcher { font-family: inherit; --aitrfoel-text-color: currentColor; --aitrfoel-text-hover-color: currentColor; --aitrfoel-active-bg: #0073aa; --aitrfoel-active-color: #fff; --aitrfoel-hover-bg: rgba(0,0,0,0.06); --aitrfoel-border-color: rgba(0,0,0,0.15); --aitrfoel-bg: transparent; --aitrfoel-dropdown-bg: #fff; --aitrfoel-dropdown-shadow: 0 4px 16px rgba(0,0,0,0.12); } .aitrfoel-switcher-list { list-style: none; padding: 0; margin: 0; display: flex; flex-wrap: wrap; gap: 4px; } .aitrfoel-switcher-list .aitrfoel-switcher-item a { display: flex; align-items: center; padding: 6px 12px; text-decoration: none; color: var(--aitrfoel-text-color); border-radius: 6px; transition: background-color 0.2s; font-size: 14px; } .aitrfoel-switcher-list .aitrfoel-switcher-item a:hover { background-color: var(--aitrfoel-hover-bg); } .aitrfoel-switcher-list .aitrfoel-switcher-item.active a { background-color: var(--aitrfoel-active-bg); color: var(--aitrfoel-active-color); font-weight: 600; } .aitrfoel-switcher-flags { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; } .aitrfoel-flag-link { display: inline-flex; align-items: center; padding: 4px; border-radius: 4px; transition: transform 0.15s; text-decoration: none; } .aitrfoel-flag-link:hover { transform: scale(1.15); } .aitrfoel-flag-link.active { box-shadow: 0 0 0 2px var(--aitrfoel-active-bg); } .aitrfoel-flag-link img { display: block; border-radius: 2px; } .aitrfoel-switcher-flags-text { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } .aitrfoel-flag-text-link { display: inline-flex; align-items: center; padding: 4px 10px; text-decoration: none; color: var(--aitrfoel-text-color); border-radius: 6px; font-size: 13px; font-weight: 500; transition: background-color 0.2s; } .aitrfoel-flag-text-link:hover { background-color: var(--aitrfoel-hover-bg); } .aitrfoel-flag-text-link.active { background-color: var(--aitrfoel-active-bg); color: var(--aitrfoel-active-color); } .aitrfoel-switcher-dropdown-wrapper { display: inline-block; position: relative; } .aitrfoel-dropdown-trigger { display: inline-flex; align-items: center; gap: 4px; padding: 6px 12px; border: 1px solid var(--aitrfoel-border-color); border-radius: 6px; font-size: 14px; font-family: inherit; background: var(--aitrfoel-bg); color: var(--aitrfoel-text-color); cursor: pointer; min-width: 120px; } .aitrfoel-dropdown-trigger:hover { border-color: var(--aitrfoel-active-bg); } .aitrfoel-dropdown-arrow { margin-left: auto; transition: transform 0.2s; flex-shrink: 0; } .aitrfoel-dropdown-trigger[aria-expanded="true"] .aitrfoel-dropdown-arrow { transform: rotate(180deg); } .aitrfoel-dropdown-list { position: absolute; top: 100%; left: 0; z-index: 9999; min-width: 100%; margin: 4px 0 0; padding: 4px 0; list-style: none; background: var(--aitrfoel-dropdown-bg); border: 1px solid var(--aitrfoel-border-color); border-radius: 6px; box-shadow: var(--aitrfoel-dropdown-shadow); } .aitrfoel-dropdown-item a { display: flex; align-items: center; padding: 6px 12px; text-decoration: none; color: var(--aitrfoel-text-color); font-size: 14px; white-space: nowrap; transition: background-color 0.15s; } .aitrfoel-dropdown-item a:hover { background-color: var(--aitrfoel-hover-bg); } .aitrfoel-dropdown-item.active a { font-weight: 600; color: var(--aitrfoel-active-bg); } ' . (!empty(trim($custom_css)) ? "\n/* Custom CSS */\n" . $custom_css : '') . ' '; } ob_start(); echo $css; if ($style === 'list') { echo ''; } elseif ($style === 'flags') { echo '
'; foreach ($switcher_data as $lang) { $active_class = !empty($lang['is_current']) ? ' active' : ''; $flag_html = class_exists('AITRFOEL_Language_Mapper') ? AITRFOEL_Language_Mapper::get_flag_html($lang['code'], 24, '') : esc_html(strtoupper($lang['code'])); printf('%s', esc_url($lang['url'] ?? '#'), esc_attr($active_class), esc_attr($lang['name'] ?? ''), $flag_html); } echo '
'; } elseif ($style === 'flags-text') { echo '
'; foreach ($switcher_data as $lang) { $active_class = !empty($lang['is_current']) ? ' active' : ''; $flag_html = class_exists('AITRFOEL_Language_Mapper') ? AITRFOEL_Language_Mapper::get_flag_html($lang['code'], 20, 'margin-right: 4px;') : ''; printf('%s%s', esc_url($lang['url'] ?? '#'), esc_attr($active_class), esc_attr($lang['name'] ?? ''), $flag_html, esc_html(strtoupper($lang['code']))); } echo '
'; } else { // Custom dropdown with flag images $current_lang = null; foreach ($switcher_data as $lang) { if (!empty($lang['is_current'])) { $current_lang = $lang; break; } } if (!$current_lang) $current_lang = $switcher_data[0]; $current_flag = $show_flags && class_exists('AITRFOEL_Language_Mapper') ? AITRFOEL_Language_Mapper::get_flag_html($current_lang['code'], 20, 'margin-right: 6px;') : ''; $current_label = $show_names ? esc_html($current_lang['native_name'] ?? $current_lang['name'] ?? strtoupper($current_lang['code'])) : esc_html(strtoupper($current_lang['code'])); echo '
'; echo ''; echo ''; echo '
'; if (class_exists('AITRFOEL_Builtin_Integration')) { AITRFOEL_Builtin_Integration::get_instance()->register_switcher_javascript(); } } return ob_get_clean(); }