can_use_premium_code() ? esc_html__('Free Templates', 'king-addons') : esc_html__('Templates Pro', 'king-addons')), 'manage_options', 'king-addons-templates', [Templates::instance(), 'render_template_catalog_page'], KING_ADDONS_URL . (!king_addons_freemius()->can_use_premium_code() ? 'includes/admin/img/icon-for-menu-templates.svg' : 'includes/admin/img/icon-for-menu-templates-v2.svg'), 54.2 ); } // Check Header & Footer Builder extension toggle $header_footer_enabled = !isset($options['ext_header-footer-builder']) || $options['ext_header-footer-builder'] === 'enabled'; if ($header_footer_enabled && (defined('KING_ADDONS_EXT_HEADER_FOOTER_BUILDER') ? KING_ADDONS_EXT_HEADER_FOOTER_BUILDER : true) && class_exists('King_Addons\Header_Footer_Builder')) { self::showHeaderFooterBuilder(); } // Check Popup Builder extension toggle $popup_builder_enabled = !isset($options['ext_popup-builder']) || $options['ext_popup-builder'] === 'enabled'; if ($popup_builder_enabled && (defined('KING_ADDONS_EXT_POPUP_BUILDER') ? KING_ADDONS_EXT_POPUP_BUILDER : true) && class_exists('King_Addons\Popup_Builder')) { self::showPopupBuilder(); } // Check WooCommerce Builder extension toggle $woo_builder_enabled = !isset($options['ext_woo-builder']) || $options['ext_woo-builder'] === 'enabled'; if ( $woo_builder_enabled && (defined('KING_ADDONS_EXT_WOO_BUILDER') ? KING_ADDONS_EXT_WOO_BUILDER : true) && class_exists('WooCommerce') && function_exists('WC') ) { $this->showWooBuilder(); } $menu['54.8'] = array( '', 'read', 'separator-king-addons-bottom', '', 'wp-menu-separator elementor' ); } /** * Enforce submenu order for King Addons: * 1) Dashboard * 2) Settings * 3) AI Settings * 4) Account (Freemius) * 5) Contact Us (Freemius) * 6) Everything else alphabetically */ public function reorderKingAddonsSubmenu(): void { global $submenu; if (!is_array($submenu) || empty($submenu['king-addons']) || !is_array($submenu['king-addons'])) { return; } $priorityBySlug = [ 'king-addons' => 0, 'king-addons-settings' => 1, 'king-addons-ai-settings' => 2, 'king-addons-account' => 3, 'king-addons-contact' => 4, ]; $items = $submenu['king-addons']; usort($items, static function ($a, $b) use ($priorityBySlug): int { $aSlug = isset($a[2]) ? (string) $a[2] : ''; $bSlug = isset($b[2]) ? (string) $b[2] : ''; $aPriority = array_key_exists($aSlug, $priorityBySlug) ? $priorityBySlug[$aSlug] : null; $bPriority = array_key_exists($bSlug, $priorityBySlug) ? $priorityBySlug[$bSlug] : null; if ($aPriority !== null || $bPriority !== null) { $aPriority = $aPriority ?? 9999; $bPriority = $bPriority ?? 9999; if ($aPriority !== $bPriority) { return $aPriority <=> $bPriority; } } $aLabel = isset($a[0]) ? wp_strip_all_tags((string) $a[0]) : ''; $bLabel = isset($b[0]) ? wp_strip_all_tags((string) $b[0]) : ''; $cmp = strcasecmp($aLabel, $bLabel); if ($cmp !== 0) { return $cmp; } return strcasecmp($aSlug, $bSlug); }); $submenu['king-addons'] = $items; } function addUpgradeMenu(): void { // Don't add menu if Freemius is showing opt-in/activation $fs = king_addons_freemius(); // Check if we're on any Freemius-related page if ( isset($_GET['fs_action']) || $fs->is_activation_mode() || (!$fs->is_registered() && !$fs->is_anonymous() && !$fs->is_tracking_prohibited()) ) { return; } // Add Upgrade submenu under King Addons (only if premium is not active) if (!$fs->can_use_premium_code()) { add_submenu_page( 'king-addons', esc_html__('Upgrade Now', 'king-addons'), esc_html__('Upgrade Now', 'king-addons'), 'manage_options', 'https://kingaddons.com/pricing/?utm_source=kng-top-menu&utm_medium=plugin&utm_campaign=kng', '' ); } } function showPopupBuilder(): void { add_menu_page( 'Popup Builder', 'Popup Builder', 'manage_options', 'king-addons-popup-builder', [Popup_Builder::instance(), 'renderPopupBuilder'], KING_ADDONS_URL . 'includes/admin/img/icon-for-popup-builder.svg', 54.4 ); } /** * Register WooCommerce Builder menu item. * * @return void */ public function showWooBuilder(): void { add_menu_page( esc_html__('WooCommerce Builder', 'king-addons'), esc_html__('WooCommerce Builder', 'king-addons'), 'manage_options', 'king-addons-woo-builder', [$this, 'renderWooBuilderPage'], 'dashicons-cart', 54.5 ); } /** * Render WooCommerce Builder admin page. * * @return void */ public function renderWooBuilderPage(): void { if (!current_user_can('manage_options')) { return; } require_once KING_ADDONS_PATH . 'includes/admin/layouts/woo-builder-page.php'; } function showHeaderFooterBuilder(): void { $menu_slug = 'king-addons-el-hf'; $callback = [\King_Addons\Header_Footer_Builder::instance(), 'renderAdminPage']; // Add Main Menu (new unified UI) add_menu_page( esc_html__('Elementor Header & Footer Builder', 'king-addons'), esc_html__('Header & Footer', 'king-addons'), 'manage_options', $menu_slug, $callback, KING_ADDONS_URL . 'includes/admin/img/icon-for-header-footer-builder.svg', 54.3 ); // Ensure the first submenu item is labeled "Templates" add_submenu_page( $menu_slug, esc_html__('Templates', 'king-addons'), esc_html__('Templates', 'king-addons'), 'manage_options', $menu_slug, $callback ); // Display Settings submenu (same page, different tab) add_submenu_page( $menu_slug, esc_html__('Display Settings', 'king-addons'), esc_html__('Display Settings', 'king-addons'), 'manage_options', $menu_slug . '&tab=settings', $callback ); } function showSettingsPage(): void { if (!current_user_can('manage_options')) { return; } require_once(KING_ADDONS_PATH . 'includes/admin/layouts/settings-page.php'); self::enqueueSettingsAssets(); } function showDashboardV3(): void { if (!current_user_can('manage_options')) { return; } require_once(KING_ADDONS_PATH . 'includes/admin/layouts/dashboard-v3/dashboard-v3.php'); } /** * Handle AJAX request to install/activate Trinity Backup plugin. * * @return void */ public function handleInstallTrinityBackup(): void { check_ajax_referer('king_addons_install_plugin', 'nonce'); if (!current_user_can('install_plugins')) { wp_send_json_error(esc_html__('Permission denied.', 'king-addons')); } $plugin_action = isset($_POST['plugin_action']) ? sanitize_text_field($_POST['plugin_action']) : ''; $plugin_slug = 'trinity-backup'; $plugin_file = 'trinity-backup/trinity-backup.php'; $plugin_path = WP_PLUGIN_DIR . '/' . $plugin_file; // If action is activate and plugin exists, just activate it if ($plugin_action === 'activate') { if (file_exists($plugin_path)) { $result = activate_plugin($plugin_file); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } wp_send_json_success(['message' => esc_html__('Plugin activated!', 'king-addons')]); } else { wp_send_json_error(esc_html__('Plugin not found.', 'king-addons')); } return; } // Install plugin require_once ABSPATH . 'wp-admin/includes/plugin-install.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php'; require_once ABSPATH . 'wp-admin/includes/class-wp-ajax-upgrader-skin.php'; // Get plugin info from WordPress.org $api = plugins_api('plugin_information', [ 'slug' => $plugin_slug, 'fields' => [ 'short_description' => false, 'sections' => false, 'requires' => false, 'rating' => false, 'ratings' => false, 'downloaded' => false, 'last_updated' => false, 'added' => false, 'tags' => false, 'compatibility' => false, 'homepage' => false, 'donate_link' => false, ], ]); if (is_wp_error($api)) { wp_send_json_error($api->get_error_message()); } $skin = new \WP_Ajax_Upgrader_Skin(); $upgrader = new \Plugin_Upgrader($skin); $result = $upgrader->install($api->download_link); if (is_wp_error($result)) { wp_send_json_error($result->get_error_message()); } if ($result === false) { wp_send_json_error(esc_html__('Installation failed.', 'king-addons')); } // Activate the plugin after installation $activate_result = activate_plugin($plugin_file); if (is_wp_error($activate_result)) { // Installed but not activated wp_send_json_success(['message' => esc_html__('Plugin installed! Please activate manually.', 'king-addons')]); } wp_send_json_success(['message' => esc_html__('Plugin installed and activated!', 'king-addons')]); } /** * Render Wishlist admin page. * * @return void */ public function renderWishlistPage(): void { if (!current_user_can('manage_options')) { return; } $options = get_option('king_addons_options', []); $wishlist_enabled = (!isset($options['ext_wishlist']) || $options['ext_wishlist'] === 'enabled') && (defined('KING_ADDONS_EXT_WISHLIST') ? KING_ADDONS_EXT_WISHLIST : true); if (!$wishlist_enabled || !class_exists(Wishlist_Settings::class)) { return; } self::enqueueSettingsAssets(); require_once KING_ADDONS_PATH . 'includes/admin/layouts/wishlist-page.php'; } /** * Render Wishlist analytics page. * * @return void */ public function renderWishlistAnalyticsPage(): void { if (!current_user_can('manage_options')) { return; } $options = get_option('king_addons_options', []); $wishlist_enabled = (!isset($options['ext_wishlist']) || $options['ext_wishlist'] === 'enabled') && (defined('KING_ADDONS_EXT_WISHLIST') ? KING_ADDONS_EXT_WISHLIST : true); if (!$wishlist_enabled || !class_exists(Wishlist_Settings::class)) { return; } self::enqueueSettingsAssets(); require_once KING_ADDONS_PATH . 'includes/admin/layouts/wishlist-analytics.php'; } function createSettings(): void { // Register a new setting for "king-addons" page. register_setting('king_addons', 'king_addons_options'); // Register a new section in the "king-addons" page. add_settings_section( 'king_addons_section_widgets', '', [$this, 'king_addons_section_widgets_callback'], 'king-addons' ); // Register a new section in the "king-addons" page. add_settings_section( 'king_addons_section_features', '', [$this, 'king_addons_section_features_callback'], 'king-addons' ); foreach (ModulesMap::getModulesMapArray()['widgets'] as $widget_id => $widget_array) { // Hide widgets hard-disabled via constants (QA rollout). $widget_constant = 'KING_ADDONS_WGT_' . strtoupper(str_replace('-', '_', (string) $widget_id)); if (defined($widget_constant) && constant($widget_constant) === false) { continue; } add_settings_field( $widget_id, $widget_array['title'], '', 'king-addons', 'king_addons_section_widgets', array( 'label_for' => $widget_id, 'description' => $widget_array['description'], 'docs_link' => $widget_array['docs-link'], 'demo_link' => $widget_array['demo-link'], 'class' => 'kng-tr kng-tr-' . $widget_id . (!empty($widget_array['has-pro']) ? ' kng-tr-freemium' : '') ) ); } foreach (ModulesMap::getModulesMapArray()['features'] as $feature_id => $feature_array) { // Hide features hard-disabled via constants (QA rollout). $feature_constant = 'KING_ADDONS_FEAT_' . strtoupper(str_replace('-', '_', (string) $feature_id)); if (defined($feature_constant) && constant($feature_constant) === false) { continue; } add_settings_field( $feature_id, $feature_array['title'], '', 'king-addons', 'king_addons_section_features', array( 'label_for' => $feature_id, 'description' => $feature_array['description'], 'docs_link' => $feature_array['docs-link'], 'demo_link' => $feature_array['demo-link'], 'class' => 'kng-tr kng-tr-' . $feature_id ) ); } } /** * Register wishlist settings group. * * @return void */ public function createWishlistSettings(): void { if (defined('KING_ADDONS_EXT_WISHLIST') && KING_ADDONS_EXT_WISHLIST === false) { return; } if (!class_exists(Wishlist_Settings::class)) { return; } register_setting( 'king_addons_wishlist', 'king_addons_wishlist_settings', [ 'type' => 'array', 'sanitize_callback' => [$this, 'sanitizeWishlistSettings'], 'default' => Wishlist_Settings::defaults(), ] ); } /** * Sanitize wishlist settings payload. * * @param array $settings Raw settings. * @return array Sanitized settings. */ public function sanitizeWishlistSettings(array $settings): array { if (!class_exists(Wishlist_Settings::class)) { return []; } $defaults = Wishlist_Settings::defaults(); $sanitized = [ 'enabled' => !empty($settings['enabled']), 'wishlist_page_id' => absint($settings['wishlist_page_id'] ?? 0), 'allow_guests' => !empty($settings['allow_guests']), 'guest_block_text' => sanitize_text_field($settings['guest_block_text'] ?? $defaults['guest_block_text']), 'button_add_text' => sanitize_text_field($settings['button_add_text'] ?? $defaults['button_add_text']), 'button_added_text' => sanitize_text_field($settings['button_added_text'] ?? $defaults['button_added_text']), 'button_display_mode' => in_array($settings['button_display_mode'] ?? 'icon_text', ['icon', 'icon_text'], true) ? $settings['button_display_mode'] : $defaults['button_display_mode'], 'button_position' => in_array($settings['button_position'] ?? 'after_add_to_cart', ['before_add_to_cart', 'after_add_to_cart'], true) ? $settings['button_position'] : $defaults['button_position'], 'show_in_archives' => !empty($settings['show_in_archives']), 'wishlist_columns' => array_values( array_intersect( (array) ($settings['wishlist_columns'] ?? []), ['image', 'title', 'price', 'stock', 'notes', 'add_to_cart', 'remove'] ) ), 'cache_enabled' => !empty($settings['cache_enabled']), 'cache_ttl' => max(0, absint($settings['cache_ttl'] ?? 0)), 'icon_choice' => sanitize_text_field($settings['icon_choice'] ?? $defaults['icon_choice']), ]; return wp_parse_args($sanitized, $defaults); } function king_addons_section_widgets_callback($args): void { ?>

can_use_premium_code()) { wp_enqueue_script('jquery'); wp_add_inline_script('jquery', " jQuery(document).ready(function($) { $('#adminmenu #toplevel_page_king-addons a[href=\"https://kingaddons.com/pricing/?utm_source=kng-top-menu&utm_medium=plugin&utm_campaign=kng\"]').attr('target', '_blank'); }); "); } } public function enqueueGlobalAdminStyles(): void { wp_enqueue_style('king-addons-hide-spam', KING_ADDONS_URL . 'includes/admin/css/hide-spam-notifications.css', [], KING_ADDONS_VERSION); } function enqueueSettingsAssets(): void { wp_enqueue_style('king-addons-settings', KING_ADDONS_URL . 'includes/admin/css/settings.css', '', KING_ADDONS_VERSION); wp_enqueue_style('wp-color-picker'); wp_enqueue_script('jquery'); wp_enqueue_script('wp-color-picker'); wp_enqueue_script(KING_ADDONS_ASSETS_UNIQUE_KEY . '-wpcolorpicker-wpcolorpicker'); wp_enqueue_script('king-addons-settings', KING_ADDONS_URL . 'includes/admin/js/settings.js', '', KING_ADDONS_VERSION); } /** * Registers AI Settings using the WordPress Settings API. * * @return void */ public function createAiSettings(): void { register_setting( 'king_addons_ai', 'king_addons_ai_options', [$this, 'sanitizeAiSettings'] ); add_settings_section( 'king_addons_ai_openai_section', esc_html__('OpenAI API Settings', 'king-addons'), [$this, 'renderAiOpenaiSection'], 'king-addons-ai-settings' ); add_settings_field( 'openai_api_key', esc_html__('OpenAI API Key', 'king-addons'), [$this, 'renderAiApiKeyField'], 'king-addons-ai-settings', 'king_addons_ai_openai_section' ); add_settings_field( 'openai_model', esc_html__('OpenAI Model (for text generation)', 'king-addons'), [$this, 'renderAiModelField'], 'king-addons-ai-settings', 'king_addons_ai_openai_section' ); add_settings_field( 'openai_vision_model', esc_html__('OpenAI Model (for image recognition)', 'king-addons'), [$this, 'renderAiVisionModelField'], 'king-addons-ai-settings', 'king_addons_ai_openai_section' ); // Add image model selector field add_settings_field( 'openai_image_model', esc_html__('OpenAI Image Model (for image generation)', 'king-addons'), [$this, 'renderAiImageModelField'], 'king-addons-ai-settings', 'king_addons_ai_openai_section' ); // Global content language add_settings_field( 'content_language_custom_enable', esc_html__('Content Language', 'king-addons'), [$this, 'renderAiContentLanguageField'], 'king-addons-ai-settings', 'king_addons_ai_openai_section' ); // Add Editor Integration section and field add_settings_section( 'king_addons_ai_editor_section', esc_html__('Editor Integration', 'king-addons'), [$this, 'renderAiEditorSection'], 'king-addons-ai-settings' ); add_settings_field( 'enable_ai_buttons', esc_html__('AI Text Editing Buttons', 'king-addons'), [$this, 'renderAiEnableButtonsField'], 'king-addons-ai-settings', 'king_addons_ai_editor_section' ); add_settings_field( 'enable_ai_image_generation_button', esc_html__('AI Image Generation Button', 'king-addons'), [$this, 'renderAiImageGenerationField'], 'king-addons-ai-settings', 'king_addons_ai_editor_section' ); // Add Alt Text Settings section add_settings_section( 'king_addons_ai_alt_text_section', esc_html__('Alt Text Settings', 'king-addons'), [$this, 'renderAiAltTextSection'], 'king-addons-ai-settings' ); add_settings_field( 'enable_ai_alt_text_button', esc_html__('AI Alt Text Button', 'king-addons'), [$this, 'renderAiAltTextButtonField'], 'king-addons-ai-settings', 'king_addons_ai_alt_text_section' ); add_settings_field( 'enable_ai_alt_text_auto_generation', ((king_addons_freemius()->can_use_premium_code()) ? esc_html__('Auto Generate Alt Text', 'king-addons') : esc_html__('Auto Generate Alt Text (PRO feature)', 'king-addons')), [$this, 'renderAiAltTextAutoGenerationField'], 'king-addons-ai-settings', 'king_addons_ai_alt_text_section' ); add_settings_field( 'ai_alt_text_generation_interval', esc_html__('Alt Text Generation Interval', 'king-addons'), [$this, 'renderAiAltTextIntervalField'], 'king-addons-ai-settings', 'king_addons_ai_alt_text_section' ); // Add Image Detail Level field add_settings_field( 'ai_alt_text_image_detail_level', esc_html__('Image Detail Level', 'king-addons'), [$this, 'renderAiAltTextImageDetailLevelField'], 'king-addons-ai-settings', 'king_addons_ai_alt_text_section' ); // Add Translation Settings section add_settings_section( 'king_addons_ai_translation_section', esc_html__('Translation Settings', 'king-addons'), [$this, 'renderAiTranslationSection'], 'king-addons-ai-settings' ); add_settings_field( 'enable_ai_page_translator', esc_html__('AI Page Translator Button', 'king-addons'), [$this, 'renderAiPageTranslatorField'], 'king-addons-ai-settings', 'king_addons_ai_translation_section' ); // Add Usage Quota Settings section and field add_settings_section( 'king_addons_ai_quota_section', esc_html__('Usage Quota Settings', 'king-addons'), [$this, 'renderAiQuotaSection'], 'king-addons-ai-settings' ); add_settings_field( 'daily_token_limit', esc_html__('Daily Token Limit', 'king-addons'), [$this, 'renderAiDailyLimitField'], 'king-addons-ai-settings', 'king_addons_ai_quota_section' ); // Add Usage Statistics section (read-only) add_settings_section( 'king_addons_ai_stats_section', esc_html__('Usage Statistics', 'king-addons'), [$this, 'renderAiStatsSection'], 'king-addons-ai-settings' ); // Clear models cache when options updated. add_action('update_option_king_addons_ai_options', [$this, 'clearAiModelsCache']); // AJAX handler for refreshing models. add_action('wp_ajax_king_addons_ai_refresh_models', [$this, 'handleAiRefreshModels']); // AJAX handler for generating text via AI add_action('wp_ajax_king_addons_ai_generate_text', [$this, 'handleAiGenerateText']); // AJAX handler to change text using AI based on user prompt and original text. add_action('wp_ajax_king_addons_ai_change_text', [$this, 'handleAiChangeText']); // AJAX handler to check token usage limits add_action('wp_ajax_king_addons_ai_check_tokens', [$this, 'handleAiCheckTokens']); // AJAX handler to check image generation limits add_action('wp_ajax_king_addons_ai_image_check_limits', [$this, 'handleAiImageCheckLimits']); // THIRD_EDIT: Register AJAX handler for AI image generation add_action('wp_ajax_king_addons_ai_generate_image', [$this, 'handleAiGenerateImage']); // AJAX handler for AI page translation add_action('wp_ajax_king_addons_ai_translate_text', [$this, 'handleAiTranslateText']); } /** * Sanitizes AI Settings options. * * @param array $input Raw input array. * @return array Sanitized input. */ public function sanitizeAiSettings(array $input): array { $sanitized = []; $sanitized['openai_api_key'] = isset($input['openai_api_key']) ? sanitize_text_field($input['openai_api_key']) : ''; $sanitized['openai_model'] = isset($input['openai_model']) ? sanitize_text_field($input['openai_model']) : ''; $sanitized['openai_vision_model'] = isset($input['openai_vision_model']) ? sanitize_text_field($input['openai_vision_model']) : ($sanitized['openai_model'] ?: 'gpt-4o-mini'); $sanitized['openai_image_model'] = isset($input['openai_image_model']) ? sanitize_text_field($input['openai_image_model']) : 'gpt-image-1'; // Sanitize Daily Token Limit. if (isset($input['daily_token_limit'])) { $daily_limit = absint($input['daily_token_limit']); $sanitized['daily_token_limit'] = max(0, $daily_limit); // Ensure non-negative } else { $sanitized['daily_token_limit'] = 1000000; // Default to 1 million tokens if not set } // Sanitize Enable AI Buttons option. $sanitized['enable_ai_buttons'] = !empty($input['enable_ai_buttons']); // Sanitize Enable AI Image Generation button option. $sanitized['enable_ai_image_generation_button'] = !empty($input['enable_ai_image_generation_button']); // Sanitize Enable AI Alt Text Button option. $sanitized['enable_ai_alt_text_button'] = !empty($input['enable_ai_alt_text_button']); // Sanitize Enable AI Alt Text Auto Generation option. $sanitized['enable_ai_alt_text_auto_generation'] = !empty($input['enable_ai_alt_text_auto_generation']); // Sanitize AI Alt Text Generation Interval. if (isset($input['ai_alt_text_generation_interval'])) { $interval = absint($input['ai_alt_text_generation_interval']); $sanitized['ai_alt_text_generation_interval'] = max(10, min(3600, $interval)); // Between 10 seconds and 1 hour } else { $sanitized['ai_alt_text_generation_interval'] = 20; // Default to 20 seconds } // Sanitize Image Detail Level $allowed_detail_levels = ['low', 'high']; $sanitized['ai_alt_text_image_detail_level'] = in_array(($input['ai_alt_text_image_detail_level'] ?? 'low'), $allowed_detail_levels, true) ? $input['ai_alt_text_image_detail_level'] : 'low'; // Sanitize Auto Tagging settings. $sanitized['auto_tagging_max_tags'] = isset($input['auto_tagging_max_tags']) ? max(1, min(20, absint($input['auto_tagging_max_tags']))) : 5; $sanitized['auto_tagging_confidence_threshold'] = isset($input['auto_tagging_confidence_threshold']) ? max(0.0, min(1.0, (float) $input['auto_tagging_confidence_threshold'])) : 0.75; $sanitized['auto_tagging_stop_words'] = isset($input['auto_tagging_stop_words']) ? sanitize_text_field($input['auto_tagging_stop_words']) : ''; // Sanitize Enable AI Page Translator option $sanitized['enable_ai_page_translator'] = !empty($input['enable_ai_page_translator']); // Validation / feedback (Settings API notice). $ai_requires_key = ( !empty($sanitized['enable_ai_buttons']) || !empty($sanitized['enable_ai_image_generation_button']) || !empty($sanitized['enable_ai_alt_text_button']) || !empty($sanitized['enable_ai_page_translator']) ); if ($ai_requires_key && empty($sanitized['openai_api_key'])) { add_settings_error( 'king_addons_ai', 'king_addons_ai_missing_api_key', esc_html__('OpenAI API Key is required to enable AI features.', 'king-addons'), 'error' ); } return $sanitized; } /** * Clears cached AI models list. * * @return void */ public function clearAiModelsCache(): void { delete_transient('king_addons_ai_models_cache'); } /** * Renders the AI Settings page content. * * @return void */ public function showAiSettingsPage(): void { if (!current_user_can('manage_options')) { return; } require_once KING_ADDONS_PATH . 'includes/admin/layouts/ai-settings-page.php'; $this->enqueueAiSettingsAssets(); } /** * Enqueues scripts and styles for the AI Settings page. * * @return void */ public function enqueueAiSettingsAssets(): void { // Enqueue admin base styles first for proper theming wp_enqueue_style('king-addons-admin', KING_ADDONS_URL . 'includes/admin/css/admin.css', '', KING_ADDONS_VERSION); wp_enqueue_style( 'king-addons-ai-settings', KING_ADDONS_URL . 'includes/admin/css/ai-settings.css', ['king-addons-admin'], // Depend on admin base styles KING_ADDONS_VERSION ); wp_enqueue_script( 'king-addons-ai-settings', KING_ADDONS_URL . 'includes/admin/js/ai-settings.js', ['jquery'], KING_ADDONS_VERSION, true ); wp_localize_script( 'king-addons-ai-settings', 'KingAddonsAiSettings', [ 'ajax_url' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('king_addons_ai_refresh_models_nonce'), 'refreshing_text' => esc_html__('Refreshing...', 'king-addons'), 'refreshed_text' => esc_html__('List updated.', 'king-addons'), 'error_text' => esc_html__('Error updating list.', 'king-addons'), ] ); } /** * Renders description for OpenAI API Settings section. * * @return void */ public function renderAiOpenaiSection(): void { echo '

' . esc_html__('Enter your OpenAI API key and select the model for AI features.', 'king-addons') . '

'; } /** * Renders the OpenAI API Key input field. * * @return void */ public function renderAiApiKeyField(): void { $options = get_option('king_addons_ai_options', []); $api_key = $options['openai_api_key'] ?? ''; printf( '', esc_attr($api_key) ); echo '

'; printf( esc_html__('Get your API key from the %1$sOpenAI Platform%2$s. Saving the key will attempt to fetch the available models.', 'king-addons'), '', '' ); echo '

'; echo '
'; echo '' . esc_html__('Important:', 'king-addons') . ' '; echo esc_html__('You must top up your OpenAI account balance by at least $5 for the API to work. Free accounts are not supported.', 'king-addons'); echo '
'; echo '
'; echo '' . esc_html__('Info:', 'king-addons') . ' '; echo esc_html__('With GPT-4o-mini, a $5 balance is enough for roughly 130,000–150,000 text generations.', 'king-addons'); echo '
'; echo '
'; echo '' . esc_html__('Useful OpenAI Links:', 'king-addons') . ''; echo '
'; } /** * Renders the model selection dropdown field with refresh button. * * @return void */ public function renderAiModelField(): void { $options = get_option('king_addons_ai_options', []); $selected = $options['openai_model'] ?? ''; $models = $this->getAiAvailableModels(); printf( ''; echo ''; echo ''; echo ''; echo '

' . esc_html__('Select an available OpenAI model capable of processing text. We recommend GPT-4o-mini or GPT-4.1-nano for best results. The list of models is cached indefinitely until manually refreshed.', 'king-addons') . '

'; } /** * Renders the vision model selection dropdown field. * * @return void */ public function renderAiVisionModelField(): void { $options = get_option('king_addons_ai_options', []); $selected = $options['openai_vision_model'] ?? ($options['openai_model'] ?? 'gpt-4o-mini'); $models = $this->getAiAvailableModels(); printf( ''; echo '

' . esc_html__('Select the default model used for AI image analysis tasks (Alt Text Generator and related vision requests).', 'king-addons') . '

'; } /** * Fetches the list of OpenAI models via API. * * @param string|null $api_key API key to use. * @return array|\WP_Error Model list or error. */ private function fetchAiOpenaiModels(?string $api_key) { if (empty($api_key)) { return new \WP_Error('missing_key', esc_html__('API key is required to fetch models.', 'king-addons')); } $endpoint = 'https://api.openai.com/v1/models'; $response = wp_remote_get($endpoint, [ 'headers' => ['Authorization' => 'Bearer ' . $api_key], 'timeout' => 20, ]); if (is_wp_error($response)) { return $response; } $code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); $data = json_decode($body, true); if ($code !== 200 || empty($data['data']) || !is_array($data['data'])) { $message = $data['error']['message'] ?? esc_html__('Invalid response from API.', 'king-addons'); return new \WP_Error('api_error', $message, ['status' => $code]); } $list = []; foreach ($data['data'] as $model) { if (isset($model['id'])) { $list[$model['id']] = $model['id']; } } ksort($list); if (empty($list)) { return new \WP_Error('no_models', esc_html__('No models found via API.', 'king-addons')); } return $list; } /** * Retrieves available models, using cache if possible. * * @return array Model list. */ private function getAiAvailableModels(): array { $cached = get_transient('king_addons_ai_models_cache'); if (false !== $cached && is_array($cached)) { return $cached; } $options = get_option('king_addons_ai_options', []); $api_key = $options['openai_api_key'] ?? null; $fetched = $this->fetchAiOpenaiModels($api_key); if (!is_wp_error($fetched)) { set_transient('king_addons_ai_models_cache', $fetched, 0); return $fetched; } return ['gpt-4o-mini' => 'GPT-4o-mini', 'gpt-4.1-nano' => 'GPT-4.1-nano']; } /** * Handles AJAX request to refresh model list. * * @return void */ public function handleAiRefreshModels(): void { check_ajax_referer('king_addons_ai_refresh_models_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => esc_html__('Permission denied.', 'king-addons')], 403); } $options = get_option('king_addons_ai_options', []); $api_key = $options['openai_api_key'] ?? null; if (empty($api_key)) { wp_send_json_error(['message' => esc_html__('API key is not set.', 'king-addons')], 400); } $this->clearAiModelsCache(); $models = $this->fetchAiOpenaiModels($api_key); if (is_wp_error($models)) { wp_send_json_error(['message' => $models->get_error_message()], 500); } if (empty($models)) { wp_send_json_error(['message' => esc_html__('No models returned by API.', 'king-addons')], 500); } set_transient('king_addons_ai_models_cache', $models, 0); wp_send_json_success(['models' => $models]); } /** * AJAX handler to generate text using OpenAI. * * @return void */ public function handleAiGenerateText(): void { check_ajax_referer('king_addons_ai_generate_nonce', 'nonce'); if (!current_user_can('manage_options')) { wp_send_json_error(['message' => esc_html__('Permission denied.', 'king-addons')], 403); } $field_name = sanitize_text_field($_POST['field_name'] ?? ''); // Accept 'prompt' parameter (new) but fall back to 'value' parameter (old) for backwards compatibility $prompt = isset($_POST['prompt']) ? sanitize_textarea_field($_POST['prompt']) : sanitize_textarea_field($_POST['value'] ?? ''); // Get editor type if provided $editor_type = sanitize_text_field($_POST['editor_type'] ?? 'text'); $options = get_option('king_addons_ai_options', []); $api_key = $options['openai_api_key'] ?? ''; $model = $options['openai_model'] ?? ''; if (empty($api_key) || empty($model)) { wp_send_json_error(['message' => esc_html__('API key or model not set.', 'king-addons')], 400); } if (empty($prompt)) { wp_send_json_error(['message' => esc_html__('Please provide a prompt.', 'king-addons')], 400); } // Check daily token limit $daily_limit = isset($options['daily_token_limit']) ? intval($options['daily_token_limit']) : self::DEFAULT_DAILY_TOKEN_LIMIT; $current_usage = $this->getAiDailyUsage(); if ($daily_limit > 0 && $current_usage >= $daily_limit) { wp_send_json_error([ 'message' => esc_html__('Daily token limit reached. Please try again tomorrow or increase the limit in AI Settings.', 'king-addons') ], 429); } // System instruction based on editor type $system_instruction = 'You are a helpful content assistant. Provide concise, well-written content based on the user\'s request.'; // Enhanced instruction for WYSIWYG editor if ($editor_type === 'wysiwyg') { $system_instruction = 'You are a helpful content assistant for a rich text editor. Provide content with proper HTML formatting. Use

tags for paragraphs with appropriate spacing between them. If relevant, use other HTML formatting like , ,