403]); } check_admin_referer('chatnex_admin_language'); $language = isset($_POST['chatnex_admin_language']) ? sanitize_text_field(wp_unslash($_POST['chatnex_admin_language'])) : ''; if (!in_array($language, ['lt', 'en'], true)) { wp_die(esc_html__('Invalid language.', 'chatnex'), '', ['response' => 400]); } $settings = get_option('chatnex_settings', []); $settings = is_array($settings) ? $settings : []; $settings['admin_language'] = $language; update_option('chatnex_settings', $settings, false); wp_safe_redirect(admin_url('admin.php?page=chatnex')); exit; } public function handle_service_connection() { if (!current_user_can('manage_options')) { wp_die(esc_html__('You are not allowed to manage ChatNEX.', 'chatnex'), '', ['response' => 403]); } check_admin_referer('chatnex_service_connection'); $connect = isset($_POST['chatnex_connect']) && '1' === sanitize_text_field(wp_unslash($_POST['chatnex_connect'])); update_option('chatnex_service_consent', $connect ? time() : false, false); if ($connect) { self::mark_auto_register_pending(); $this->maybe_auto_register_project('administrator_opt_in'); } $this->purge_public_page_cache(); wp_safe_redirect(admin_url('admin.php?page=chatnex')); exit; } public function add_privacy_policy_content() { if (!function_exists('wp_add_privacy_policy_content')) { return; } wp_add_privacy_policy_content('ChatNEX', wp_kses_post( '

' . __('This site uses the ChatNEX AI assistant provided by ENEX MB. After the site administrator connects the service, the site URL, name, plugin version and assistant settings are sent to https://chat.enex.lt. When a visitor uses the chat, their messages, conversation identifier, language, page URL and any contact details they choose to enter are sent to ChatNEX to answer questions and deliver enquiries. ChatNEX uses OpenAI to process AI requests. The chat stores conversation state in browser storage and periodically sends service availability requests while the widget is active.', 'chatnex') . '

' . '

' . __('Site owners must explain their own purposes, legal basis, retention and contact handling in their privacy notice. Do not enter passwords, payment card details or sensitive personal information in the chat. Contact info@enex.lt for information about the ChatNEX service and data requests. Stripe processes subscription payments outside this website.', 'chatnex') . '

' )); } public function register_admin_menu() { add_menu_page( 'ChatNEX', 'ChatNEX', 'manage_options', 'chatnex', [$this, 'render_settings_page'], 'dashicons-format-chat', 56 ); add_submenu_page( 'chatnex', chatnex_admin_text('Išvaizda', 'Appearance'), chatnex_admin_text('Išvaizda', 'Appearance'), 'manage_options', 'chatnex-design', [$this, 'render_design_page'] ); add_submenu_page( 'chatnex', chatnex_admin_text('DI elgsena', 'AI behavior'), chatnex_admin_text('DI elgsena', 'AI behavior'), 'manage_options', 'chatnex-ai-behavior', [$this, 'render_ai_behavior_page'] ); } public static function mark_auto_register_pending() { if (defined('WP_INSTALLING') && WP_INSTALLING) { return; } if (!chatnex_service_connected()) { return; } update_option('chatnex_auto_register_pending', time(), false); delete_transient('chatnex_auto_register_lock'); } public function maybe_auto_register_project_in_admin() { if (!is_admin()) { return; } if (!current_user_can('manage_options')) { return; } if (defined('WP_INSTALLING') && WP_INSTALLING) { return; } if (wp_doing_ajax()) { return; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only admin-page routing, never authorizes a settings change or service consent. $page = isset($_GET['page']) ? sanitize_key(wp_unslash($_GET['page'])) : ''; $is_chatnex_page = strpos($page, 'chatnex') === 0; $pending = (int) get_option('chatnex_auto_register_pending', 0); if (!$pending && !$is_chatnex_page) { return; } $this->maybe_auto_register_project($is_chatnex_page ? 'chatnex_admin' : 'activation_pending'); } public function maybe_auto_register_project($source = 'manual') { if (!chatnex_service_connected()) { return false; } if (defined('WP_INSTALLING') && WP_INSTALLING) { return false; } if (wp_doing_ajax()) { return false; } $lock_key = 'chatnex_auto_register_lock'; if (get_transient($lock_key)) { return false; } $settings = get_option($this->option_name, []); $settings = is_array($settings) ? $settings : []; $backend_url = 'https://chat.enex.lt'; $project_id = isset($settings['project_id']) ? strtolower(sanitize_text_field((string) $settings['project_id'])) : ''; $public_key = isset($settings['public_key']) ? sanitize_text_field((string) $settings['public_key']) : ''; $site_url = home_url('/'); $site_name = get_bloginfo('name'); if (empty($site_url) || empty($backend_url)) { return false; } set_transient($lock_key, 1, 120); $body = [ 'site_url' => $site_url, 'site_name' => $site_name, 'source' => $source, 'plugin_version' => CHATNEX_VERSION, ]; if (!empty($project_id)) { $body['project_id'] = $project_id; } if (!empty($public_key)) { $body['public_key'] = $public_key; } $request_args = [ 'timeout' => 3, 'redirection' => 2, 'blocking' => true, 'sslverify' => true, 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => wp_json_encode($body, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]; $response = wp_remote_post($backend_url . '/api/project-auto-register', $request_args); delete_transient($lock_key); if (is_wp_error($response)) { update_option('chatnex_auto_register_pending', time(), false); update_option('chatnex_auto_register_last_status', 'warning', false); update_option('chatnex_auto_register_last_message', $response->get_error_message(), false); return false; } $code = wp_remote_retrieve_response_code($response); $raw_body = wp_remote_retrieve_body($response); if ($code < 200 || $code >= 300 || empty($raw_body)) { update_option('chatnex_auto_register_pending', time(), false); update_option('chatnex_auto_register_last_status', 'warning', false); update_option('chatnex_auto_register_last_message', 'Backend grąžino netinkamą atsakymą: HTTP ' . (int) $code, false); return false; } $data = json_decode($raw_body, true); if (!is_array($data) || empty($data['ok'])) { update_option('chatnex_auto_register_pending', time(), false); update_option('chatnex_auto_register_last_status', 'warning', false); update_option('chatnex_auto_register_last_message', 'Backend atsakymas neatpažintas.', false); return false; } $resolved_project_id = isset($data['project_id']) ? strtolower(sanitize_text_field((string) $data['project_id'])) : ''; $resolved_public_key = isset($data['public_key']) ? sanitize_text_field((string) $data['public_key']) : ''; $billing_management_token = isset($data['billing_management_token']) ? sanitize_text_field((string) $data['billing_management_token']) : ''; if (empty($resolved_project_id) && empty($resolved_public_key)) { update_option('chatnex_auto_register_pending', time(), false); update_option('chatnex_auto_register_last_status', 'warning', false); update_option('chatnex_auto_register_last_message', 'Backend negrąžino project_id arba public_key.', false); return false; } $updated = $settings; $updated['backend_url'] = $backend_url; if (!empty($resolved_project_id)) { $updated['project_id'] = $resolved_project_id; } if (!empty($resolved_public_key)) { $updated['public_key'] = $resolved_public_key; } update_option($this->option_name, $updated, false); if (!empty($billing_management_token)) { update_option('chatnex_billing_management_token', $billing_management_token, false); } delete_option('chatnex_auto_register_pending'); delete_option('chatnex_auto_register_last_status'); delete_option('chatnex_auto_register_last_message'); $this->sync_ai_behavior_settings_to_backend($updated); $this->sync_widget_settings_to_backend($updated); $this->purge_public_page_cache(); update_option('chatnex_last_live_sync_version', CHATNEX_VERSION, false); return true; } public function register_settings() { register_setting('chatnex_settings_group', $this->option_name, [$this, 'sanitize_settings']); add_settings_section( 'chatnex_main_section', 'Pagrindiniai nustatymai', function () { echo '

' . esc_html(chatnex_admin_text('Nustatykite ChatNEX įskiepį ir prijunkite prie centrinio backend.', 'Configure the ChatNEX plugin and connect it to the central backend.')) . '

'; }, 'chatnex' ); add_settings_field( 'enabled', chatnex_admin_text('Įjungti chat', 'Enable chat'), [$this, 'render_enabled_field'], 'chatnex', 'chatnex_main_section' ); add_settings_field( 'widget_language', chatnex_admin_text('Chat kalba', 'Visitor chat language'), [$this, 'render_widget_language_field'], 'chatnex', 'chatnex_main_section' ); add_settings_field( 'admin_language', chatnex_admin_text('Įskiepio administravimo kalba', 'Plugin administration language'), [$this, 'render_admin_language_field'], 'chatnex', 'chatnex_main_section' ); add_settings_field( 'project_id', chatnex_admin_text('Projekto ID', 'Project ID'), [$this, 'render_project_id_field'], 'chatnex', 'chatnex_main_section' ); add_settings_field( 'public_key', chatnex_admin_text('Public key', 'Public key'), [$this, 'render_public_key_field'], 'chatnex', 'chatnex_main_section' ); add_settings_field( 'lead_recipients', chatnex_admin_text('Užklausų gavėjai', 'Lead recipients'), [$this, 'render_lead_recipients_field'], 'chatnex', 'chatnex_main_section' ); add_settings_field( 'chat_title', chatnex_admin_text('Chat antraštė', 'Chat title'), [$this, 'render_chat_title_field'], 'chatnex', 'chatnex_main_section' ); add_settings_field( 'helper_text', chatnex_admin_text('Helper tekstas', 'Helper text'), [$this, 'render_helper_text_field'], 'chatnex', 'chatnex_main_section' ); add_settings_field( 'welcome_text', chatnex_admin_text('Pradinis tekstas', 'Welcome message'), [$this, 'render_welcome_text_field'], 'chatnex', 'chatnex_main_section' ); add_settings_field( 'quick_questions', chatnex_admin_text('Greiti klausimai', 'Quick questions'), [$this, 'render_quick_questions_field'], 'chatnex', 'chatnex_main_section' ); add_settings_section( 'chatnex_ai_behavior_section', 'DI elgsena', function () { echo '

' . esc_html(chatnex_admin_text('Nustatykite, kaip ChatNEX bendrauja su lankytoju: emoji, toną, šiltumą ir kontaktų siūlymą.', 'Configure how ChatNEX communicates with visitors: emoji, tone, warmth and contact behavior.')) . '

'; }, 'chatnex-ai-behavior' ); add_settings_field( 'additional_ai_context', chatnex_admin_text('Papildomos AI taisyklės / informacija', 'Additional AI rules / information'), [$this, 'render_additional_ai_context_field'], 'chatnex-ai-behavior', 'chatnex_ai_behavior_section' ); add_settings_field( 'ai_emoji_enabled', chatnex_admin_text('Naudoti emoji', 'Use emoji'), [$this, 'render_ai_emoji_enabled_field'], 'chatnex-ai-behavior', 'chatnex_ai_behavior_section' ); add_settings_field( 'ai_emoji_level', chatnex_admin_text('Emoji kiekis (0–100)', 'Emoji amount (0–100)'), [$this, 'render_ai_emoji_level_field'], 'chatnex-ai-behavior', 'chatnex_ai_behavior_section' ); add_settings_field( 'ai_tone_style', chatnex_admin_text('Bendravimo tonas', 'Communication tone'), [$this, 'render_ai_tone_style_field'], 'chatnex-ai-behavior', 'chatnex_ai_behavior_section' ); add_settings_field( 'ai_warmth_level', chatnex_admin_text('Bendravimo šiltumas (0–100)', 'Communication warmth (0–100)'), [$this, 'render_ai_warmth_level_field'], 'chatnex-ai-behavior', 'chatnex_ai_behavior_section' ); add_settings_field( 'ai_contact_stage_action', chatnex_admin_text('Kontaktų etapo veiksmas', 'Contact-stage action'), [$this, 'render_ai_contact_stage_action_field'], 'chatnex-ai-behavior', 'chatnex_ai_behavior_section' ); add_settings_field( 'ai_contact_trigger_after_ai_replies', chatnex_admin_text('Priverstinai prašyti / duoti kontaktus po AI atsakymų', 'Force a contact action after AI replies'), [$this, 'render_ai_contact_trigger_after_ai_replies_field'], 'chatnex-ai-behavior', 'chatnex_ai_behavior_section' ); add_settings_field( 'ai_uncertainty_level', chatnex_admin_text('Atsakymo atsargumas (0–100)', 'Answer caution (0–100)'), [$this, 'render_ai_uncertainty_level_field'], 'chatnex-ai-behavior', 'chatnex_ai_behavior_section' ); add_settings_section( 'chatnex_design_section', chatnex_admin_text('Išvaizdos nustatymai', 'Appearance settings'), function () { echo '

' . esc_html(chatnex_admin_text('Čia galite reguliuoti ChatNEX spalvas, skaidrumą ir vizualinius elementus.', 'Configure ChatNEX colors, transparency and visual elements.')) . '

'; }, 'chatnex-design' ); add_settings_field( 'bubble_color', chatnex_admin_text('Pranešimo burbuliuko spalva', 'Notification bubble color'), [$this, 'render_bubble_color_field'], 'chatnex-design', 'chatnex_design_section' ); add_settings_field( 'bubble_opacity', chatnex_admin_text('Pranešimo burbuliuko skaidrumas', 'Notification bubble transparency'), [$this, 'render_bubble_opacity_field'], 'chatnex-design', 'chatnex_design_section' ); add_settings_field( 'bubble_size', chatnex_admin_text('Chat burbuliuko dydis', 'Chat bubble size'), [$this, 'render_bubble_size_field'], 'chatnex-design', 'chatnex_design_section' ); add_settings_field( 'bubble_icon', chatnex_admin_text('Pranešimo burbuliuko ikonėlė', 'Notification bubble icon'), [$this, 'render_bubble_icon_field'], 'chatnex-design', 'chatnex_design_section' ); add_settings_field( 'header_color', chatnex_admin_text('Chat lango viršutinės dalies spalva', 'Chat header color'), [$this, 'render_header_color_field'], 'chatnex-design', 'chatnex_design_section' ); add_settings_field( 'header_opacity', chatnex_admin_text('Chat lango viršutinės dalies skaidrumas', 'Chat header transparency'), [$this, 'render_header_opacity_field'], 'chatnex-design', 'chatnex_design_section' ); add_settings_field( 'send_button_color', chatnex_admin_text('Siųsti mygtuko spalva', 'Send button color'), [$this, 'render_send_button_color_field'], 'chatnex-design', 'chatnex_design_section' ); add_settings_field( 'chat_background_color', chatnex_admin_text('Chat lango fono spalva', 'Chat window background color'), [$this, 'render_chat_background_color_field'], 'chatnex-design', 'chatnex_design_section' ); add_settings_field( 'chat_background_opacity', chatnex_admin_text('Chat lango skaidrumas', 'Chat window transparency'), [$this, 'render_chat_background_opacity_field'], 'chatnex-design', 'chatnex_design_section' ); add_settings_field( 'logo_url', chatnex_admin_text('Logotipo demonstravimas viršutinėje chat dalyje', 'Logo shown in the chat header'), [$this, 'render_logo_url_field'], 'chatnex-design', 'chatnex_design_section' ); add_settings_field( 'avatar_preset', chatnex_admin_text('Asistento ženkliukas', 'Assistant avatar'), [$this, 'render_avatar_preset_field'], 'chatnex-design', 'chatnex_design_section' ); } private function sync_ai_behavior_settings_to_backend($settings) { if (!chatnex_service_connected()) { return; } $project_id = isset($settings['project_id']) ? strtolower(sanitize_text_field((string) $settings['project_id'])) : ''; $backend_url = isset($settings['backend_url']) ? untrailingslashit(esc_url_raw((string) $settings['backend_url'])) : ''; $public_key = isset($settings['public_key']) ? sanitize_text_field((string) $settings['public_key']) : ''; if (empty($project_id) || empty($backend_url)) { return; } $payload = [ 'project_id' => $project_id, 'public_key' => $public_key, 'style' => [ 'emoji_enabled' => !empty($settings['ai_emoji_enabled']), 'emoji_level' => intval($settings['ai_emoji_level'] ?? 50), 'tone_style' => (string) ($settings['ai_tone_style'] ?? 'friendly'), 'warmth_level' => intval($settings['ai_warmth_level'] ?? 70), 'contact_stage_action' => (string) ($settings['ai_contact_stage_action'] ?? 'no_contact_flow'), 'contact_strategy_balance' => intval($settings['ai_contact_strategy_balance'] ?? $settings['ai_cta_level'] ?? 50), 'uncertainty_level' => intval($settings['ai_uncertainty_level'] ?? 80), 'contact_offer_mode' => 'never', 'registration_offer_mode' => 'never', 'contact_trigger_after_ai_replies' => (int) ($settings['ai_contact_trigger_after_ai_replies'] ?? 0), ], ]; wp_remote_post($backend_url . '/api/project-ai-behavior/sync', [ 'timeout' => 8, 'redirection' => 3, 'sslverify' => true, 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => wp_json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]); } private function sync_widget_settings_to_backend($settings) { if (!chatnex_service_connected()) { return; } $project_id = isset($settings['project_id']) ? strtolower(sanitize_text_field((string) $settings['project_id'])) : ''; $backend_url = isset($settings['backend_url']) ? untrailingslashit(esc_url_raw((string) $settings['backend_url'])) : ''; $public_key = isset($settings['public_key']) ? sanitize_text_field((string) $settings['public_key']) : ''; if (empty($project_id) || empty($backend_url)) { return; } $chat_background_opacity = intval($settings['chat_background_opacity'] ?? 0); $header_opacity = intval($settings['header_opacity'] ?? 0); $bubble_opacity = intval($settings['bubble_opacity'] ?? 0); $max_transparency = max($chat_background_opacity, $header_opacity, $bubble_opacity); $payload = [ 'project_id' => $project_id, 'public_key' => $public_key, 'site_url' => home_url('/'), 'plugin_version' => CHATNEX_VERSION, 'additional_ai_context' => (string) ($settings['additional_ai_context'] ?? ''), 'widget' => [ 'widget_enabled' => !empty($settings['enabled']), 'widget_position' => 'bottom-left', 'widget_size' => 'medium', 'widget_theme' => 'custom', 'primary_color' => (string) ($settings['bubble_color'] ?? ''), 'secondary_color' => (string) ($settings['header_color'] ?? ''), 'accent_color' => (string) ($settings['send_button_color'] ?? ''), 'transparency_enabled' => $max_transparency > 0, 'transparency_value' => max(0, min(100, 100 - $max_transparency)), 'bubble_label' => (string) ($settings['helper_text'] ?? ''), 'welcome_message' => (string) ($settings['welcome_text'] ?? ''), 'input_placeholder' => (($settings['widget_language'] ?? 'lt') === 'en') ? 'Type your message...' : 'Parašykite žinutę...', 'send_button_label' => (($settings['widget_language'] ?? 'lt') === 'en') ? 'Send' : 'Siųsti', 'header_title' => (string) ($settings['chat_title'] ?? ''), 'header_subtitle' => (string) ($settings['helper_text'] ?? ''), 'show_branding' => false, ], ]; wp_remote_post($backend_url . '/api/project-widget-settings/sync', [ 'timeout' => 8, 'redirection' => 3, 'sslverify' => true, 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => wp_json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]); } public function maybe_sync_live_settings_after_update() { if (!chatnex_service_connected()) { return; } if (defined('WP_INSTALLING') && WP_INSTALLING) { return; } if (function_exists('wp_doing_ajax') && wp_doing_ajax()) { return; } $last_synced_version = (string) get_option('chatnex_last_live_sync_version', ''); if ($last_synced_version === CHATNEX_VERSION) { return; } $lock_key = 'chatnex_live_settings_version_sync_lock'; if (get_transient($lock_key)) { return; } set_transient($lock_key, 1, 120); $settings = $this->get_settings(); $project_id = isset($settings['project_id']) ? trim((string) $settings['project_id']) : ''; $backend_url = isset($settings['backend_url']) ? trim((string) $settings['backend_url']) : ''; if ($project_id === '' || $backend_url === '') { delete_transient($lock_key); return; } $this->sync_ai_behavior_settings_to_backend($settings); $this->sync_widget_settings_to_backend($settings); $this->purge_public_page_cache(); update_option('chatnex_last_live_sync_version', CHATNEX_VERSION, false); delete_transient($lock_key); } private function purge_public_page_cache() { if (function_exists('wp_cache_clear_cache')) { wp_cache_clear_cache(); } if (function_exists('wp_cache_clean_cache')) { global $file_prefix; wp_cache_clean_cache($file_prefix ?? ''); } if (function_exists('wpsc_delete_post_cache')) { $front_page_id = (int) get_option('page_on_front'); if ($front_page_id > 0) { wpsc_delete_post_cache($front_page_id); } } if (function_exists('rocket_clean_domain')) { rocket_clean_domain(); } if (has_action('litespeed_purge_all')) { // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- Official LiteSpeed Cache integration hook. do_action('litespeed_purge_all'); } if (function_exists('wp_cache_flush')) { wp_cache_flush(); } } private function get_central_project_meta($project_id, $backend_url, $public_key = '') { if (!chatnex_service_connected()) { return null; } $project_id = strtolower(sanitize_text_field((string) $project_id)); $backend_url = untrailingslashit(esc_url_raw((string) $backend_url)); $public_key = sanitize_text_field((string) $public_key); if (empty($project_id) || empty($backend_url)) { return null; } $url = add_query_arg([ 'project_id' => $project_id, 'public_key' => $public_key, ], $backend_url . '/api/project-public-meta'); $response = wp_remote_get($url, [ 'timeout' => 8, 'redirection' => 3, 'sslverify' => true, ]); if (is_wp_error($response)) { return null; } $code = wp_remote_retrieve_response_code($response); $body = wp_remote_retrieve_body($response); if ($code < 200 || $code >= 300 || empty($body)) { return null; } $data = json_decode($body, true); if (!is_array($data)) { return null; } if (isset($data['project_meta']) && is_array($data['project_meta'])) { return $data['project_meta']; } return $data; } public function handle_autosave_settings() { if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Neturite teisės keisti ChatNEX nustatymų.'], 403); } check_ajax_referer('chatnex_autosave_settings', 'nonce'); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Each settings field is validated and sanitized by sanitize_settings() below. $input = isset($_POST[$this->option_name]) && is_array($_POST[$this->option_name]) ? wp_unslash($_POST[$this->option_name]) : []; if (!is_array($input)) { wp_send_json_error(['message' => 'Neteisingi nustatymų duomenys.'], 400); } $sanitized = $this->sanitize_settings($input); update_option($this->option_name, $sanitized, false); wp_send_json_success([ 'message' => 'Išsaugota', 'saved_at' => current_time('mysql'), 'version' => CHATNEX_VERSION, ]); } private function proxy_billing_request($endpoint, $payload = [], $receipt_response = false) { if (!current_user_can('manage_options')) { wp_send_json_error(['message' => 'Neturite teisės valdyti ChatNEX prenumeratos.'], 403); } check_ajax_referer('chatnex_billing_management', 'nonce'); if (!chatnex_service_connected()) { wp_send_json_error(['message' => __('Connect ChatNEX before managing a subscription.', 'chatnex')], 409); } $settings = $this->get_settings(); $project_id = isset($settings['project_id']) ? strtolower(sanitize_text_field((string) $settings['project_id'])) : ''; $billing_token = sanitize_text_field((string) get_option('chatnex_billing_management_token', '')); if (empty($project_id) || empty($billing_token)) { wp_send_json_error(['message' => 'Trūksta projekto mokėjimų valdymo rakto.'], 409); } $response = wp_remote_post('https://chat.enex.lt' . $endpoint, [ 'timeout' => 20, 'redirection' => 0, 'blocking' => true, 'sslverify' => true, 'headers' => [ 'Content-Type' => 'application/json', ], 'body' => wp_json_encode(array_merge([ 'project_id' => $project_id, 'billing_management_token' => $billing_token, ], $payload), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ]); if (is_wp_error($response)) { wp_send_json_error(['message' => $response->get_error_message()], 502); } $code = (int) wp_remote_retrieve_response_code($response); $data = json_decode(wp_remote_retrieve_body($response), true); $data = is_array($data) ? $data : []; if ($receipt_response && $code >= 200 && $code < 300 && !empty($data['receipt_id']) && preg_match('/^[a-f0-9-]{36}$/', (string) $data['receipt_id'])) { wp_send_json_success([ 'receipt_id' => sanitize_text_field($data['receipt_id']), 'received_at' => sanitize_text_field($data['received_at'] ?? ''), 'receipt_text' => sanitize_textarea_field($data['receipt_text'] ?? ''), 'email_accepted' => !empty($data['email_accepted']), 'refund_completed' => false, ]); } if ($code < 200 || $code >= 300 || empty($data['url'])) { $message = isset($data['error']) ? sanitize_text_field((string) $data['error']) : 'ChatNEX mokėjimų serveris grąžino klaidą.'; wp_send_json_error(['message' => $message], $code >= 400 && $code <= 599 ? $code : 502); } $url = esc_url_raw((string) $data['url']); $host = strtolower((string) wp_parse_url($url, PHP_URL_HOST)); if (!in_array($host, ['checkout.stripe.com', 'billing.stripe.com'], true)) { wp_send_json_error(['message' => 'Gauta neleistina mokėjimo nuoroda.'], 502); } wp_send_json_success(['url' => $url]); } public function handle_billing_checkout() { check_ajax_referer('chatnex_billing_management', 'nonce'); $interval = isset($_POST['interval']) ? sanitize_key((string) wp_unslash($_POST['interval'])) : ''; if (!in_array($interval, ['month', 'year'], true)) { wp_send_json_error(['message' => 'Neteisingas prenumeratos laikotarpis.'], 400); } $accepted = isset($_POST['terms_accepted']) && '1' === sanitize_text_field(wp_unslash($_POST['terms_accepted'])); if (!$accepted) { wp_send_json_error(['message' => chatnex_admin_text('Patvirtinkite prenumeratos sąlygas.', 'Please accept the subscription terms.')], 400); } $this->proxy_billing_request('/api/billing/checkout', ['interval' => $interval, 'terms_accepted' => true, 'legal_version' => CHATNEX_LEGAL_VERSION]); } public function handle_billing_withdrawal() { check_ajax_referer('chatnex_billing_management', 'nonce'); $name = isset($_POST['customer_name']) ? sanitize_text_field(wp_unslash($_POST['customer_name'])) : ''; $confirmed = isset($_POST['confirm_withdrawal']) && '1' === sanitize_text_field(wp_unslash($_POST['confirm_withdrawal'])); if (!$confirmed || strlen($name) < 2 || strlen($name) > 180) { wp_send_json_error(['message' => chatnex_admin_text('Įrašykite vardą ir patvirtinkite pareiškimą.', 'Enter your name and confirm the statement.')], 400); } $this->proxy_billing_request('/api/billing/withdrawal', ['customer_name' => $name, 'confirm_withdrawal' => true], true); } public function handle_billing_portal() { $this->proxy_billing_request('/api/billing/portal'); } public function sanitize_settings($input) { $output = $this->get_settings(); $settings_scope = isset($input['settings_scope']) ? sanitize_key((string) $input['settings_scope']) : ''; if (array_key_exists('enabled', $input)) { $output['enabled'] = !empty($input['enabled']) ? 1 : 0; } $output['backend_url'] = 'https://chat.enex.lt'; $admin_language = isset($input['admin_language']) ? strtolower(sanitize_text_field((string) $input['admin_language'])) : ($output['admin_language'] ?? 'en'); $output['admin_language'] = in_array($admin_language, ['lt', 'en'], true) ? $admin_language : 'en'; $widget_language = isset($input['widget_language']) ? strtolower(sanitize_text_field((string) $input['widget_language'])) : ($output['widget_language'] ?? 'en'); $output['widget_language'] = in_array($widget_language, ['lt', 'en'], true) ? $widget_language : 'en'; $output['project_id'] = isset($input['project_id']) ? strtolower(sanitize_text_field($input['project_id'])) : ($output['project_id'] ?? ''); $output['public_key'] = isset($input['public_key']) ? sanitize_text_field($input['public_key']) : ($output['public_key'] ?? ''); $raw_recipients = isset($input['lead_recipients']) && is_array($input['lead_recipients']) ? $input['lead_recipients'] : ($output['lead_recipients'] ?? []); $clean_recipients = []; foreach ($raw_recipients as $recipient) { $recipient = sanitize_email($recipient); if (!$recipient || !is_email($recipient)) { continue; } $clean_recipients[] = strtolower($recipient); } $clean_recipients = array_values(array_unique($clean_recipients)); $output['lead_recipients'] = $clean_recipients; $output['chat_title'] = isset($input['chat_title']) ? sanitize_text_field($input['chat_title']) : ($output['chat_title'] ?? 'ChatNEX'); $output['helper_text'] = isset($input['helper_text']) ? sanitize_text_field($input['helper_text']) : ($output['helper_text'] ?? 'Mūsų Dirbtinis intelektas Jums padės 🙂'); $output['welcome_text'] = isset($input['welcome_text']) ? sanitize_textarea_field($input['welcome_text']) : ($output['welcome_text'] ?? 'Sveiki 👋 Kuo galiu padėti? Galite pasirinkti vieną iš greitų klausimų žemiau arba parašyti savo žinutę.'); $q1 = isset($input['quick_question_1']) ? sanitize_text_field($input['quick_question_1']) : ($output['quick_question_1'] ?? 'Kiek kainuoja?'); $q2 = isset($input['quick_question_2']) ? sanitize_text_field($input['quick_question_2']) : ($output['quick_question_2'] ?? 'Per kiek padarote?'); $q3 = isset($input['quick_question_3']) ? sanitize_text_field($input['quick_question_3']) : ($output['quick_question_3'] ?? ''); $output['quick_question_1'] = $q1; $output['quick_question_2'] = $q2; $output['quick_question_3'] = $q3; $output['quick_questions'] = implode(" ", array_values(array_filter([$q1, $q2, $q3]))); $output['additional_ai_context'] = isset($input['additional_ai_context']) ? sanitize_textarea_field($input['additional_ai_context']) : ($output['additional_ai_context'] ?? ''); $output['bubble_color'] = isset($input['bubble_color']) ? sanitize_hex_color($input['bubble_color']) : ($output['bubble_color'] ?? '#5b6df8'); $output['bubble_opacity'] = isset($input['bubble_opacity']) ? max(0, min(100, intval($input['bubble_opacity']))) : ($output['bubble_opacity'] ?? 0); $output['bubble_size'] = isset($input['bubble_size']) ? max(44, min(96, intval($input['bubble_size']))) : max(44, min(96, intval($output['bubble_size'] ?? 64))); $output['bubble_icon'] = isset($input['bubble_icon']) ? sanitize_text_field($input['bubble_icon']) : ($output['bubble_icon'] ?? 'chat'); $output['bubble_icon_custom_url'] = isset($input['bubble_icon_custom_url']) ? esc_url_raw(trim($input['bubble_icon_custom_url'])) : ($output['bubble_icon_custom_url'] ?? ''); $output['header_color'] = isset($input['header_color']) ? sanitize_hex_color($input['header_color']) : ($output['header_color'] ?? '#5b6df8'); $output['header_opacity'] = isset($input['header_opacity']) ? max(0, min(100, intval($input['header_opacity']))) : ($output['header_opacity'] ?? 0); $output['send_button_color'] = isset($input['send_button_color']) ? sanitize_hex_color($input['send_button_color']) : ($output['send_button_color'] ?? '#5b6df8'); $output['chat_background_color'] = isset($input['chat_background_color']) ? sanitize_hex_color($input['chat_background_color']) : ($output['chat_background_color'] ?? '#ffffff'); $output['chat_background_opacity'] = isset($input['chat_background_opacity']) ? max(0, min(100, intval($input['chat_background_opacity']))) : ($output['chat_background_opacity'] ?? 0); $output['logo_url'] = isset($input['logo_url']) ? esc_url_raw(trim($input['logo_url'])) : ($output['logo_url'] ?? ''); $output['avatar_preset'] = isset($input['avatar_preset']) ? sanitize_text_field($input['avatar_preset']) : ($output['avatar_preset'] ?? 'ai-robot'); $output['avatar_custom_url'] = isset($input['avatar_custom_url']) ? esc_url_raw(trim($input['avatar_custom_url'])) : ($output['avatar_custom_url'] ?? ''); $output['avatar_url'] = $this->resolve_avatar_url($output); $is_ai_behavior_save = isset($input['ai_emoji_level']) || isset($input['ai_tone_style']) || isset($input['ai_contact_stage_action']) || isset($input['ai_contact_trigger_after_ai_replies']); if ($is_ai_behavior_save) { $output['ai_emoji_enabled'] = !empty($input['ai_emoji_enabled']) ? 1 : 0; } else { $output['ai_emoji_enabled'] = isset($output['ai_emoji_enabled']) ? (int) $output['ai_emoji_enabled'] : 1; } $output['ai_emoji_level'] = isset($input['ai_emoji_level']) ? max(0, min(100, intval($input['ai_emoji_level']))) : ($output['ai_emoji_level'] ?? 50); $tone = isset($input['ai_tone_style']) ? sanitize_text_field($input['ai_tone_style']) : ($output['ai_tone_style'] ?? 'friendly'); $output['ai_tone_style'] = in_array($tone, ['neutral', 'friendly', 'warm', 'sales'], true) ? $tone : 'friendly'; $output['ai_warmth_level'] = isset($input['ai_warmth_level']) ? max(0, min(100, intval($input['ai_warmth_level']))) : ($output['ai_warmth_level'] ?? 70); $is_contact_settings_save = $settings_scope === 'ai_behavior' || array_key_exists('ai_contact_stage_request_customer_contact', $input) || array_key_exists('ai_contact_stage_show_company_contacts', $input) || array_key_exists('ai_contact_stage_action', $input); if ($is_contact_settings_save) { $requestCustomerContact = !empty($input['ai_contact_stage_request_customer_contact']); $showCompanyContacts = !empty($input['ai_contact_stage_show_company_contacts']); if ($requestCustomerContact && $showCompanyContacts) { $stageAction = 'mixed_contact_flow'; } elseif ($requestCustomerContact) { $stageAction = 'request_customer_contact'; } elseif ($showCompanyContacts) { $stageAction = 'show_company_contacts'; } else { $stageAction = 'no_contact_flow'; } if ( !isset($input['ai_contact_stage_request_customer_contact']) && !isset($input['ai_contact_stage_show_company_contacts']) && isset($input['ai_contact_stage_action']) ) { $legacyStageAction = sanitize_text_field($input['ai_contact_stage_action']); $allowedLegacyStageActions = [ 'no_contact_flow', 'request_customer_contact', 'show_company_contacts', 'mixed_contact_flow' ]; $stageAction = in_array($legacyStageAction, $allowedLegacyStageActions, true) ? $legacyStageAction : ($output['ai_contact_stage_action'] ?? 'no_contact_flow'); } $output['ai_contact_stage_action'] = $stageAction; } else { $output['ai_contact_stage_action'] = $output['ai_contact_stage_action'] ?? 'no_contact_flow'; } // Backward compatibility only. Real logic is controlled by ai_contact_stage_action. if ($output['ai_contact_stage_action'] === 'show_company_contacts') { $output['ai_contact_strategy_balance'] = 100; } elseif ($output['ai_contact_stage_action'] === 'request_customer_contact') { $output['ai_contact_strategy_balance'] = 0; } elseif ($output['ai_contact_stage_action'] === 'mixed_contact_flow') { $output['ai_contact_strategy_balance'] = 50; } else { $output['ai_contact_strategy_balance'] = 0; } $output['ai_cta_level'] = $output['ai_contact_strategy_balance']; $output['ai_uncertainty_level'] = isset($input['ai_uncertainty_level']) ? max(0, min(100, intval($input['ai_uncertainty_level']))) : ($output['ai_uncertainty_level'] ?? 80); // Legacy intensity fields are no longer user-facing. // Contact behavior is controlled only by ai_contact_stage_action + ai_contact_trigger_after_ai_replies. $output['ai_contact_offer_mode'] = 'never'; $output['ai_registration_offer_mode'] = 'never'; $output['ai_contact_trigger_after_ai_replies'] = isset($input['ai_contact_trigger_after_ai_replies']) ? max(0, min(10, intval($input['ai_contact_trigger_after_ai_replies']))) : ($output['ai_contact_trigger_after_ai_replies'] ?? 0); $this->sync_ai_behavior_settings_to_backend($output); $this->sync_widget_settings_to_backend($output); $this->purge_public_page_cache(); return $output; } private function get_settings() { $defaults = [ 'enabled' => 1, 'admin_language' => chatnex_get_admin_language(), 'widget_language' => 'en', 'backend_url' => 'https://chat.enex.lt', 'project_id' => '', 'public_key' => '', 'lead_recipients' => [], 'chat_title' => 'ChatNEX AI Assistant', 'helper_text' => 'Ask us anything 🙂', 'welcome_text' => 'Hi 👋 Ask me anything about our services, pricing, projects or contact details 🙂', 'quick_question_1' => 'What services do you offer?', 'quick_question_2' => 'How can I contact you?', 'quick_question_3' => '', 'quick_questions' => "What services do you offer?\nHow can I contact you?", 'additional_ai_context' => '', 'bubble_color' => '#5b6df8', 'bubble_opacity' => 0, 'bubble_size' => 64, 'bubble_icon' => 'chat', 'bubble_icon_custom_url' => '', 'header_color' => '#5b6df8', 'header_opacity' => 0, 'send_button_color' => '#5b6df8', 'chat_background_color' => '#ffffff', 'chat_background_opacity' => 0, 'logo_url' => '', 'avatar_preset' => 'ai-robot', 'avatar_custom_url' => '', 'avatar_url' => CHATNEX_PLUGIN_URL . 'assets/avatars/ai-robot.svg', 'ai_emoji_enabled' => 1, 'ai_emoji_level' => 50, 'ai_tone_style' => 'friendly', 'ai_warmth_level' => 70, 'ai_contact_stage_action' => 'no_contact_flow', 'ai_contact_strategy_balance' => 50, 'ai_cta_level' => 50, 'ai_uncertainty_level' => 80, 'ai_contact_offer_mode' => 'never', 'ai_registration_offer_mode' => 'never', 'ai_contact_trigger_after_ai_replies' => 0 ]; $saved = get_option($this->option_name, []); $settings = wp_parse_args($saved, $defaults); if (empty($settings['ai_contact_stage_action'])) { $settings['ai_contact_stage_action'] = 'no_contact_flow'; } if (!in_array($settings['ai_contact_stage_action'], ['no_contact_flow', 'request_customer_contact', 'show_company_contacts', 'mixed_contact_flow'], true)) { $settings['ai_contact_stage_action'] = 'no_contact_flow'; } if (empty($settings['quick_question_1']) || empty($settings['quick_question_2'])) { $items = array_values(array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', (string) $settings['quick_questions'])))); if (!empty($items[0]) && empty($settings['quick_question_1'])) { $settings['quick_question_1'] = $items[0]; } if (!empty($items[1]) && empty($settings['quick_question_2'])) { $settings['quick_question_2'] = $items[1]; } if (!empty($items[2]) && empty($settings['quick_question_3'])) { $settings['quick_question_3'] = $items[2]; } } $settings['avatar_url'] = $this->resolve_avatar_url($settings); return $settings; } public function render_enabled_field() { $settings = $this->get_settings(); echo ''; } public function render_project_id_field() { $settings = $this->get_settings(); echo ''; } public function render_public_key_field() { $settings = $this->get_settings(); echo ''; echo '

' . esc_html(chatnex_admin_text('Viešas projekto raktas, skirtas įskiepio autorizacijai. Tai nėra OpenAI API key. Testavimo etape šį lauką galima palikti tuščią, jei backend jo dar netikrina.', 'Public project key used to authorize the plugin. This is not an OpenAI API key. During testing, this field may be left empty if the backend does not validate it yet.')) . '

'; } public function render_lead_recipients_field() { $settings = $this->get_settings(); $recipients = isset($settings['lead_recipients']) && is_array($settings['lead_recipients']) ? $settings['lead_recipients'] : []; if (empty($recipients)) { $recipients = ['']; } echo '
'; echo '
'; foreach ($recipients as $index => $recipient) { $remove_style = ($index === 0 && count($recipients) === 1) ? 'display:none;' : ''; echo '
'; echo ''; echo ''; echo '
'; } echo '
'; echo ''; echo '

' . esc_html(chatnex_admin_text('Kiekvienas gavėjas įvedamas atskiroje eilutėje.', 'Enter each recipient on a separate line.')) . '

'; echo '
'; } public function render_admin_language_field() { $settings = $this->get_settings(); $value = isset($settings['admin_language']) ? (string) $settings['admin_language'] : 'en'; ?>

get_settings(); $value = isset($settings['widget_language']) ? (string) $settings['widget_language'] : 'en'; echo ''; echo '

' . esc_html(chatnex_admin_text('Keičia ChatNEX sąsajos tekstus: būseną, laukelį, siuntimo mygtuką ir sistemos pranešimus.', 'Changes visitor-facing ChatNEX interface text: status, input field, send button and system messages.')) . '

'; } public function render_chat_title_field() { $settings = $this->get_settings(); echo ''; } public function render_helper_text_field() { $settings = $this->get_settings(); echo ''; } public function render_welcome_text_field() { $settings = $this->get_settings(); echo ''; } public function render_quick_questions_field() { $settings = $this->get_settings(); $q1 = $settings['quick_question_1'] ?? ''; $q2 = $settings['quick_question_2'] ?? ''; $q3 = $settings['quick_question_3'] ?? ''; echo '
'; echo ''; echo ''; echo '
'; echo ''; echo '
'; echo ''; echo '

' . esc_html(chatnex_admin_text('Galite nustatyti iki trijų greitų klausimų.', 'You can configure up to three quick questions.')) . '

'; echo '
'; } public function render_additional_ai_context_field() { $settings = $this->get_settings(); $value = isset($settings['additional_ai_context']) ? (string) $settings['additional_ai_context'] : ''; $placeholder = chatnex_admin_text("Pvz.\nStandartinį / pigiausią puslapį paruošiame per 4–5 darbo dienas.\nVerslo puslapį ir el. parduotuvę paruošiame iki 3 savaičių.\nLogotipas į standartinio puslapio kainą neįeina.\nDirbame visoje Lietuvoje.\nPerkant 10–24 vnt. taikoma 10 % nuolaida.", "Example:\nA standard website is completed within 4–5 business days.\nA business website or online store is completed within 3 weeks.\nLogo design is not included in the standard website price.\nWe work throughout Lithuania.\nA 10% discount applies to orders of 10–24 units."); echo '
'; echo '' . esc_html(chatnex_admin_text('Kaip pildyti, kad AI suprastų teisingai:', 'How to enter information so the AI interprets it correctly:')) . ''; echo ''; echo '

' . esc_html(chatnex_admin_text('Pavyzdžiai:', 'Examples:')) . '
'; echo esc_html(chatnex_admin_text('Standartinį puslapį paruošiame per 4–5 darbo dienas.', 'A standard website is completed within 4–5 business days.')) . '
'; echo esc_html(chatnex_admin_text('Verslo puslapį ir el. parduotuvę paruošiame iki 3 savaičių.', 'A business website or online store is completed within 3 weeks.')) . '
'; echo esc_html(chatnex_admin_text('Jei klientas klausia dėl individualaus 3D gaminio, paraginti susisiekti arba registruotis.', 'When a customer asks about a custom 3D product, encourage them to contact or register.')) . '
'; echo esc_html(chatnex_admin_text('Perkant 10–24 vnt. taikoma 10 % nuolaida.', 'A 10% discount applies to orders of 10–24 units.')) . '

'; echo '
'; echo ''; echo '

' . esc_html(chatnex_admin_text('Čia rašoma papildoma oficiali informacija AI atsakymams, kurios nėra puslapio turinyje. Šis tekstas siunčiamas į centrinį ChatNEX backend ir gali būti naudojamas atsakant lankytojams.', 'Enter official information that is not available on the website. It is sent to the central ChatNEX backend and may be used when answering visitors.')) . '

'; } private function hex_to_rgba($hex, $transparency_percent = 0) { $hex = ltrim((string) $hex, '#'); if (strlen($hex) === 3) { $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; } if (strlen($hex) !== 6) { return 'rgba(91,109,248,1)'; } $r = hexdec(substr($hex, 0, 2)); $g = hexdec(substr($hex, 2, 2)); $b = hexdec(substr($hex, 4, 2)); $transparency = max(0, min(100, ((int) $transparency_percent))); $a = 1 - ($transparency / 100); return sprintf('rgba(%d,%d,%d,%.3f)', $r, $g, $b, $a); } private function is_frontend_html_request() { if (is_admin()) { return false; } if (function_exists('wp_doing_ajax') && wp_doing_ajax()) { return false; } if (function_exists('wp_is_json_request') && wp_is_json_request()) { return false; } if (is_feed() || is_robots() || is_trackback()) { return false; } return true; } private function get_public_payload($settings) { return [ 'enabled' => (bool) $settings['enabled'], 'backendUrl' => 'https://chat.enex.lt', 'projectId' => $settings['project_id'], 'publicKey' => $settings['public_key'], 'leadRecipients' => array_values(array_filter(array_map('strval', (array) ($settings['lead_recipients'] ?? [])))), 'chatTitle' => $settings['chat_title'], 'helperText' => $settings['helper_text'], 'welcomeText' => $settings['welcome_text'], 'quickQuestions' => array_values(array_filter(array_map('trim', preg_split('/\r\n|\r|\n/', $settings['quick_questions'])))), 'additionalAiContext' => (string) ($settings['additional_ai_context'] ?? ''), 'design' => [ 'bubbleBg' => $this->hex_to_rgba($settings['bubble_color'], $settings['bubble_opacity']), 'bubbleSize' => max(44, min(96, (int) ($settings['bubble_size'] ?? 64))), 'bubbleIcon' => $settings['bubble_icon'], 'bubbleIconCustomUrl' => $settings['bubble_icon_custom_url'], 'headerBg' => $this->hex_to_rgba($settings['header_color'], $settings['header_opacity']), 'sendBg' => $this->hex_to_rgba($settings['send_button_color'], 0), 'chatBg' => $this->hex_to_rgba($settings['chat_background_color'], $settings['chat_background_opacity']), 'logoUrl' => $settings['logo_url'], 'avatarUrl' => $settings['avatar_url'], ], 'aiBehavior' => [ 'emoji_enabled' => !empty($settings['ai_emoji_enabled']), 'emoji_level' => (int) $settings['ai_emoji_level'], 'tone_style' => (string) $settings['ai_tone_style'], 'warmth_level' => (int) $settings['ai_warmth_level'], 'contact_stage_action' => (string) ($settings['ai_contact_stage_action'] ?? 'no_contact_flow'), 'contact_strategy_balance' => (int) ($settings['ai_contact_strategy_balance'] ?? $settings['ai_cta_level'] ?? 50), 'uncertainty_level' => (int) $settings['ai_uncertainty_level'], 'contact_offer_mode' => 'never', 'registration_offer_mode' => 'never', 'contact_trigger_after_ai_replies' => (int) ($settings['ai_contact_trigger_after_ai_replies'] ?? 0), ], 'siteUrl' => home_url('/'), 'widgetLanguage' => (string) ($settings['widget_language'] ?? 'lt'), 'pluginVersion' => CHATNEX_VERSION, 'pageUrl' => (is_singular() ? get_permalink() : home_url(add_query_arg([], $GLOBALS['wp']->request ?? ''))), 'strings' => (($settings['widget_language'] ?? 'lt') === 'en') ? [ 'inputPlaceholder' => 'Type your message...', 'send' => 'Send', 'online' => 'Online', 'demoMode' => 'Demo mode', 'helper' => 'Our AI assistant is here to help 🙂', 'hideTarget' => 'Drag here to hide', 'connecting' => 'Checking information...', 'checking' => 'Checking information...', 'error' => 'Could not get an answer. Please try again.', 'timeout' => 'The answer took too long. Please try again.', 'limitAlmostReached' => 'Message limit is almost reached: 1 message left.', 'rateLimitGeneric' => 'Message limit reached. Please try again later 🙂', 'rateLimitDaily' => 'Daily message limit reached. Please try again tomorrow 🙂', 'rateLimitMinute' => 'Too many requests. Please try again in a minute 🙂', 'rateLimitBurst' => 'Messages are being sent too quickly. Please wait a moment 🙂', 'rateLimitSession' => 'Conversation message limit reached. Please try again later 🙂', 'rateLimitDuplicate' => 'The same message is repeated too often. Please rephrase it 🙂', 'demoReply' => 'ChatNEX is connected. The backend is not configured yet, so a demo reply is shown 🙂' ] : [ 'inputPlaceholder' => 'Parašykite žinutę...', 'send' => 'Siųsti', 'online' => 'Prisijungęs', 'demoMode' => 'Demo režimas', 'helper' => 'Mūsų Dirbtinis intelektas Jums padės 🙂', 'hideTarget' => 'Nutempkite čia, kad paslėptumėte', 'connecting' => 'Tikrinama informacija...', 'checking' => 'Tikrinama informacija...', 'error' => 'Nepavyko gauti atsakymo. Bandykite dar kartą.', 'timeout' => 'Atsakymas užtruko per ilgai. Bandykite dar kartą.', 'limitAlmostReached' => 'Žinučių limitas beveik pasiektas, liko 1 žinutė!', 'rateLimitGeneric' => 'Pasiektas pranešimų limitas. Pabandykite vėliau 🙂', 'rateLimitDaily' => 'Pasiekėte dienos pranešimų limitą. Bandykite rytoj 🙂', 'rateLimitMinute' => 'Per daug užklausų per trumpą laiką. Pabandykite po minutės 🙂', 'rateLimitBurst' => 'Per greitai siunčiamos žinutės. Palaukite kelias sekundes 🙂', 'rateLimitSession' => 'Pasiekėte šio pokalbio pranešimų limitą. Pabandykite vėliau 🙂', 'rateLimitDuplicate' => 'Ta pati žinutė kartojama per dažnai. Pabandykite performuluoti 🙂', 'demoReply' => 'ChatNEX prijungtas. Backend dar nesukonfigūruotas, todėl rodomas demo atsakymas 🙂' ] ]; } private function get_inline_bootstrap_markup($settings) { $json_flags = JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT; $payload = wp_json_encode($this->get_public_payload($settings), $json_flags); $css_url = wp_json_encode(esc_url_raw(CHATNEX_PLUGIN_URL . 'public/chat.css?ver=' . CHATNEX_VERSION), $json_flags); $js_url = wp_json_encode(esc_url_raw(CHATNEX_PLUGIN_URL . 'public/chat.js?ver=' . CHATNEX_VERSION), $json_flags); $script = '(function(){ var data = ' . $payload . '; window.ChatNEXData = data; if (!document.getElementById("chatnex-root")) { var root = document.createElement("div"); root.id = "chatnex-root"; root.setAttribute("aria-live", "polite"); (document.body || document.documentElement).appendChild(root); } if (!document.getElementById("chatnex-public-css")) { var css = document.createElement("link"); css.id = "chatnex-public-css"; css.rel = "stylesheet"; css.href = ' . $css_url . '; (document.head || document.documentElement).appendChild(css); } if (!document.getElementById("chatnex-public-js")) { var js = document.createElement("script"); js.id = "chatnex-public-js"; js.src = ' . $js_url . '; js.defer = true; (document.body || document.documentElement).appendChild(js); } })();'; return '
' . wp_get_inline_script_tag($script, ['id' => 'chatnex-inline-loader']); } public function inject_chatnex_markup_into_html($html) { if (!chatnex_service_connected()) { return $html; } $settings = $this->get_settings(); if (empty($settings['enabled']) || !is_string($html) || trim($html) === '') { return $html; } $content_type = ''; foreach (headers_list() as $header) { if (stripos($header, 'Content-Type:') === 0) { $content_type = strtolower(trim(substr($header, strlen('Content-Type:')))); break; } } if ($content_type && strpos($content_type, 'text/html') === false) { return $html; } if (stripos($html, 'get_inline_bootstrap_markup($settings); if (stripos($html, '') !== false) { return preg_replace('/<\/body>/i', $markup . '', $html, 1); } return $html . $markup; } public function enqueue_admin_assets($hook) { if ($hook !== 'toplevel_page_chatnex' && $hook !== 'chatnex_page_chatnex-design' && $hook !== 'chatnex_page_chatnex-ai-behavior') { return; } wp_enqueue_media(); wp_enqueue_style('wp-color-picker'); wp_enqueue_script('wp-color-picker'); wp_enqueue_style( 'chatnex-admin', CHATNEX_PLUGIN_URL . 'admin/admin.css', [], CHATNEX_VERSION ); wp_enqueue_script( 'chatnex-admin', CHATNEX_PLUGIN_URL . 'admin/admin.js', ['jquery', 'wp-color-picker'], CHATNEX_VERSION, true ); $settings = $this->get_settings(); $admin_language = isset($settings['admin_language']) ? (string) $settings['admin_language'] : 'en'; $is_admin_en = $admin_language === 'en'; wp_localize_script('chatnex-admin', 'ChatNEXAdminData', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'autosaveNonce' => wp_create_nonce('chatnex_autosave_settings'), 'billingNonce' => wp_create_nonce('chatnex_billing_management'), 'adminLanguage' => $admin_language, 'autosave' => [ 'ready' => $is_admin_en ? 'Auto-save ready' : 'Auto-save paruoštas', 'waiting' => $is_admin_en ? 'Waiting...' : 'Laukiama...', 'saving' => $is_admin_en ? 'Saving...' : 'Saugoma...', 'saved' => $is_admin_en ? 'Saved' : 'Išsaugota', 'failed' => $is_admin_en ? 'Save failed' : 'Nepavyko išsaugoti' ], 'mediaTitle' => $is_admin_en ? 'Select an image' : 'Pasirinkite paveikslėlį', 'mediaButton' => $is_admin_en ? 'Use this image' : 'Naudoti paveikslėlį', 'avatarPresets' => array_map(function($item) { return $item['url']; }, $this->get_avatar_presets()), 'recipientTexts' => [ 'freeLimit' => $is_admin_en ? 'The Free plan allows only 1 recipient.' : 'Free versijoje galima nurodyti tik 1 gavėją.', 'addRecipient' => $is_admin_en ? 'Add recipient' : 'Pridėti gavėją', 'removeRecipient' => $is_admin_en ? 'Remove' : 'Pašalinti' ], 'projectMeta' => [ 'backendUrl' => 'https://chat.enex.lt', 'projectId' => chatnex_service_connected() ? (string) ($settings['project_id'] ?? '') : '', 'publicKey' => (string) ($settings['public_key'] ?? ''), 'loading' => $is_admin_en ? 'Loading central limit information...' : 'Kraunama centrinių limitų informacija...', 'missingProject' => $is_admin_en ? 'Enter the Project ID and save the settings to view central limits.' : 'Įveskite Projekto ID ir išsaugokite nustatymus, kad matytumėte centrinius limitus.', 'loadError' => $is_admin_en ? 'Could not load central limits from the backend.' : 'Nepavyko gauti centrinių limitų iš backend.', 'noLimit' => $is_admin_en ? 'unlimited' : 'be limito', 'day' => $is_admin_en ? 'day' : 'diena', 'week' => $is_admin_en ? 'week' : 'savaitė', 'month' => $is_admin_en ? 'month' : 'mėnuo', 'enabled' => $is_admin_en ? 'enabled' : 'įjungtas', 'disabled' => $is_admin_en ? 'disabled' : 'išjungtas', 'free' => 'free', 'premium' => 'premium' ], 'preview' => [ 'bubbleColor' => $settings['bubble_color'], 'bubbleOpacity' => (int) $settings['bubble_opacity'], 'bubbleSize' => max(44, min(96, (int) ($settings['bubble_size'] ?? 64))), 'headerColor' => $settings['header_color'], 'headerOpacity' => (int) $settings['header_opacity'], 'sendButtonColor' => $settings['send_button_color'], 'chatBackgroundColor' => $settings['chat_background_color'], 'chatBackgroundOpacity' => (int) $settings['chat_background_opacity'], 'logoUrl' => $settings['logo_url'], 'avatarUrl' => $settings['avatar_url'], ] ]); } public function render_bubble_color_field() { $settings = $this->get_settings(); echo ''; } public function render_bubble_opacity_field() { $settings = $this->get_settings(); echo ' ' . esc_html($settings['bubble_opacity']) . '%'; } public function render_bubble_size_field() { $settings = $this->get_settings(); $size = max(44, min(96, (int) ($settings['bubble_size'] ?? 64))); echo ' ' . esc_html($size) . ' px'; echo '

' . esc_html(chatnex_admin_text('Pasirinkite 44–96 px. Burbulas visada išlaikys taisyklingą apskritimo formą.', 'Choose 44–96 px. The bubble will always keep a perfect circular shape.')) . '

'; } public function render_bubble_icon_field() { $settings = $this->get_settings(); $current = $settings['bubble_icon'] ?? 'chat'; $custom_url = $settings['bubble_icon_custom_url'] ?? ''; $options = [ 'chat' => chatnex_admin_text('Chat burbulas', 'Chat bubble'), 'sparkles' => chatnex_admin_text('Žvaigždutės', 'Sparkles'), 'robot' => chatnex_admin_text('Robotas', 'Robot'), 'message' => chatnex_admin_text('Žinutė', 'Message'), 'custom' => chatnex_admin_text('Įkelti savo', 'Upload custom') ]; echo ''; echo '
'; echo ' '; echo ''; echo '

' . esc_html(chatnex_admin_text('Jei pasirinksite savo ikonėlę, rekomenduojama PNG arba SVG su skaidriu fonu.', 'For a custom icon, use a PNG or SVG with a transparent background.')) . '

'; echo '
'; } public function render_header_color_field() { $settings = $this->get_settings(); echo ''; } public function render_header_opacity_field() { $settings = $this->get_settings(); echo ' ' . esc_html($settings['header_opacity']) . '%'; } public function render_send_button_color_field() { $settings = $this->get_settings(); echo ''; } public function render_chat_background_color_field() { $settings = $this->get_settings(); echo ''; } public function render_chat_background_opacity_field() { $settings = $this->get_settings(); echo ' ' . esc_html($settings['chat_background_opacity']) . '%'; } public function render_logo_url_field() { $settings = $this->get_settings(); echo ' '; echo ''; } private function get_avatar_presets() { return [ 'ai-robot' => [ 'label' => chatnex_admin_text('AI robotas', 'AI robot'), 'url' => CHATNEX_PLUGIN_URL . 'assets/avatars/ai-robot.svg', ], 'assistant-female' => [ 'label' => chatnex_admin_text('Asistentė', 'Female assistant'), 'url' => CHATNEX_PLUGIN_URL . 'assets/avatars/assistant-female.svg', ], 'assistant-male' => [ 'label' => chatnex_admin_text('Konsultantas', 'Consultant'), 'url' => CHATNEX_PLUGIN_URL . 'assets/avatars/assistant-male.svg', ], 'assistant-minimal' => [ 'label' => 'Minimal AI', 'url' => CHATNEX_PLUGIN_URL . 'assets/avatars/assistant-minimal.svg', ], ]; } private function resolve_avatar_url($settings) { $preset = $settings['avatar_preset'] ?? 'ai-robot'; $custom = $settings['avatar_custom_url'] ?? ''; if ($preset === 'custom' && !empty($custom)) { return $custom; } $presets = $this->get_avatar_presets(); return $presets[$preset]['url'] ?? $presets['ai-robot']['url']; } public function render_avatar_preset_field() { $settings = $this->get_settings(); $current = $settings['avatar_preset'] ?? 'ai-robot'; $custom_url = $settings['avatar_custom_url'] ?? ''; $presets = $this->get_avatar_presets(); echo ''; echo '
'; echo ' '; echo ''; echo '

' . esc_html(chatnex_admin_text('Galite pasirinkti vieną iš pateiktų variantų arba įkelti savo paveikslėlį.', 'Choose one of the provided options or upload a custom image.')) . '

'; echo '
'; } public function render_ai_emoji_enabled_field() { $settings = $this->get_settings(); echo ''; } public function render_ai_emoji_level_field() { $settings = $this->get_settings(); echo ' ' . esc_html($settings['ai_emoji_level']) . ''; } public function render_ai_tone_style_field() { $settings = $this->get_settings(); $current = $settings['ai_tone_style'] ?? 'friendly'; $options = [ 'neutral' => chatnex_admin_text('Neutralus', 'Neutral'), 'friendly' => chatnex_admin_text('Draugiškas', 'Friendly'), 'warm' => chatnex_admin_text('Šiltas', 'Warm'), 'sales' => chatnex_admin_text('Pardaviminis', 'Sales-oriented'), ]; echo ''; } public function render_ai_warmth_level_field() { $settings = $this->get_settings(); echo ' ' . esc_html($settings['ai_warmth_level']) . ''; } public function render_ai_contact_stage_action_field() { $settings = $this->get_settings(); $value = (string) ($settings['ai_contact_stage_action'] ?? 'no_contact_flow'); if (!in_array($value, ['no_contact_flow', 'request_customer_contact', 'show_company_contacts', 'mixed_contact_flow'], true)) { $value = 'no_contact_flow'; } $requestChecked = in_array($value, ['request_customer_contact', 'mixed_contact_flow'], true); $showChecked = in_array($value, ['show_company_contacts', 'mixed_contact_flow'], true); $requestName = esc_attr($this->option_name) . '[ai_contact_stage_request_customer_contact]'; $showName = esc_attr($this->option_name) . '[ai_contact_stage_show_company_contacts]'; echo '
'; echo ''; echo ''; echo '
'; echo '

' . esc_html(chatnex_admin_text('Jei abi varnelės išjungtos – ChatNEX kontaktų nesiūlo. Jei įjungta viena – vykdo tik tą veiksmą. Jei įjungtos abi – AI gali pagal situaciją ir pateikti įmonės kontaktus, ir paprašyti kliento kontaktų.', 'If both options are disabled, ChatNEX does not initiate a contact action. If one is enabled, only that action is used. If both are enabled, the AI may provide company details or request customer details depending on the conversation.')) . '

'; } public function render_ai_uncertainty_level_field() { $settings = $this->get_settings(); echo ' ' . esc_html($settings['ai_uncertainty_level']) . ''; } public function render_ai_contact_offer_mode_field() { $settings = $this->get_settings(); $current = $settings['ai_contact_offer_mode'] ?? 'natural'; $options = [ 'never' => chatnex_admin_text('Nesiūlyti', 'Never'), 'natural' => chatnex_admin_text('Natūraliai', 'Naturally'), 'active' => chatnex_admin_text('Aktyviai', 'Actively'), ]; echo ''; } public function render_ai_registration_offer_mode_field() { $settings = $this->get_settings(); $current = $settings['ai_registration_offer_mode'] ?? 'natural'; $options = [ 'never' => chatnex_admin_text('Nesiūlyti', 'Never'), 'natural' => chatnex_admin_text('Natūraliai', 'Naturally'), 'active' => chatnex_admin_text('Aktyviai', 'Actively'), ]; echo ''; } public function render_ai_contact_trigger_after_ai_replies_field() { $settings = $this->get_settings(); $value = (int) ($settings['ai_contact_trigger_after_ai_replies'] ?? 0); if ($value < 0) $value = 0; if ($value > 10) $value = 10; echo ' ' . esc_html($value) . ''; echo '

' . esc_html(chatnex_admin_text('0 = priverstinis kontaktų etapas išjungtas. Jei reikšmė didesnė nei 0, ChatNEX po tiek AI atsakymų privalomai atliks aukščiau pasirinktą kontaktų veiksmą. Iki tol AI kontaktus siūlo tik pagal pokalbio situaciją arba kai lankytojas pats prašo.', '0 disables forced contact actions. A value above 0 makes ChatNEX perform the selected contact action after that many AI replies. Before then, contacts are offered only when appropriate or when requested by the visitor.')) . '

'; } public function render_design_page() { $settings = $this->get_settings(); include CHATNEX_PLUGIN_DIR . 'admin/design-page.php'; } public function render_ai_behavior_page() { $settings = $this->get_settings(); include CHATNEX_PLUGIN_DIR . 'admin/ai-behavior-page.php'; } public function render_settings_page() { $settings = $this->get_settings(); include CHATNEX_PLUGIN_DIR . 'admin/settings-page.php'; } public function enqueue_public_assets() { if (!chatnex_service_connected()) { return; } if (is_admin()) { return; } $settings = $this->get_settings(); if (empty($settings['enabled'])) { return; } wp_enqueue_style( 'chatnex-public', CHATNEX_PLUGIN_URL . 'public/chat.css', [], CHATNEX_VERSION ); wp_enqueue_script( 'chatnex-public', CHATNEX_PLUGIN_URL . 'public/chat.js', [], CHATNEX_VERSION, true ); $payload = $this->get_public_payload($settings); wp_localize_script('chatnex-public', 'ChatNEXData', $payload); } public function render_chat_markup() { if (!chatnex_service_connected()) { return; } $settings = $this->get_settings(); if (empty($settings['enabled'])) { return; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- Helper uses JSON_HEX_* for data and wp_get_inline_script_tag() for script markup; URLs are sanitized before JSON encoding. echo $this->get_inline_bootstrap_markup($settings); } } register_activation_hook(CHATNEX_PLUGIN_FILE, ['ChatNEX_Plugin', 'mark_auto_register_pending']); new ChatNEX_Plugin();