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 . 'includes/admin/img/icon-for-menu-templates.svg', 58.71 ); } if (KING_ADDONS_EXT_HEADER_FOOTER_BUILDER) { self::showHeaderFooterBuilder(); } if (KING_ADDONS_EXT_POPUP_BUILDER) { self::showPopupBuilder(); } // Add AI Settings submenu under King Addons add_submenu_page( 'king-addons', esc_html__('AI Settings', 'king-addons'), esc_html__('AI Settings', 'king-addons'), 'manage_options', 'king-addons-ai-settings', [$this, 'showAiSettingsPage'] ); } 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', 58.73 ); } function showHeaderFooterBuilder(): void { $post_type = 'king-addons-el-hf'; $menu_slug = 'edit.php?post_type=' . $post_type; // Add Main Menu add_menu_page( esc_html__('Elementor Header & Footer Builder', 'king-addons'), esc_html__('Header & Footer', 'king-addons'), 'manage_options', $menu_slug, // Menu slug points to the custom post type edit screen '', // No callback function needed KING_ADDONS_URL . 'includes/admin/img/icon-for-header-footer-builder.svg', 58.72 ); // Add 'All Templates' Submenu - this will be the first submenu item add_submenu_page( $menu_slug, // Parent slug matches the main menu slug esc_html__('All Templates', 'king-addons'), esc_html__('All Templates', 'king-addons'), 'edit_posts', $menu_slug ); } function showAdminPage(): void { if (!current_user_can('manage_options')) { return; } self::enqueueAdminAssets(); require_once(KING_ADDONS_PATH . 'includes/admin/layouts/admin-page.php'); } function showSettingsPage(): void { if (!current_user_can('manage_options')) { return; } require_once(KING_ADDONS_PATH . 'includes/admin/layouts/settings-page.php'); self::enqueueSettingsAssets(); } 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) { 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) { 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 ) ); } } 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'); }); "); } } 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', 'king-addons'), [$this, 'renderAiModelField'], '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', 'king-addons'), [$this, 'renderAiImageModelField'], '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_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'] = 60; // Default to 60 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 Enable AI Page Translator option $sanitized['enable_ai_page_translator'] = ! empty($input['enable_ai_page_translator']); 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 '' . 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') . '
'; } /** * 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 , , tags for paragraphs, maintaining good spacing and readability. Do NOT use code fences (```html or ```) in your response - provide just the clean HTML.";
}
$response = wp_remote_post(
'https://api.openai.com/v1/chat/completions',
[
'headers' => [
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode([
'model' => $model,
'messages' => $messages,
'max_tokens' => 500,
'temperature' => 0.7, // Slight creativity for better content
]),
'timeout' => 30,
]
);
if (is_wp_error($response)) {
wp_send_json_error(['message' => $response->get_error_message()], 500);
}
$code = wp_remote_retrieve_response_code($response);
$data = json_decode(wp_remote_retrieve_body($response), true);
if ($code !== 200 || empty($data['choices'][0]['message']['content'])) {
$error_msg = $data['error']['message'] ?? esc_html__('AI API error.', 'king-addons');
wp_send_json_error(['message' => $error_msg], 500);
}
$generated = trim($data['choices'][0]['message']['content']);
// Clean up any code fence markers for WYSIWYG editor
if ($editor_type === 'wysiwyg') {
// Remove code fence markers (```html and ```) that might be returned by AI
$generated = preg_replace('/^```(?:html|HTML)?\s*/', '', $generated);
$generated = preg_replace('/```\s*$/', '', $generated);
}
// Update token usage statistics if present in the response
if (isset($data['usage']['total_tokens'])) {
$this->incrementAiDailyUsage(intval($data['usage']['total_tokens']));
}
wp_send_json_success([
'text' => $generated,
'usage' => [
'tokens_used' => $data['usage']['total_tokens'] ?? 0,
'daily_used' => $this->getAiDailyUsage(),
'daily_limit' => $daily_limit,
]
]);
}
/**
* AJAX handler to change text using AI based on user prompt and original text.
*
* @return void
*/
public function handleAiChangeText(): void
{
check_ajax_referer('king_addons_ai_change_nonce', 'nonce');
if (! current_user_can('manage_options')) {
wp_send_json_error(['message' => esc_html__('Permission denied.', 'king-addons')], 403);
}
$field_name = isset($_POST['field_name']) ? sanitize_text_field(wp_unslash($_POST['field_name'])) : '';
$prompt = isset($_POST['prompt']) ? sanitize_text_field(wp_unslash($_POST['prompt'])) : '';
$original = isset($_POST['original']) ? wp_kses_post(wp_unslash($_POST['original'])) : '';
$instruction_context = isset($_POST['instruction_context']) ? sanitize_textarea_field(wp_unslash($_POST['instruction_context'])) : '';
$options = get_option('king_addons_ai_options', []);
$api_key = $options['openai_api_key'] ?? '';
$model = $options['openai_model'] ?? '';
if (empty($api_key) || empty($model) || empty($prompt) || empty($original)) {
wp_send_json_error(['message' => esc_html__('Missing data for AI change.', '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);
}
// Default instruction if none provided
if (empty($instruction_context)) {
$instruction_context = 'You are an assistant that modifies text per user instruction. If the instruction mentions adding paragraphs or texts, make sure to KEEP the original text and ADD to it. If the instruction is about changing style, maintain the same information but change the tone. Return the complete modified text.';
// Add specific formatting instructions for WYSIWYG editor
$editor_type = isset($_POST['editor_type']) ? sanitize_text_field(wp_unslash($_POST['editor_type'])) : 'text';
if ($editor_type === 'wysiwyg') {
$instruction_context = 'You are an assistant that modifies HTML content for a rich text editor. IMPORTANT: If asked to add a specific number of paragraphs (like "add 2 paragraphs" or "add 3 sections"), you MUST add EXACTLY that number of distinct paragraphs or sections - no more, no less. If asked to add "some" or "several" paragraphs, add at minimum 2-3 paragraphs. Always keep the original content intact and add the new paragraphs after the original content. Use proper HTML formatting with tags for each paragraph. Ensure there is appropriate spacing between paragraphs. Preserve any existing HTML formatting (like , , , etc). DO NOT wrap your output in code fences (``` or ```html) - respond only with the actual HTML. Return the complete modified content with proper HTML structure.';
}
}
// Analyze if the prompt is likely requesting to add content rather than replace
$add_content_keywords = [
// English
'add',
'insert',
'extend',
'append',
'more',
'additional',
'expand',
// Russian
'добавь',
'вставь',
'расширь',
// Spanish
'añadir',
'agregar',
'insertar',
'adjuntar',
'extender',
// French
'ajouter',
'insérer',
'étendre',
'annexer',
'joindre',
// German
'hinzufügen',
'einfügen',
'erweitern',
'anhängen',
'ergänzen',
// Italian
'aggiungere',
'inserire',
'allegare',
'estendere',
'appendere',
// Portuguese
'adicionar',
'inserir',
'acrescentar',
'anexar',
'estender',
// Polish
'dodać',
'wstawić',
'dołączyć',
'rozszerzyć',
'załączyć'
];
// Also look for numeric patterns like "add 2 paragraphs" or "добавь 3 абзаца"
// Enhanced pattern to find numeric paragraph requests in different languages
$numeric_pattern = '/(?:' .
// English verbs
'add|append|insert|create|write|' .
// Russian verbs
'добавь|вставь|создай|напиши|' .
// Spanish verbs
'añadir|agregar|insertar|crear|escribir|' .
// French verbs
'ajouter|insérer|créer|écrire|' .
// German verbs
'hinzufügen|einfügen|erstellen|schreiben|' .
// Italian verbs
'aggiungere|inserire|creare|scrivere|' .
// Portuguese verbs
'adicionar|inserir|criar|escrever|' .
// Polish verbs
'dodać|wstawić|utworzyć|napisać' .
')\s+(\d+|' .
// English quantifiers
'several|few|couple|some|' .
// Russian quantifiers
'несколько|пару|еще|ещё|' .
// Spanish quantifiers
'varios|algunos|un par|unos|' .
// French quantifiers
'plusieurs|quelques|une paire|certains|' .
// German quantifiers
'mehrere|einige|ein paar|manche|' .
// Italian quantifiers
'diversi|alcuni|un paio|qualche|' .
// Portuguese quantifiers
'vários|alguns|um par|' .
// Polish quantifiers
'kilka|parę|pare|niektóre' .
')\s+(?:' .
// English nouns
'paragraph|paragraphs|section|sections|content|text|' .
// Russian nouns
'абзац|абзаца|абзацев|раздел|разделы|текст|контент|параграф|параграфа|параграфов|' .
// Spanish nouns
'párrafo|párrafos|sección|secciones|contenido|texto|' .
// French nouns
'paragraphe|paragraphes|section|sections|contenu|texte|' .
// German nouns
'absatz|absätze|abschnitt|abschnitte|inhalt|text|' .
// Italian nouns
'paragrafo|paragrafi|sezione|sezioni|contenuto|testo|' .
// Portuguese nouns
'parágrafo|parágrafos|seção|seções|conteúdo|texto|' .
// Polish nouns
'akapit|akapity|sekcja|sekcje|treść|tekst' .
')/i';
$contains_add_keyword = false;
$numeric_match = [];
$requested_paragraphs = 0;
// First check for specific numeric requests
if (preg_match($numeric_pattern, $prompt, $numeric_match)) {
$contains_add_keyword = true;
$number_text = $numeric_match[1] ?? '';
// Convert text numbers to digits
if (is_numeric($number_text)) {
$requested_paragraphs = (int)$number_text;
} else {
// For words like "several", "few", "couple", etc.
switch (strtolower($number_text)) {
// Words meaning approximately "2"
case 'couple':
case 'пару': // Russian
case 'пара': // Russian
case 'un par': // Spanish
case 'une paire': // French
case 'ein paar': // German
case 'un paio': // Italian
case 'um par': // Portuguese
case 'parę': // Polish
case 'pare': // Polish
$requested_paragraphs = 2;
break;
// Words meaning approximately "3-4" (several/few)
case 'few':
case 'several':
case 'some':
case 'несколько': // Russian
case 'еще': // Russian
case 'ещё': // Russian
case 'varios': // Spanish
case 'algunos': // Spanish
case 'unos': // Spanish
case 'plusieurs': // French
case 'quelques': // French
case 'certains': // French
case 'mehrere': // German
case 'einige': // German
case 'manche': // German
case 'diversi': // Italian
case 'alcuni': // Italian
case 'qualche': // Italian
case 'vários': // Portuguese
case 'alguns': // Portuguese
case 'kilka': // Polish
case 'niektóre': // Polish
default:
$requested_paragraphs = 3; // Default "several" = 3
break;
}
}
} else {
// Then check for general add keywords
foreach ($add_content_keywords as $keyword) {
if (stripos($prompt, $keyword) !== false) {
$contains_add_keyword = true;
$requested_paragraphs = 2; // Default to 2 paragraphs if just "add paragraphs"
break;
}
}
}
// Build the system message dynamically based on the prompt analysis
$system_message = $instruction_context;
if ($contains_add_keyword) {
if ($requested_paragraphs > 0) {
// Request only new paragraphs, without modifying original content
$system_message = 'You are an assistant that generates NEW content only, without modifying the original text. DO NOT repeat or return the original text in your response.';
$system_message .= sprintf(
' IMPORTANT: You must generate EXACTLY %d NEW distinct paragraphs. Return ONLY these new paragraphs, properly formatted with HTML tags around each paragraph. The generated paragraphs should be a logical continuation or addition to the original content.',
$requested_paragraphs
);
} else {
$system_message .= ' IMPORTANT: The user is asking you to ADD content, not replace it. Make sure to preserve all the original text and add to it with at least 2-3 new paragraphs or sections.';
}
}
$body = [
'model' => $model,
'messages' => [
[
'role' => 'system',
'content' => $system_message,
],
[
'role' => 'user',
'content' => ($contains_add_keyword && $requested_paragraphs > 0)
? sprintf(
"Original Text for context: %s\n\nInstruction: Generate %d new paragraphs to add to this text, following the same style and continuing the topic. Return ONLY the new paragraphs.",
$original,
$requested_paragraphs
)
: sprintf(
/* translators: %1$s: User's instruction prompt, %2$s: Original text to modify. */
esc_html__("Instruction: %1\$s\nOriginal Text: %2\$s\n\nReturn the complete modified text that incorporates both the original content and your changes, unless explicitly asked to replace content.", 'king-addons'),
$prompt,
$original
),
],
],
'max_tokens' => 10000, // Increased to allow for more content
'temperature' => 0.7, // Slightly more creative
];
// Set append_mode flag for paragraph additions
$append_mode = ($contains_add_keyword && $requested_paragraphs > 0);
// Modify request for WYSIWYG editor
$editor_type = isset($_POST['editor_type']) ? sanitize_text_field(wp_unslash($_POST['editor_type'])) : 'text';
if ($editor_type === 'wysiwyg') {
// Add a specific instruction for formatting
$body['messages'][0]['content'] .= ' Format the response as proper HTML with tags for paragraphs and appropriate spacing. IMPORTANT: Do NOT use code fences (``` or ```html) in your response.';
if (!$append_mode) {
// Only add this for non-append mode
$body['messages'][1]['content'] .= "\n\nOutput should be properly formatted HTML with tags for paragraphs, maintaining good spacing and readability. Do NOT use code fences (```html or ```) - provide just the clean HTML.";
}
// Increase temperature for WYSIWYG to be more creative when creating paragraphs
$body['temperature'] = 0.8;
// Increase max_tokens for longer responses with multiple paragraphs
$body['max_tokens'] = 15000;
}
$response = wp_remote_post(
'https://api.openai.com/v1/chat/completions',
[
'headers' => [
'Authorization' => 'Bearer ' . $api_key,
'Content-Type' => 'application/json',
],
'body' => wp_json_encode($body),
'timeout' => 30,
]
);
if (is_wp_error($response)) {
wp_send_json_error(['message' => $response->get_error_message()], 500);
}
$code = wp_remote_retrieve_response_code($response);
$data = json_decode(wp_remote_retrieve_body($response), true);
if ($code !== 200 || empty($data['choices'][0]['message']['content'])) {
$error_msg = $data['error']['message'] ?? esc_html__('AI change error.', 'king-addons');
wp_send_json_error(['message' => $error_msg], 500);
}
$changed = trim($data['choices'][0]['message']['content']);
// Clean up any code fence markers for WYSIWYG editor
if ($editor_type === 'wysiwyg') {
// Remove code fence markers (```html and ```) that might be returned by AI
$changed = preg_replace('/^```(?:html|HTML)?\s*/', '', $changed);
$changed = preg_replace('/```\s*$/', '', $changed);
}
// Update token usage statistics if present in the response
if (isset($data['usage']['total_tokens'])) {
$this->incrementAiDailyUsage(intval($data['usage']['total_tokens']));
}
// Send response with append mode flag
wp_send_json_success([
'text' => $changed,
'append_mode' => $append_mode,
'original' => $append_mode ? $original : '',
'usage' => [
'tokens_used' => $data['usage']['total_tokens'] ?? 0,
'daily_used' => $this->getAiDailyUsage(),
'daily_limit' => $daily_limit,
]
]);
}
/**
* Renders Usage Quota Settings section.
*
* @return void
*/
public function renderAiQuotaSection(): void
{
echo ' ' . esc_html__('Set the daily token limit for AI features.', 'king-addons') . ' ' . esc_html__('Set the maximum number of tokens allowed per day for AI features. Set to 0 for unlimited.', 'king-addons') . ' ' . esc_html__('About tokens:', 'king-addons') . ' ' .
esc_html__('Tokens are the basic unit of text that the AI processes. As a rough guide:', 'king-addons') . ' • ' . esc_html__('1 token ≈ 4 characters or 0.75 words in English', 'king-addons') . ' • ' . esc_html__('A typical paragraph might use around 50-100 tokens', 'king-addons') . ' • ' . esc_html__('A full page of text (500 words) is approximately 750 tokens', 'king_addons') . ' • ' . esc_html__('Recommended daily limit: 10,000 - 50,000 tokens for moderate use', 'king-addons') . ' ' . esc_html(sprintf(__('Usage: %d%%', 'king-addons'), $usage_percentage)) . ' ' . esc_html__('No daily token limit is set. All requests will be processed.', 'king-addons') . ' ' . esc_html__('Tokens used today:', 'king-addons') . ' ' . esc_html(number_format_i18n($used)) . ' ' . esc_html__('Control the integration of AI features in the Elementor editor.', 'king-addons') . ' ' . esc_html__('Configure automatic alt text generation for images in Media Library.', 'king-addons') . ' ' . esc_html__('Show "Generate" button in Media Library to manually create alt text for images using AI.', 'king-addons') . ' ' . esc_html__('Automatically generate alt text when new images are uploaded to Media Library. Great for SEO.', 'king-addons') . ' ' . esc_html__('How often (in seconds) the system should process alt text generation queue. Recommended: 60 seconds to avoid OpenAI API rate limits. Lower values may cause API errors during high usage periods. Range: 10-3600 seconds.', 'king-addons') . ' ';
printf(
/* translators: %1$s: URL to OpenAI Organization Settings */
wp_kses(
__( 'Select the default model for AI image generation. By default, the model is DALL·E 3. For now, your organization must be verified to use the model GPT Image 1. Please go to OpenAI Organization Settings to verify. If you just verified, it can take up to 15 minutes for access to propagate.', 'king-addons' ),
[ 'a' => [ 'href' => [], 'target' => [], 'rel' => [] ] ]
),
esc_url( 'https://platform.openai.com/settings/organization/general' )
);
echo ' ' . esc_html__("Controls the detail level OpenAI uses to analyze images. 'Low' uses a fixed, lower token cost. 'High' uses more tokens based on image size (potentially more accurate analysis, but costs more). See OpenAI pricing for details.", 'king-addons') . ' ' . esc_html__('Configure AI Page Translator settings for Elementor editor.', 'king-addons') . ' ' . esc_html__('When enabled, adds an AI Page Translator button to the Elementor editor top toolbar that allows you to translate entire pages with one click. Automatically detects and translates all text content in widgets including advanced repeater fields.', 'king-addons') . ',
, etc. for better readability and structure. IMPORTANT: Do NOT wrap your HTML in code fences (``` or ```html). Respond ONLY with the actual HTML content.';
}
// Prepare request to OpenAI Chat Completions
$messages = [
['role' => 'system', 'content' => $system_instruction],
['role' => 'user', 'content' => $prompt]
];
// Add format instruction for WYSIWYG
if ($editor_type === 'wysiwyg') {
$messages[1]['content'] .= "\n\nOutput should be properly formatted HTML with
';
echo '
';
// Add progress bar
echo '';
echo '';
echo ' ';
echo '' . esc_html__('Tokens Used Today', 'king-addons') . ' ';
echo '' . esc_html(number_format_i18n($used)) . ' ';
echo '';
echo ' ';
echo '' . esc_html__('Daily Limit', 'king-addons') . ' ';
echo '' . esc_html($limit_display) . ' ';
echo '';
echo ' ';
echo '' . esc_html__('Remaining', 'king-addons') . ' ';
echo '' . esc_html($remaining_display) . ' ';
echo '