PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.1.3
MxChat – AI Chatbot & Content Generation for WordPress v3.1.3
3.2.21 3.2.20 3.2.19 3.2.18 3.2.17 3.2.16 3.2.15 3.2.14 3.2.12 3.2.13 3.2.11 3.2.10 3.2.9 3.2.8 3.2.7 3.2.6 3.2.5 3.2.4 3.2.3 3.2.2 3.2.1 2.0.3 2.0.4 2.0.5 2.0.6 All 152 releases
mxchat-basic / includes / class-mxchat-admin.php

class-mxchat-admin.php in MxChat – AI Chatbot & Content Generation for WordPress 3.1.3, at includes/class-mxchat-admin.php

8,332 lines 339.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) {
3 exit; // Exit if accessed directly
4 }
5
6 class MxChat_Admin {
7 private $options;
8 private $chat_count;
9 private $is_activated;
10 private $knowledge_manager;
11
12 /**
13 * Get whether the license is activated
14 * @return bool
15 */
16 public function is_activated() {
17 return $this->is_activated;
18 }
19
20 /**
21 * Get plugin options
22 * @return array
23 */
24 public function get_options() {
25 return $this->options;
26 }
27
28 public function __construct($knowledge_manager = null) {
29 $this->options = get_option('mxchat_options');
30 $this->chat_count = get_option('mxchat_chat_count', 0);
31 $this->is_activated = $this->is_license_active();
32 $this->knowledge_manager = $knowledge_manager;
33
34 // Initialize default options if they are not set
35 if (!$this->options) {
36 $this->initialize_default_options();
37 }
38
39 // Add admin menu and initialize settings
40 add_action('admin_menu', array($this, 'mxchat_add_plugin_page'));
41 add_action('admin_init', array($this, 'mxchat_page_init'));
42 add_action('admin_init', array($this, 'mxchat_prompts_page_init'));
43 add_action('admin_enqueue_scripts', array($this, 'mxchat_enqueue_admin_assets'));
44 add_action('wp_ajax_mxchat_delete_chat_history', array($this, 'mxchat_delete_chat_history'));
45 add_action('admin_post_mxchat_delete_prompt', array($this, 'mxchat_handle_delete_prompt'));
46 add_action('wp_ajax_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
47 add_action('wp_ajax_nopriv_mxchat_fetch_chat_history', array($this, 'mxchat_fetch_chat_history'));
48 add_action('wp_ajax_mxchat_fetch_conversation', array($this, 'mxchat_fetch_conversation'));
49 add_action('wp_footer', array($this, 'mxchat_append_chatbot_to_body'));
50 add_action('admin_head-mxchat-prompts', array($this, 'mxchat_enqueue_admin_assets'));
51 add_action('admin_head-toplevel_page_mxchat-max', array($this, 'mxchat_enqueue_admin_assets'));
52 add_action('admin_notices', array($this, 'mxchat_display_admin_notice'));
53 add_action('admin_post_mxchat_delete_all_prompts', array($this, 'mxchat_handle_delete_all_prompts'));
54 add_action('admin_post_mxchat_add_intent', array($this, 'mxchat_handle_add_intent'));
55 add_action('admin_post_mxchat_delete_intent', array($this, 'mxchat_handle_delete_intent'));
56 add_action('admin_post_mxchat_edit_intent', array($this, 'mxchat_handle_edit_intent'));
57 add_action('wp_ajax_mxchat_export_transcripts', array($this, 'export_chat_transcripts'));
58 add_action('admin_init', array($this, 'mxchat_transcripts_page_init'));
59 add_action('wp_ajax_dismiss_live_agent_notice', array($this, 'dismiss_live_agent_notice'));
60 add_action('wp_ajax_dismiss_theme_migration_notice', array($this, 'dismiss_theme_migration_notice'));
61 add_action('mxchat_cleanup_old_transcripts', array($this, 'cleanup_old_transcripts'));
62
63 add_action('admin_init', array($this, 'register_pinecone_settings'));
64 add_action('admin_init', array($this, 'register_openai_vectorstore_settings'));
65
66 add_action('admin_notices', array($this, 'display_admin_notices'));
67
68 add_action('wp_ajax_mxchat_test_streaming_actual', [$this, 'mxchat_handle_test_streaming_actual']);
69 add_action('wp_ajax_mxchat_test_streaming', [$this, 'mxchat_handle_test_streaming']); // Keep existing as fallback
70 add_action('wp_ajax_mxchat_test_vectorstore_connection', array($this, 'mxchat_test_vectorstore_connection'));
71
72 add_action('wp_ajax_mxchat_save_selected_bot', array($this, 'mxchat_save_selected_bot'));
73 add_action('wp_ajax_mxchat_fetch_openrouter_models', array($this, 'fetch_openrouter_models'));
74 add_action('wp_ajax_mxchat_get_rag_context', array($this, 'mxchat_get_rag_context'));
75
76 // Actions page AJAX handlers
77 add_action('wp_ajax_mxchat_fetch_actions_list', array($this, 'mxchat_fetch_actions_list'));
78 add_action('wp_ajax_mxchat_toggle_action_status', array($this, 'mxchat_toggle_action_status'));
79 add_action('wp_ajax_mxchat_bulk_delete_actions', array($this, 'mxchat_bulk_delete_actions'));
80 add_action('wp_ajax_mxchat_add_intent_ajax', array($this, 'mxchat_add_intent_ajax'));
81 add_action('wp_ajax_mxchat_edit_intent_ajax', array($this, 'mxchat_edit_intent_ajax'));
82 add_action('wp_ajax_mxchat_delete_intent_ajax', array($this, 'mxchat_delete_intent_ajax'));
83
84 // Slack test connection
85 add_action('wp_ajax_mxchat_test_slack_connection', array($this, 'mxchat_test_slack_connection'));
86
87 // Translation handlers
88 add_action('wp_ajax_mxchat_translate_messages', array($this, 'mxchat_translate_messages'));
89 add_action('wp_ajax_mxchat_get_transcript_translation', array($this, 'mxchat_get_transcript_translation'));
90 }
91
92 private function is_license_active() {
93 // Get the raw value without translation
94 $license_status = get_option('mxchat_license_status', 'inactive');
95
96 // Check against multiple possible values, bypassing translation issues
97 return ($license_status === 'active' || $license_status === esc_html__('active', 'mxchat'));
98 }
99
100 private function initialize_default_options() {
101 $default_options = array(
102 'api_key' => '',
103 'xai_api_key' => '',
104 'claude_api_key' => '',
105 'deepseek_api_key' => '',
106 'voyage_api_key' => '',
107 'gemini_api_key' => '',
108 'enable_streaming_toggle' => 'off',
109 'enable_web_search' => 'off',
110 'embedding_model' => 'text-embedding-ada-002',
111 'system_prompt_instructions' => 'You are an AI Chatbot assistant for this website. Your main goal is to assist visitors with questions and provide helpful information. Here are your key guidelines:
112
113 # Response Style - CRITICALLY IMPORTANT
114 - MAXIMUM LENGTH: 1-3 short sentences per response
115 - Ultra-concise: Get straight to the answer with no filler
116 - No introductions like "Sure!" or "I\'d be happy to help"
117 - No phrases like "based on my knowledge" or "according to information"
118 - No explanatory text before giving the answer
119 - No summaries or repetition
120 - Hyperlink all URLs
121 - Respond in user\'s language
122 - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
123
124 # Knowledge Base Requirements - PREVENT HALLUCINATIONS
125 - ONLY answer questions using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
126 - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
127 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
128 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
129 - Better to admit insufficient information than provide inaccurate answers',
130 'model' => esc_html__('gpt-5.1-chat-latest', 'mxchat'),
131 'rate_limit_logged_out' => esc_html__('100', 'mxchat'),
132 'role_rate_limits' => array(),
133 'rate_limit_message' => esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
134 'enable_email_block' => '',
135 'email_blocker_header_content' => __("<h2>Welcome to Our Chat!</h2>\n<p>Let's get started. Enter your email to begin chatting with us.</p>", 'mxchat'),
136 'email_blocker_button_text' => esc_html__('Start Chat', 'mxchat'),
137 'enable_name_field' => 'off', // NEW
138 'name_field_placeholder' => esc_html__('Enter your name', 'mxchat'), // NEW
139 'top_bar_title' => esc_html__('MxChat', 'mxchat'),
140 'intro_message' => __('Hello! How can I assist you today?', 'mxchat'),
141 'ai_agent_text' => esc_html__('AI Agent', 'mxchat'),
142 'input_copy' => esc_html__('How can I assist?', 'mxchat'),
143 'append_to_body' => esc_html__('off', 'mxchat'),
144 'post_type_visibility_mode' => 'all', // 'all', 'include', 'exclude'
145 'post_type_visibility_list' => array(), // Array of post type slugs
146 'contextual_awareness_toggle' => 'off',
147 'citation_links_toggle' => 'on',
148 'show_frontend_debugger' => 'on',
149 'close_button_color' => esc_html__('#fff', 'mxchat'),
150 'chatbot_bg_color' => esc_html__('#fff', 'mxchat'),
151 'user_message_bg_color' => esc_html__('#fff', 'mxchat'),
152 'user_message_font_color' => esc_html__('#212121', 'mxchat'),
153 'bot_message_bg_color' => esc_html__('#212121', 'mxchat'),
154 'bot_message_font_color' => esc_html__('#fff', 'mxchat'),
155 'top_bar_bg_color' => esc_html__('#212121', 'mxchat'),
156 'send_button_font_color' => esc_html__('#212121', 'mxchat'),
157 'chat_input_font_color' => esc_html__('#212121', 'mxchat'),
158 'chatbot_background_color' => esc_html__('#212121', 'mxchat'),
159 'icon_color' => esc_html__('#fff', 'mxchat'),
160 'enable_woocommerce_integration' => esc_html__('0', 'mxchat'),
161 'link_target_toggle' => esc_html__('off', 'mxchat'),
162 'pre_chat_message' => esc_html__('Hey there! Ask me anything!', 'mxchat'),
163
164 // New fields for Loops Integration
165 'loops_api_key' => '',
166 'loops_mailing_list' => '',
167 'triggered_phrase_response' => __('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
168 'email_capture_response' => __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
169 'popular_question_1' => '',
170 'popular_question_2' => '',
171 'popular_question_3' => '',
172 'pdf_intent_trigger_text' => __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
173 'pdf_intent_success_text' => __("I've processed the PDF. What questions do you have about it?", 'mxchat'),
174 'pdf_intent_error_text' => __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
175 'pdf_max_pages' => 69,
176 'show_pdf_upload_button' => 'on',
177 'show_word_upload_button' => 'on',
178
179 // Live Agent Integration (Slack)
180 'live_agent_webhook_url' => '',
181 'live_agent_secret_key' => '',
182 'live_agent_bot_token' => '',
183 'live_agent_message_bg_color' => esc_html__('#ffffff', 'mxchat'),
184 'live_agent_message_font_color' => esc_html__('#333333', 'mxchat'),
185
186 // Telegram Integration
187 'telegram_status' => 'off',
188 'telegram_bot_token' => '',
189 'telegram_group_id' => '',
190 'telegram_webhook_secret' => '',
191 'telegram_notification_message' => __("I've notified a support agent. Please allow a moment for them to respond. If you'd like to continue with AI, just type \"Switch to AI\" at any time.", 'mxchat'),
192 'telegram_away_message' => __("I just checked, and it looks like our support team isn't personally available at the moment. If you'd like, you can leave your email address, and they'll get back to you as soon as possible. In the meantime, you can keep chatting with me — just let me know how I can help!", 'mxchat'),
193
194 'chat_toolbar_toggle' => esc_html__('off', 'mxchat'),
195 'mode_indicator_bg_color' => esc_html__('#767676', 'mxchat'),
196 'mode_indicator_font_color' => esc_html__('#ffffff', 'mxchat'),
197 'toolbar_icon_color' => esc_html__('#212121', 'mxchat'),
198
199 // Optimization settings
200 'script_loading_strategy' => 'default',
201
202 // Debug settings
203 'debug_mode' => 'off',
204 );
205
206
207 // Merge existing options with defaults
208 $existing_options = get_option('mxchat_options', array());
209 $merged_options = wp_parse_args($existing_options, $default_options);
210
211 // Update the options if they have changed
212 if ($existing_options !== $merged_options) {
213 update_option('mxchat_options', $merged_options);
214 }
215
216 // Add default limits for each role
217 $roles = wp_roles()->get_names();
218 foreach ($roles as $role_id => $role_name) {
219 $default_options['role_rate_limits'][$role_id] = esc_html__('100', 'mxchat');
220 }
221
222 return $default_options;
223
224 // Update the $this->options property
225 $this->options = $merged_options;
226 }
227
228 public function mxchat_add_plugin_page() {
229 // Main menu page
230 add_menu_page(
231 esc_html__('MxChat Settings', 'mxchat'),
232 esc_html__('MxChat', 'mxchat'),
233 'manage_options',
234 'mxchat-max',
235 array($this, 'mxchat_create_admin_page'),
236 'dashicons-testimonial',
237 6
238 );
239
240 // Rename the first submenu item from "MxChat" to "Settings"
241 add_submenu_page(
242 'mxchat-max',
243 esc_html__('MxChat Settings', 'mxchat'),
244 esc_html__('Settings', 'mxchat'),
245 'manage_options',
246 'mxchat-max',
247 array($this, 'mxchat_create_admin_page')
248 );
249
250 // Submenu page for Knowledge
251 add_submenu_page(
252 'mxchat-max',
253 esc_html__('Prompts', 'mxchat'),
254 esc_html__('Knowledge', 'mxchat'),
255 'manage_options',
256 'mxchat-prompts',
257 array($this, 'mxchat_create_prompts_page')
258 );
259
260 add_submenu_page(
261 'mxchat-max',
262 esc_html__('Chat Transcripts', 'mxchat'),
263 esc_html__('Transcripts', 'mxchat'),
264 'manage_options',
265 'mxchat-transcripts',
266 array($this, 'mxchat_create_transcripts_page')
267 );
268
269 add_submenu_page(
270 'mxchat-max',
271 esc_html__('MxChat Actions', 'mxchat'),
272 esc_html__('Actions', 'mxchat'),
273 'manage_options',
274 'mxchat-actions',
275 array($this, 'mxchat_actions_page_html')
276 );
277
278 // Content Generator page
279 add_submenu_page(
280 'mxchat-max',
281 esc_html__('Content', 'mxchat'),
282 esc_html__('Content', 'mxchat'),
283 'manage_options',
284 'mxchat-content',
285 array($this, 'mxchat_create_content_page')
286 );
287
288 // Consolidated Pro & Extensions page (replaces separate Add Ons and Pro Upgrade pages)
289 add_submenu_page(
290 'mxchat-max',
291 esc_html__('Pro & Extensions', 'mxchat'),
292 esc_html__('Pro & Extensions', 'mxchat'),
293 'manage_options',
294 'mxchat-activation',
295 array($this, 'mxchat_create_activation_page')
296 );
297 }
298
299 public function mxchat_create_addons_page() {
300 require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
301 $addons_page = new MxChat_Addons();
302 $addons_page->render_page();
303 }
304
305 /**
306 * Render the Content Generator admin page
307 */
308 public function mxchat_create_content_page() {
309 require_once plugin_dir_path(__FILE__) . 'admin-content-page.php';
310 mxchat_render_content_page($this);
311 }
312
313 /**
314 * Test actual streaming functionality in the WordPress environment
315 */
316 public function mxchat_handle_test_streaming_actual() {
317 check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
318
319 // Check if headers have already been sent
320 if (headers_sent()) {
321 wp_send_json_error(['message' => 'Headers already sent - streaming not possible']);
322 return;
323 }
324
325 // Check for required functions
326 if (!function_exists('curl_init')) {
327 wp_send_json_error(['message' => 'cURL not available - streaming requires cURL']);
328 return;
329 }
330
331 // Get user's selected model and API key
332 $options = get_option('mxchat_options', []);
333 $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
334
335 // Get the provider from the model
336 $model_parts = explode('-', $selected_model);
337 $provider = strtolower($model_parts[0]);
338
339 // Get the appropriate API key
340 $api_key = '';
341 switch ($provider) {
342 case 'gpt':
343 case 'o1':
344 $api_key = $options['api_key'] ?? '';
345 break;
346 case 'claude':
347 $api_key = $options['claude_api_key'] ?? '';
348 break;
349 case 'grok':
350 $api_key = $options['xai_api_key'] ?? '';
351 break;
352 case 'deepseek':
353 $api_key = $options['deepseek_api_key'] ?? '';
354 break;
355 case 'gemini':
356 $api_key = $options['gemini_api_key'] ?? '';
357 break;
358 default:
359 // Default to OpenAI for unknown models
360 $api_key = $options['api_key'] ?? '';
361 $provider = 'gpt';
362 break;
363 }
364
365 if (empty($api_key)) {
366 wp_send_json_error(['message' => "API key not configured for {$provider} provider"]);
367 return;
368 }
369
370 // Test streaming with the selected model and provider
371 try {
372 $this->perform_streaming_test($provider, $selected_model, $api_key);
373 } catch (Exception $e) {
374 wp_send_json_error(['message' => 'Streaming test exception: ' . $e->getMessage()]);
375 }
376 }
377
378 /**
379 * Perform the actual streaming test
380 */
381 private function perform_streaming_test($provider, $model, $api_key) {
382 // Set streaming headers
383 header('Content-Type: text/event-stream');
384 header('Cache-Control: no-cache');
385 header('X-Accel-Buffering: no'); // Disable nginx buffering
386
387 // Prepare test message
388 $test_message = "Please respond with exactly: 'Streaming test successful!' - send this as a short response for testing.";
389
390 // Configure API request based on provider
391 $url = '';
392 $headers = [];
393 $body = [];
394
395 switch ($provider) {
396 case 'gpt':
397 case 'o1':
398 $url = 'https://api.openai.com/v1/chat/completions';
399 $headers = [
400 'Content-Type: application/json',
401 'Authorization: Bearer ' . $api_key
402 ];
403 $body = [
404 'model' => $model,
405 'messages' => [['role' => 'user', 'content' => $test_message]],
406 'max_tokens' => 50,
407 'temperature' => 0.3,
408 'stream' => true
409 ];
410 break;
411
412 case 'claude':
413 $url = 'https://api.anthropic.com/v1/messages';
414 $headers = [
415 'Content-Type: application/json',
416 'x-api-key: ' . $api_key,
417 'anthropic-version: 2023-06-01'
418 ];
419 $body = [
420 'model' => $model,
421 'messages' => [['role' => 'user', 'content' => $test_message]],
422 'max_tokens' => 50,
423 'temperature' => 0.3,
424 'stream' => true
425 ];
426 break;
427
428 case 'grok':
429 $url = 'https://api.x.ai/v1/chat/completions';
430 $headers = [
431 'Content-Type: application/json',
432 'Authorization: Bearer ' . $api_key
433 ];
434 $body = [
435 'model' => $model,
436 'messages' => [['role' => 'user', 'content' => $test_message]],
437 'max_tokens' => 50,
438 'temperature' => 0.3,
439 'stream' => true
440 ];
441 break;
442
443 case 'deepseek':
444 $url = 'https://api.deepseek.com/v1/chat/completions';
445 $headers = [
446 'Content-Type: application/json',
447 'Authorization: Bearer ' . $api_key
448 ];
449 $body = [
450 'model' => $model,
451 'messages' => [['role' => 'user', 'content' => $test_message]],
452 'max_tokens' => 50,
453 'temperature' => 0.3,
454 'stream' => true
455 ];
456 break;
457
458 default:
459 echo "data: " . json_encode(['error' => 'Unsupported provider for streaming test: ' . $provider]) . "\n\n";
460 flush();
461 return;
462 }
463
464 // Initialize cURL
465 $ch = curl_init();
466 curl_setopt($ch, CURLOPT_URL, $url);
467 curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
468 curl_setopt($ch, CURLOPT_POST, true);
469 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
470 curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
471 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
472 curl_setopt($ch, CURLOPT_TIMEOUT, 30);
473 curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($ch, $data) use ($provider) {
474 return $this->process_streaming_test_data($data, $provider);
475 });
476
477 // Send initial test message
478 echo "data: " . json_encode(['content' => '[Starting streaming test...]']) . "\n\n";
479 flush();
480
481 $result = curl_exec($ch);
482 $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
483 $curl_error = curl_error($ch);
484 curl_close($ch);
485
486 if ($curl_error) {
487 echo "data: " . json_encode(['error' => 'cURL Error: ' . $curl_error]) . "\n\n";
488 flush();
489 return;
490 }
491
492 if ($http_code !== 200) {
493 echo "data: " . json_encode(['error' => 'API returned HTTP ' . $http_code]) . "\n\n";
494 flush();
495 return;
496 }
497
498 // Send completion signal
499 echo "data: [DONE]\n\n";
500 flush();
501 }
502
503 /**
504 * Process streaming data for the test
505 */
506 private function process_streaming_test_data($data, $provider) {
507 static $chunk_count = 0;
508
509 $lines = explode("\n", $data);
510
511 foreach ($lines as $line) {
512 if (trim($line) === '') {
513 continue;
514 }
515
516 // Handle different provider formats
517 if ($provider === 'claude') {
518 // Claude uses event: and data: format
519 if (strpos($line, 'data: ') === 0) {
520 $json_str = substr($line, 6);
521 $json = json_decode($json_str, true);
522
523 if (isset($json['type']) && $json['type'] === 'content_block_delta') {
524 if (isset($json['delta']['text'])) {
525 $chunk_count++;
526 echo "data: " . json_encode([
527 'content' => $json['delta']['text'],
528 'test_chunk' => $chunk_count
529 ]) . "\n\n";
530 flush();
531 }
532 }
533 }
534 } else {
535 // OpenAI, X.AI, DeepSeek format
536 if (strpos($line, 'data: ') === 0) {
537 $json_str = substr($line, 6);
538
539 if ($json_str === '[DONE]') {
540 // Don't echo [DONE] here, let the main function handle it
541 continue;
542 }
543
544 $json = json_decode($json_str, true);
545 if (isset($json['choices'][0]['delta']['content'])) {
546 $chunk_count++;
547 echo "data: " . json_encode([
548 'content' => $json['choices'][0]['delta']['content'],
549 'test_chunk' => $chunk_count
550 ]) . "\n\n";
551 flush();
552 }
553 }
554 }
555 }
556
557 return strlen($data);
558 }
559
560 /**
561 * Updated version of your existing test method (keep this as a fallback)
562 */
563 public function mxchat_handle_test_streaming() {
564 check_ajax_referer('mxchat_test_streaming_nonce', 'nonce');
565
566 // Use the actual streaming test instead
567 $this->mxchat_handle_test_streaming_actual();
568 }
569
570
571 public function register_pinecone_settings() {
572 register_setting(
573 'mxchat_pinecone_addon_options',
574 'mxchat_pinecone_addon_options',
575 array(
576 'type' => 'array',
577 'sanitize_callback' => array($this, 'sanitize_pinecone_settings'),
578 'default' => array(
579 'mxchat_use_pinecone' => '0',
580 'mxchat_pinecone_api_key' => '',
581 'mxchat_pinecone_host' => '',
582 'mxchat_pinecone_index' => '',
583 'mxchat_pinecone_environment' => ''
584 )
585 )
586 );
587 }
588
589 public function sanitize_pinecone_settings($input) {
590 $sanitized = array();
591
592 $sanitized['mxchat_use_pinecone'] = isset($input['mxchat_use_pinecone']) ? '1' : '0';
593 $sanitized['mxchat_pinecone_api_key'] = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
594 $sanitized['mxchat_pinecone_host'] = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
595 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
596 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
597
598 // Remove https:// from host if present
599 $sanitized['mxchat_pinecone_host'] = str_replace(['https://', 'http://'], '', $sanitized['mxchat_pinecone_host']);
600
601 return $sanitized;
602 }
603
604 public function register_openai_vectorstore_settings() {
605 register_setting(
606 'mxchat_openai_vectorstore_options',
607 'mxchat_openai_vectorstore_options',
608 array(
609 'type' => 'array',
610 'sanitize_callback' => array($this, 'sanitize_openai_vectorstore_settings'),
611 'default' => array(
612 'mxchat_use_openai_vectorstore' => '0',
613 'mxchat_vectorstore_ids' => '',
614 'mxchat_vectorstore_max_results' => 5
615 )
616 )
617 );
618 }
619
620 public function sanitize_openai_vectorstore_settings($input) {
621 $sanitized = array();
622
623 $sanitized['mxchat_use_openai_vectorstore'] = isset($input['mxchat_use_openai_vectorstore']) ? '1' : '0';
624 $sanitized['mxchat_vectorstore_ids'] = sanitize_text_field($input['mxchat_vectorstore_ids'] ?? '');
625 $sanitized['mxchat_vectorstore_max_results'] = absint($input['mxchat_vectorstore_max_results'] ?? 5);
626
627 // Ensure max results is within reasonable range
628 if ($sanitized['mxchat_vectorstore_max_results'] < 1) {
629 $sanitized['mxchat_vectorstore_max_results'] = 1;
630 }
631 if ($sanitized['mxchat_vectorstore_max_results'] > 20) {
632 $sanitized['mxchat_vectorstore_max_results'] = 20;
633 }
634
635 return $sanitized;
636 }
637
638 /**
639 * AJAX handler to test OpenAI Vector Store connection
640 */
641 public function mxchat_test_vectorstore_connection() {
642 check_ajax_referer('mxchat_admin_nonce', 'nonce');
643
644 if (!current_user_can('manage_options')) {
645 wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
646 return;
647 }
648
649 $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
650 $vectorstore_ids = $vectorstore_options['mxchat_vectorstore_ids'] ?? '';
651 $mxchat_options = get_option('mxchat_options', array());
652 $api_key = $mxchat_options['api_key'] ?? '';
653
654 if (empty($api_key)) {
655 wp_send_json_error(array('message' => __('OpenAI API key is not configured.', 'mxchat')));
656 return;
657 }
658
659 if (empty($vectorstore_ids)) {
660 wp_send_json_error(array('message' => __('No Vector Store ID configured.', 'mxchat')));
661 return;
662 }
663
664 // Get the first Vector Store ID for testing
665 $ids_array = array_map('trim', explode(',', $vectorstore_ids));
666 $test_id = $ids_array[0];
667
668 // Test by retrieving the Vector Store info
669 $response = wp_remote_get(
670 'https://api.openai.com/v1/vector_stores/' . $test_id,
671 array(
672 'headers' => array(
673 'Authorization' => 'Bearer ' . $api_key,
674 'Content-Type' => 'application/json',
675 'OpenAI-Beta' => 'assistants=v2'
676 ),
677 'timeout' => 30
678 )
679 );
680
681 if (is_wp_error($response)) {
682 wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $response->get_error_message()));
683 return;
684 }
685
686 $status_code = wp_remote_retrieve_response_code($response);
687 $body = json_decode(wp_remote_retrieve_body($response), true);
688
689 if ($status_code === 200 && isset($body['id'])) {
690 $file_count = $body['file_counts']['completed'] ?? 0;
691 $name = $body['name'] ?? $test_id;
692 wp_send_json_success(array(
693 'message' => sprintf(
694 __('Connected successfully! Vector Store: %s (%d files)', 'mxchat'),
695 esc_html($name),
696 $file_count
697 )
698 ));
699 } elseif ($status_code === 404) {
700 wp_send_json_error(array('message' => __('Vector Store not found. Please check the ID.', 'mxchat')));
701 } elseif ($status_code === 401) {
702 wp_send_json_error(array('message' => __('Invalid API key.', 'mxchat')));
703 } else {
704 $error_message = $body['error']['message'] ?? __('Unknown error occurred.', 'mxchat');
705 wp_send_json_error(array('message' => $error_message));
706 }
707 }
708
709 /**
710 * AJAX handler to test Slack connection and validate scopes
711 */
712 public function mxchat_test_slack_connection() {
713 check_ajax_referer('mxchat_admin_nonce', 'nonce');
714
715 if (!current_user_can('manage_options')) {
716 wp_send_json_error(array('message' => __('Permission denied.', 'mxchat')));
717 return;
718 }
719
720 $bot_token = $this->options['live_agent_bot_token'] ?? '';
721
722 if (empty($bot_token)) {
723 wp_send_json_error(array('message' => __('Slack Bot Token is not configured. Please enter your token and save settings first.', 'mxchat')));
724 return;
725 }
726
727 // Test 1: Validate bot token with auth.test
728 $auth_response = wp_remote_post('https://slack.com/api/auth.test', array(
729 'headers' => array(
730 'Authorization' => 'Bearer ' . $bot_token,
731 'Content-Type' => 'application/json'
732 ),
733 'timeout' => 15
734 ));
735
736 if (is_wp_error($auth_response)) {
737 wp_send_json_error(array('message' => __('Connection failed: ', 'mxchat') . $auth_response->get_error_message()));
738 return;
739 }
740
741 $auth_body = json_decode(wp_remote_retrieve_body($auth_response), true);
742
743 if (!isset($auth_body['ok']) || !$auth_body['ok']) {
744 $error = $auth_body['error'] ?? 'unknown_error';
745 $error_messages = array(
746 'invalid_auth' => __('Invalid bot token. Please check your token starts with xoxb-', 'mxchat'),
747 'not_authed' => __('No authentication token provided.', 'mxchat'),
748 'account_inactive' => __('The Slack workspace has been deactivated.', 'mxchat'),
749 'token_revoked' => __('The bot token has been revoked. Please generate a new one.', 'mxchat'),
750 );
751 $message = $error_messages[$error] ?? sprintf(__('Authentication failed: %s', 'mxchat'), $error);
752 wp_send_json_error(array('message' => $message));
753 return;
754 }
755
756 $team_name = $auth_body['team'] ?? 'Unknown Workspace';
757 $bot_name = $auth_body['user'] ?? 'Unknown Bot';
758
759 // Test 2: Check if we can list channels (tests channels:read scope)
760 $channels_response = wp_remote_post('https://slack.com/api/conversations.list', array(
761 'headers' => array(
762 'Authorization' => 'Bearer ' . $bot_token,
763 'Content-Type' => 'application/json'
764 ),
765 'body' => json_encode(array('limit' => 1)),
766 'timeout' => 15
767 ));
768
769 $channels_body = json_decode(wp_remote_retrieve_body($channels_response), true);
770 $can_read_channels = isset($channels_body['ok']) && $channels_body['ok'];
771
772 // Test 3: Check if we can create channels (tests channels:manage scope)
773 // We'll just check the error message without actually creating
774 $create_response = wp_remote_post('https://slack.com/api/conversations.create', array(
775 'headers' => array(
776 'Authorization' => 'Bearer ' . $bot_token,
777 'Content-Type' => 'application/json'
778 ),
779 'body' => json_encode(array('name' => 'mxchat-test-' . time(), 'is_private' => false)),
780 'timeout' => 15
781 ));
782
783 $create_body = json_decode(wp_remote_retrieve_body($create_response), true);
784
785 // If channel was created, delete it immediately
786 if (isset($create_body['ok']) && $create_body['ok'] && isset($create_body['channel']['id'])) {
787 wp_remote_post('https://slack.com/api/conversations.archive', array(
788 'headers' => array(
789 'Authorization' => 'Bearer ' . $bot_token,
790 'Content-Type' => 'application/json'
791 ),
792 'body' => json_encode(array('channel' => $create_body['channel']['id'])),
793 'timeout' => 15
794 ));
795 $can_create_channels = true;
796 } else {
797 $create_error = $create_body['error'] ?? '';
798 // name_taken means we have permission but channel exists
799 $can_create_channels = ($create_error === 'name_taken' || (isset($create_body['ok']) && $create_body['ok']));
800
801 // Check for missing scope errors
802 if ($create_error === 'missing_scope') {
803 $can_create_channels = false;
804 }
805 }
806
807 // Build result message
808 $results = array();
809 $results[] = sprintf(__('Workspace: %s', 'mxchat'), esc_html($team_name));
810 $results[] = sprintf(__('Bot: %s', 'mxchat'), esc_html($bot_name));
811 $results[] = '';
812 $results[] = ($can_read_channels ? '' : '') . ' ' . __('channels:read - List channels', 'mxchat');
813 $results[] = ($can_create_channels ? '' : '') . ' ' . __('channels:manage - Create channels', 'mxchat');
814
815 $missing_scopes = array();
816 if (!$can_read_channels) $missing_scopes[] = 'channels:read';
817 if (!$can_create_channels) $missing_scopes[] = 'channels:manage';
818
819 if (!empty($missing_scopes)) {
820 wp_send_json_error(array(
821 'message' => implode("\n", $results),
822 'missing_scopes' => $missing_scopes,
823 'partial' => true
824 ));
825 } else {
826 wp_send_json_success(array(
827 'message' => implode("\n", $results)
828 ));
829 }
830 }
831
832 public function mxchat_display_admin_notice() {
833 // Success notice
834 if ($message = get_transient('mxchat_admin_notice_success')) {
835 ?>
836 <div class="notice notice-success is-dismissible">
837 <p><?php echo esc_html($message); ?></p>
838 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
839 </div>
840 <?php
841 delete_transient('mxchat_admin_notice_success'); // Clear the transient after displaying
842 }
843
844 // Error notice
845 if ($message = get_transient('mxchat_admin_notice_error')) {
846 ?>
847 <div class="notice notice-error is-dismissible">
848 <p><?php echo esc_html($message); ?></p>
849 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php echo esc_html__('Dismiss this notice.', 'mxchat'); ?></span></button>
850 </div>
851 <?php
852 delete_transient('mxchat_admin_notice_error'); // Clear the transient after displaying
853 }
854 }
855
856 public function show_live_agent_disabled_banner() {
857 $show_disabled_notice = get_option('mxchat_show_live_agent_disabled_notice', false);
858
859 if ($show_disabled_notice) {
860 ?>
861 <div class="mxchat-live-agent-disabled-notice" id="mxchat-disabled-notice">
862 <div class="mxchat-pro-notification">
863 <button type="button" class="mxchat-dismiss-btn" onclick="dismissLiveAgentNotice()" aria-label="Dismiss notification">
864 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
865 <line x1="18" y1="6" x2="6" y2="18"></line>
866 <line x1="6" y1="6" x2="18" y2="18"></line>
867 </svg>
868 </button>
869 <div class="mxchat-live-agent-content">
870 <h3>🔧 Live Agent Integration Updated!</h3>
871 <p>We've temporarily disabled your Live Agent integration due to recent enhancements that have made it much better! You can easily turn it back on by going to <strong>Toolbar & Components → Live Agent Settings</strong> and reviewing the new configuration options.</p>
872 </div>
873 </div>
874 </div>
875 <?php
876 }
877 }
878 public function mxchat_create_admin_page() {
879 $this->add_live_agent_nonce();
880 $this->add_theme_migration_nonce();
881
882 // Include and render the new sidebar-based settings page
883 require_once plugin_dir_path(__FILE__) . 'admin-settings-page.php';
884 mxchat_render_settings_page($this);
885 }
886
887 public function dismiss_live_agent_notice() {
888 // Add debugging
889 //error_log('dismiss_live_agent_notice called');
890 //error_log('POST data: ' . print_r($_POST, true));
891
892 // Verify nonce
893 if (!wp_verify_nonce($_POST['nonce'], 'dismiss_live_agent_notice')) {
894 //error_log('Nonce verification failed');
895 wp_die('Security check failed');
896 }
897
898 // Remove the notice flag
899 $deleted = delete_option('mxchat_show_live_agent_disabled_notice');
900 //error_log('Option deleted: ' . ($deleted ? 'yes' : 'no'));
901
902 wp_send_json_success();
903 }
904 public function add_live_agent_nonce() {
905 if (get_option('mxchat_show_live_agent_disabled_notice', false)) {
906 // Make sure your admin script is enqueued and localize the data
907 wp_localize_script('mxchat-admin-js', 'mxchatLiveAgent', array(
908 'nonce' => wp_create_nonce('dismiss_live_agent_notice'),
909 'ajaxurl' => admin_url('admin-ajax.php')
910 ));
911 }
912 }
913
914 /**
915 * Show theme migration notice for Pro users with AI-generated themes
916 * Only shown once - dismissible and stored in options
917 */
918 public function show_theme_migration_banner() {
919 // Only show if Pro is activated
920 if (!$this->is_activated) {
921 return;
922 }
923
924 // Check if notice should be shown
925 $show_notice = get_option('mxchat_show_theme_migration_notice', false);
926
927 if ($show_notice) {
928 ?>
929 <div class="mxchat-theme-migration-notice" id="mxchat-theme-migration-notice">
930 <div class="mxchat-pro-notification">
931 <button type="button" class="mxchat-dismiss-btn" onclick="dismissThemeMigrationNotice()" aria-label="Dismiss notification">
932 <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
933 <line x1="18" y1="6" x2="6" y2="18"></line>
934 <line x1="6" y1="6" x2="18" y2="18"></line>
935 </svg>
936 </button>
937 <div class="mxchat-theme-migration-content">
938 <h3>🎨 AI Theme Migration Required</h3>
939 <p>If you're using an AI-generated chatbot theme, you'll need to migrate it to match the new CSS structure. Go to <strong>Theme Settings</strong>, select your theme from the sidebar, and click the <strong>Migrate</strong> button.</p>
940 </div>
941 </div>
942 </div>
943 <?php
944 }
945 }
946
947 /**
948 * Dismiss theme migration notice via AJAX
949 */
950 public function dismiss_theme_migration_notice() {
951 // Verify nonce
952 if (!wp_verify_nonce($_POST['nonce'], 'dismiss_theme_migration_notice')) {
953 wp_die('Security check failed');
954 }
955
956 // Remove the notice flag
957 delete_option('mxchat_show_theme_migration_notice');
958
959 wp_send_json_success();
960 }
961
962 /**
963 * Add nonce for theme migration notice dismiss
964 */
965 public function add_theme_migration_nonce() {
966 if (get_option('mxchat_show_theme_migration_notice', false) && $this->is_activated) {
967 wp_localize_script('mxchat-admin-js', 'mxchatThemeMigration', array(
968 'nonce' => wp_create_nonce('dismiss_theme_migration_notice'),
969 'ajaxurl' => admin_url('admin-ajax.php')
970 ));
971 }
972 }
973
974 public function mxchat_create_transcripts_page() {
975 global $wpdb;
976 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
977
978 // Get basic stats
979 $total_chats = $wpdb->get_var("SELECT COUNT(DISTINCT session_id) FROM $table_name") ?: 0;
980 $total_messages = $wpdb->get_var("SELECT COUNT(*) FROM $table_name") ?: 0;
981
982 // Count unique users with detailed breakdown
983 $total_users = $wpdb->get_var("
984 SELECT COUNT(DISTINCT
985 CASE
986 WHEN user_email != '' AND user_email IS NOT NULL THEN user_email
987 WHEN user_id != 0 THEN CONCAT('user_', user_id)
988 WHEN user_identifier NOT LIKE 'Tech-Savvy User'
989 AND user_identifier NOT LIKE 'Detail-Oriented User'
990 AND user_identifier NOT LIKE 'Language Learner'
991 AND user_identifier NOT LIKE 'Casual Browser'
992 AND user_identifier NOT LIKE 'Policy Enforcer'
993 AND user_identifier NOT LIKE 'Researcher'
994 AND user_identifier NOT LIKE 'Loyalty Member'
995 AND user_identifier NOT LIKE 'Gift Buyer'
996 AND user_identifier NOT LIKE 'Parent or Caregiver'
997 THEN user_identifier
998 ELSE session_id
999 END
1000 )
1001 FROM $table_name
1002 WHERE role != 'assistant'
1003 ");
1004
1005 // Get user type breakdown
1006 $registered_users = $wpdb->get_var("
1007 SELECT COUNT(DISTINCT user_email)
1008 FROM $table_name
1009 WHERE user_email != '' AND user_email IS NOT NULL
1010 ");
1011
1012 $guest_users = $wpdb->get_var("
1013 SELECT COUNT(DISTINCT user_identifier)
1014 FROM $table_name
1015 WHERE (user_email = '' OR user_email IS NULL)
1016 AND role != 'assistant'
1017 AND user_identifier NOT LIKE 'Tech-Savvy User'
1018 AND user_identifier NOT LIKE 'Detail-Oriented User'
1019 AND user_identifier NOT LIKE 'Language Learner'
1020 AND user_identifier NOT LIKE 'Casual Browser'
1021 AND user_identifier NOT LIKE 'Policy Enforcer'
1022 AND user_identifier NOT LIKE 'Researcher'
1023 AND user_identifier NOT LIKE 'Loyalty Member'
1024 AND user_identifier NOT LIKE 'Gift Buyer'
1025 AND user_identifier NOT LIKE 'Parent or Caregiver'
1026 ");
1027
1028 // Get agent test messages count
1029 $agent_tests = $wpdb->get_var("
1030 SELECT COUNT(DISTINCT session_id)
1031 FROM $table_name
1032 WHERE user_identifier IN (
1033 'Tech-Savvy User',
1034 'Detail-Oriented User',
1035 'Language Learner',
1036 'Casual Browser',
1037 'Policy Enforcer',
1038 'Researcher',
1039 'Loyalty Member',
1040 'Gift Buyer',
1041 'Parent or Caregiver'
1042 )
1043 ");
1044 // Get activity metrics
1045 $today_chats = $wpdb->get_var("
1046 SELECT COUNT(DISTINCT session_id)
1047 FROM $table_name
1048 WHERE DATE(timestamp) = CURDATE()
1049 ");
1050
1051 $week_chats = $wpdb->get_var("
1052 SELECT COUNT(DISTINCT session_id)
1053 FROM $table_name
1054 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1055 ");
1056
1057 $month_chats = $wpdb->get_var("
1058 SELECT COUNT(DISTINCT session_id)
1059 FROM $table_name
1060 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 30 DAY)
1061 ");
1062
1063 // Get daily chat data for last 7 days
1064 $daily_stats = $wpdb->get_results("
1065 SELECT
1066 DATE(timestamp) as date,
1067 COUNT(DISTINCT session_id) as chats,
1068 COUNT(*) as messages
1069 FROM $table_name
1070 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1071 GROUP BY DATE(timestamp)
1072 ORDER BY date ASC
1073 ");
1074
1075 // Get average messages per chat
1076 $avg_messages = $wpdb->get_var("
1077 SELECT AVG(message_count)
1078 FROM (
1079 SELECT session_id, COUNT(*) as message_count
1080 FROM $table_name
1081 GROUP BY session_id
1082 ) as chat_counts
1083 ");
1084 $avg_messages = $avg_messages ? round($avg_messages, 1) : 0;
1085
1086 // Get busiest hour
1087 $busiest_hour = $wpdb->get_row("
1088 SELECT HOUR(timestamp) as hour, COUNT(DISTINCT session_id) as chat_count
1089 FROM $table_name
1090 GROUP BY HOUR(timestamp)
1091 ORDER BY chat_count DESC
1092 LIMIT 1
1093 ");
1094
1095 // Prepare chart data
1096 $chart_labels = array();
1097 $chart_chats = array();
1098 $chart_messages = array();
1099
1100 // Fill last 7 days with data
1101 for ($i = 6; $i >= 0; $i--) {
1102 $date = date('Y-m-d', strtotime("-$i days"));
1103 $day_name = date('D', strtotime("-$i days"));
1104 $chart_labels[] = $day_name;
1105
1106 $found = false;
1107 foreach ($daily_stats as $stat) {
1108 if ($stat->date === $date) {
1109 $chart_chats[] = (int)$stat->chats;
1110 $chart_messages[] = (int)$stat->messages;
1111 $found = true;
1112 break;
1113 }
1114 }
1115 if (!$found) {
1116 $chart_chats[] = 0;
1117 $chart_messages[] = 0;
1118 }
1119 }
1120
1121 // Prepare page data for the template
1122 $page_data = array(
1123 'total_chats' => $total_chats,
1124 'total_messages' => $total_messages,
1125 'total_users' => $total_users,
1126 'registered_users' => $registered_users,
1127 'guest_users' => $guest_users,
1128 'agent_tests' => $agent_tests,
1129 'today_chats' => $today_chats,
1130 'week_chats' => $week_chats,
1131 'month_chats' => $month_chats,
1132 'avg_messages' => $avg_messages,
1133 'busiest_hour' => $busiest_hour,
1134 'chart_labels' => $chart_labels,
1135 'chart_chats' => $chart_chats,
1136 'chart_messages' => $chart_messages,
1137 );
1138
1139 // Include and render the new template
1140 require_once plugin_dir_path(__FILE__) . 'admin-transcripts-page.php';
1141 mxchat_render_transcripts_page($this, $page_data);
1142 }
1143
1144 /**
1145 * Get chart data for transcripts page
1146 * Used by both page render and script localization
1147 *
1148 * @return array Chart data with labels, chats, and messages arrays
1149 */
1150 private function get_transcripts_chart_data() {
1151 global $wpdb;
1152 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1153
1154 // Get daily chat data for last 7 days - same query as mxchat_transcripts_page()
1155 $daily_stats = $wpdb->get_results("
1156 SELECT
1157 DATE(timestamp) as date,
1158 COUNT(DISTINCT session_id) as chats,
1159 COUNT(*) as messages
1160 FROM $table_name
1161 WHERE timestamp >= DATE_SUB(NOW(), INTERVAL 7 DAY)
1162 GROUP BY DATE(timestamp)
1163 ORDER BY date ASC
1164 ");
1165
1166 // Prepare chart data
1167 $chart_labels = array();
1168 $chart_chats = array();
1169 $chart_messages = array();
1170
1171 // Fill last 7 days with data - same logic as mxchat_transcripts_page()
1172 for ($i = 6; $i >= 0; $i--) {
1173 $date = date('Y-m-d', strtotime("-$i days"));
1174 $day_name = date('D', strtotime("-$i days"));
1175 $chart_labels[] = $day_name;
1176
1177 $found = false;
1178 if ($daily_stats) {
1179 foreach ($daily_stats as $stat) {
1180 if ($stat->date === $date) {
1181 $chart_chats[] = (int)$stat->chats;
1182 $chart_messages[] = (int)$stat->messages;
1183 $found = true;
1184 break;
1185 }
1186 }
1187 }
1188 if (!$found) {
1189 $chart_chats[] = 0;
1190 $chart_messages[] = 0;
1191 }
1192 }
1193
1194 return array(
1195 'labels' => $chart_labels,
1196 'chats' => $chart_chats,
1197 'messages' => $chart_messages
1198 );
1199 }
1200
1201 public function mxchat_transcripts_notification_section_callback() {
1202 echo '<p>' . esc_html__('Configure email notifications for new chat transcripts. You will receive an email notification when a new chat session begins.', 'mxchat') . '</p>';
1203 }
1204 public function mxchat_enable_notifications_callback() {
1205 $options = get_option('mxchat_transcripts_options', array());
1206 $enabled = isset($options['mxchat_enable_notifications']) ? $options['mxchat_enable_notifications'] : 0;
1207 ?>
1208 <label for="mxchat_enable_notifications">
1209 <input type="checkbox" id="mxchat_enable_notifications"
1210 name="mxchat_transcripts_options[mxchat_enable_notifications]"
1211 value="1" <?php checked(1, $enabled); ?>>
1212 <?php esc_html_e('Send email notification when a new chat session starts', 'mxchat'); ?>
1213 </label>
1214 <p class="description">
1215 <?php esc_html_e('Enable this option to receive email notifications for new chat sessions.', 'mxchat'); ?>
1216 </p>
1217 <?php
1218 }
1219 public function mxchat_notification_email_callback() {
1220 $options = get_option('mxchat_transcripts_options', array());
1221 $email = isset($options['mxchat_notification_email']) ? $options['mxchat_notification_email'] : get_option('admin_email');
1222 ?>
1223 <input type="email" id="mxchat_notification_email"
1224 name="mxchat_transcripts_options[mxchat_notification_email]"
1225 value="<?php echo esc_attr($email); ?>"
1226 class="regular-text">
1227 <p class="description">
1228 <?php esc_html_e('Enter the email address where notifications should be sent. Defaults to the admin email address.', 'mxchat'); ?>
1229 </p>
1230 <?php
1231 }
1232
1233 public function mxchat_auto_delete_transcripts_callback() {
1234 $options = get_option('mxchat_transcripts_options', array());
1235 $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1236 ?>
1237 <select id="mxchat_auto_delete_transcripts"
1238 name="mxchat_transcripts_options[mxchat_auto_delete_transcripts]">
1239 <option value="never" <?php selected($interval, 'never'); ?>>
1240 <?php esc_html_e('Never (Keep All Transcripts)', 'mxchat'); ?>
1241 </option>
1242 <option value="1week" <?php selected($interval, '1week'); ?>>
1243 <?php esc_html_e('After 1 Week', 'mxchat'); ?>
1244 </option>
1245 <option value="2weeks" <?php selected($interval, '2weeks'); ?>>
1246 <?php esc_html_e('After 2 Weeks', 'mxchat'); ?>
1247 </option>
1248 <option value="1month" <?php selected($interval, '1month'); ?>>
1249 <?php esc_html_e('After 1 Month', 'mxchat'); ?>
1250 </option>
1251 </select>
1252 <p class="description">
1253 <?php esc_html_e('Automatically delete old chat transcripts after the selected time period. This helps manage database size and privacy.', 'mxchat'); ?>
1254 </p>
1255 <?php
1256 }
1257
1258 public function mxchat_auto_email_transcript_callback() {
1259 $options = get_option('mxchat_transcripts_options', array());
1260 $enabled = isset($options['mxchat_auto_email_transcript_enabled']) ? $options['mxchat_auto_email_transcript_enabled'] : 0;
1261 $delay = isset($options['mxchat_auto_email_transcript_delay']) ? $options['mxchat_auto_email_transcript_delay'] : '30';
1262 $require_contact = isset($options['mxchat_auto_email_transcript_require_contact']) ? $options['mxchat_auto_email_transcript_require_contact'] : 0;
1263 ?>
1264 <label>
1265 <input type="checkbox"
1266 id="mxchat_auto_email_transcript_enabled"
1267 name="mxchat_transcripts_options[mxchat_auto_email_transcript_enabled]"
1268 value="1"
1269 <?php checked($enabled, 1); ?>>
1270 <?php esc_html_e('Enable Auto-Email of Full Transcript', 'mxchat'); ?>
1271 </label>
1272 <br><br>
1273 <label for="mxchat_auto_email_transcript_delay">
1274 <?php esc_html_e('Send transcript after:', 'mxchat'); ?>
1275 </label>
1276 <select id="mxchat_auto_email_transcript_delay"
1277 name="mxchat_transcripts_options[mxchat_auto_email_transcript_delay]">
1278 <option value="15" <?php selected($delay, '15'); ?>>
1279 <?php esc_html_e('15 minutes', 'mxchat'); ?>
1280 </option>
1281 <option value="30" <?php selected($delay, '30'); ?>>
1282 <?php esc_html_e('30 minutes', 'mxchat'); ?>
1283 </option>
1284 <option value="60" <?php selected($delay, '60'); ?>>
1285 <?php esc_html_e('1 hour', 'mxchat'); ?>
1286 </option>
1287 </select>
1288 <br><br>
1289 <label>
1290 <input type="checkbox"
1291 id="mxchat_auto_email_transcript_require_contact"
1292 name="mxchat_transcripts_options[mxchat_auto_email_transcript_require_contact]"
1293 value="1"
1294 <?php checked($require_contact, 1); ?>>
1295 <?php esc_html_e('Only send if visitor provided contact info', 'mxchat'); ?>
1296 </label>
1297 <p class="description">
1298 <?php esc_html_e('Automatically email the full transcript as a .txt file after the specified time has passed since the last user message. The scheduled email will be cancelled if a new message is received.', 'mxchat'); ?>
1299 <br>
1300 <?php esc_html_e('When "Only send if visitor provided contact info" is enabled, transcripts will only be emailed if the visitor shared an email address or phone number (including WhatsApp) in the chat.', 'mxchat'); ?>
1301 </p>
1302 <?php
1303 }
1304
1305
1306
1307 public function sanitize_transcripts_options($input) {
1308 $sanitized = array();
1309
1310 $sanitized['mxchat_enable_notifications'] = isset($input['mxchat_enable_notifications']) ? 1 : 0;
1311
1312 if (isset($input['mxchat_notification_email'])) {
1313 $sanitized['mxchat_notification_email'] = sanitize_email($input['mxchat_notification_email']);
1314 if (!is_email($sanitized['mxchat_notification_email'])) {
1315 add_settings_error(
1316 'mxchat_transcripts_options',
1317 'invalid_email',
1318 __('Please enter a valid email address for notifications.', 'mxchat'),
1319 'error'
1320 );
1321 $sanitized['mxchat_notification_email'] = get_option('admin_email');
1322 }
1323 }
1324
1325 // Sanitize auto-delete setting
1326 $valid_intervals = array('never', '1week', '2weeks', '1month');
1327 if (isset($input['mxchat_auto_delete_transcripts'])) {
1328 $sanitized['mxchat_auto_delete_transcripts'] = in_array($input['mxchat_auto_delete_transcripts'], $valid_intervals)
1329 ? $input['mxchat_auto_delete_transcripts']
1330 : 'never';
1331 } else {
1332 $sanitized['mxchat_auto_delete_transcripts'] = 'never';
1333 }
1334
1335 // Get old value to check if auto-delete setting changed
1336 $old_options = get_option('mxchat_transcripts_options');
1337 $old_interval = isset($old_options['mxchat_auto_delete_transcripts']) ? $old_options['mxchat_auto_delete_transcripts'] : 'never';
1338
1339 // If the auto-delete setting changed, reschedule the cron job
1340 if ($old_interval !== $sanitized['mxchat_auto_delete_transcripts']) {
1341 $this->schedule_transcript_cleanup($sanitized['mxchat_auto_delete_transcripts']);
1342 }
1343
1344 // Sanitize auto-email transcript settings
1345 $sanitized['mxchat_auto_email_transcript_enabled'] = isset($input['mxchat_auto_email_transcript_enabled']) ? 1 : 0;
1346
1347 $valid_delays = array('15', '30', '60');
1348 if (isset($input['mxchat_auto_email_transcript_delay'])) {
1349 $sanitized['mxchat_auto_email_transcript_delay'] = in_array($input['mxchat_auto_email_transcript_delay'], $valid_delays)
1350 ? $input['mxchat_auto_email_transcript_delay']
1351 : '30';
1352 } else {
1353 $sanitized['mxchat_auto_email_transcript_delay'] = '30';
1354 }
1355
1356 // Sanitize require contact info setting
1357 $sanitized['mxchat_auto_email_transcript_require_contact'] = isset($input['mxchat_auto_email_transcript_require_contact']) ? 1 : 0;
1358
1359 return $sanitized;
1360 }
1361
1362 /**
1363 * Schedule or unschedule the transcript cleanup cron job
1364 */
1365 public function schedule_transcript_cleanup($interval) {
1366 // Clear any existing scheduled event
1367 $timestamp = wp_next_scheduled('mxchat_cleanup_old_transcripts');
1368 if ($timestamp) {
1369 wp_unschedule_event($timestamp, 'mxchat_cleanup_old_transcripts');
1370 }
1371
1372 // Schedule new event if not set to "never"
1373 if ($interval !== 'never') {
1374 // Schedule to run daily at 3 AM
1375 $next_run = strtotime('tomorrow 3:00 AM');
1376 wp_schedule_event($next_run, 'daily', 'mxchat_cleanup_old_transcripts');
1377 }
1378 }
1379
1380 /**
1381 * Delete old transcripts based on the configured interval
1382 */
1383 public function cleanup_old_transcripts() {
1384 $options = get_option('mxchat_transcripts_options', array());
1385 $interval = isset($options['mxchat_auto_delete_transcripts']) ? $options['mxchat_auto_delete_transcripts'] : 'never';
1386
1387 // If set to never, don't delete anything
1388 if ($interval === 'never') {
1389 return;
1390 }
1391
1392 // Calculate the cutoff date
1393 $days = 0;
1394 switch ($interval) {
1395 case '1week':
1396 $days = 7;
1397 break;
1398 case '2weeks':
1399 $days = 14;
1400 break;
1401 case '1month':
1402 $days = 30;
1403 break;
1404 default:
1405 return; // Invalid interval, don't delete anything
1406 }
1407
1408 global $wpdb;
1409 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1410 $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
1411
1412 // Calculate the cutoff timestamp
1413 $cutoff_date = date('Y-m-d H:i:s', strtotime("-{$days} days"));
1414
1415 // First, get the session IDs that will be deleted (to clean up translations too)
1416 $sessions_to_delete = $wpdb->get_col(
1417 $wpdb->prepare(
1418 "SELECT DISTINCT session_id FROM {$table_name} WHERE timestamp < %s",
1419 $cutoff_date
1420 )
1421 );
1422
1423 // Delete transcripts older than the cutoff date
1424 $deleted = $wpdb->query(
1425 $wpdb->prepare(
1426 "DELETE FROM {$table_name} WHERE timestamp < %s",
1427 $cutoff_date
1428 )
1429 );
1430
1431 // Also delete any translations for the deleted sessions
1432 if (!empty($sessions_to_delete) && $wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table) {
1433 $placeholders = implode(',', array_fill(0, count($sessions_to_delete), '%s'));
1434 $wpdb->query(
1435 $wpdb->prepare(
1436 "DELETE FROM {$translations_table} WHERE session_id IN ($placeholders)",
1437 $sessions_to_delete
1438 )
1439 );
1440 }
1441
1442 // Log the cleanup action
1443 if ($deleted !== false && $deleted > 0) {
1444 //error_log(sprintf('MXChat: Auto-deleted %d transcripts older than %d days', $deleted, $days));
1445 }
1446 }
1447
1448 public function export_chat_transcripts() {
1449 if (!current_user_can('manage_options')) {
1450 wp_die(esc_html__('You do not have sufficient permissions to access this page.', 'mxchat'));
1451 }
1452
1453 check_ajax_referer('mxchat_export_transcripts', 'security');
1454
1455 global $wpdb;
1456 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1457
1458 // Get all transcripts ordered by session and timestamp
1459 $results = $wpdb->get_results(
1460 "SELECT session_id, user_email, user_identifier, role, message, timestamp
1461 FROM {$table_name}
1462 ORDER BY session_id, timestamp ASC"
1463 );
1464
1465 if (empty($results)) {
1466 wp_send_json_error(array('message' => 'No transcripts found.'));
1467 wp_die();
1468 }
1469
1470 // Set headers for CSV download
1471 header('Content-Type: text/csv');
1472 header('Content-Disposition: attachment; filename="chat-transcripts-' . date('Y-m-d') . '.csv"');
1473 header('Pragma: no-cache');
1474 header('Expires: 0');
1475
1476 // Create output stream
1477 $output = fopen('php://output', 'w');
1478
1479 // Add UTF-8 BOM for proper Excel encoding
1480 fputs($output, "\xEF\xBB\xBF");
1481
1482 // Add CSV headers
1483 fputcsv($output, array(
1484 'Session ID',
1485 'Email',
1486 'User Identifier',
1487 'Role',
1488 'Message',
1489 'Timestamp'
1490 ));
1491
1492 // Add data rows
1493 foreach ($results as $row) {
1494 fputcsv($output, array(
1495 $row->session_id,
1496 $row->user_email,
1497 $row->user_identifier,
1498 $row->role,
1499 $row->message,
1500 $row->timestamp
1501 ));
1502 }
1503
1504 fclose($output);
1505 wp_die();
1506 }
1507
1508 /**
1509 * Handle translation of chat messages via AJAX
1510 */
1511 public function mxchat_translate_messages() {
1512 if (!current_user_can('manage_options')) {
1513 wp_send_json_error(['error' => 'Insufficient permissions']);
1514 wp_die();
1515 }
1516
1517 $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
1518 $target_lang = isset($_POST['target_lang']) ? sanitize_text_field($_POST['target_lang']) : 'en';
1519 $messages_json = isset($_POST['messages']) ? wp_unslash($_POST['messages']) : '[]';
1520 $messages = json_decode($messages_json, true);
1521
1522 if (empty($session_id)) {
1523 wp_send_json_error(['error' => 'No session ID provided']);
1524 wp_die();
1525 }
1526
1527 if (empty($messages) || !is_array($messages)) {
1528 wp_send_json_error(['error' => 'No messages to translate']);
1529 wp_die();
1530 }
1531
1532 // Language names for prompting
1533 $languages = [
1534 'en' => 'English',
1535 'es' => 'Spanish',
1536 'fr' => 'French',
1537 'de' => 'German',
1538 'it' => 'Italian',
1539 'pt' => 'Portuguese',
1540 'nl' => 'Dutch',
1541 'ru' => 'Russian',
1542 'zh' => 'Chinese',
1543 'ja' => 'Japanese',
1544 'ko' => 'Korean',
1545 'ar' => 'Arabic',
1546 'hi' => 'Hindi',
1547 'tr' => 'Turkish',
1548 'pl' => 'Polish',
1549 'vi' => 'Vietnamese',
1550 'th' => 'Thai',
1551 'id' => 'Indonesian',
1552 'sv' => 'Swedish',
1553 'da' => 'Danish'
1554 ];
1555
1556 $target_lang_name = isset($languages[$target_lang]) ? $languages[$target_lang] : 'English';
1557
1558 // Build combined text for translation (numbered for parsing)
1559 $numbered_messages = [];
1560 foreach ($messages as $i => $msg) {
1561 $content = isset($msg['content']) ? trim($msg['content']) : '';
1562 if (!empty($content)) {
1563 $numbered_messages[] = "[MSG" . $i . "]" . $content . "[/MSG" . $i . "]";
1564 }
1565 }
1566
1567 if (empty($numbered_messages)) {
1568 wp_send_json_error(['error' => 'No valid messages to translate']);
1569 wp_die();
1570 }
1571
1572 $combined_text = implode("\n\n", $numbered_messages);
1573
1574 // Prepare the translation prompt
1575 $system_prompt = "You are a translator. Translate the following messages to {$target_lang_name}. Keep the [MSG#] and [/MSG#] tags exactly as they are - only translate the content between them. Maintain the original formatting, line breaks, and any HTML tags. Return ONLY the translated messages with the tags, no explanations.";
1576
1577 // Get user's selected model and determine provider
1578 $options = get_option('mxchat_options', []);
1579 $selected_model = $options['model'] ?? 'gpt-5.1-chat-latest';
1580
1581 // Check if using OpenRouter
1582 if ($selected_model === 'openrouter') {
1583 $provider = 'openrouter';
1584 $selected_model = $options['openrouter_selected_model'] ?? '';
1585 $api_key = $options['openrouter_api_key'] ?? '';
1586
1587 if (empty($selected_model)) {
1588 wp_send_json_error(['error' => 'No OpenRouter model selected']);
1589 wp_die();
1590 }
1591 } else {
1592 // Determine provider from model name
1593 $model_parts = explode('-', $selected_model);
1594 $provider = strtolower($model_parts[0]);
1595
1596 // Get the appropriate API key based on provider
1597 $api_key = '';
1598 switch ($provider) {
1599 case 'gpt':
1600 case 'o1':
1601 $api_key = $options['api_key'] ?? '';
1602 break;
1603 case 'claude':
1604 $api_key = $options['claude_api_key'] ?? '';
1605 break;
1606 case 'grok':
1607 $api_key = $options['xai_api_key'] ?? '';
1608 break;
1609 case 'deepseek':
1610 $api_key = $options['deepseek_api_key'] ?? '';
1611 break;
1612 case 'gemini':
1613 $api_key = $options['gemini_api_key'] ?? '';
1614 break;
1615 default:
1616 // Default to OpenAI
1617 $api_key = $options['api_key'] ?? '';
1618 $provider = 'gpt';
1619 break;
1620 }
1621 }
1622
1623 if (empty($api_key)) {
1624 wp_send_json_error(['error' => 'No API key configured for ' . $provider]);
1625 wp_die();
1626 }
1627
1628 // Make API request based on provider
1629 $response = $this->translate_with_provider($provider, $selected_model, $api_key, $system_prompt, $combined_text);
1630
1631 if (is_wp_error($response)) {
1632 wp_send_json_error(['error' => $response->get_error_message()]);
1633 wp_die();
1634 }
1635
1636 // Parse the response to extract translated messages
1637 $translations = [];
1638 foreach ($messages as $msg) {
1639 $index = $msg['index'];
1640 $pattern = '/\[MSG' . $index . '\](.*?)\[\/MSG' . $index . '\]/s';
1641 if (preg_match($pattern, $response, $matches)) {
1642 $translations[] = [
1643 'index' => $index,
1644 'translated' => trim($matches[1])
1645 ];
1646 }
1647 }
1648
1649 // Save translations to database
1650 if (!empty($translations)) {
1651 $this->save_transcript_translation($session_id, $target_lang, $translations);
1652 }
1653
1654 wp_send_json(['success' => true, 'translations' => $translations, 'language' => $target_lang]);
1655 wp_die();
1656 }
1657
1658 /**
1659 * Save transcript translation to database
1660 */
1661 private function save_transcript_translation($session_id, $language_code, $translations) {
1662 global $wpdb;
1663 $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
1664
1665 // Check if table exists, create if not
1666 if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
1667 mxchat_create_translations_table();
1668 }
1669
1670 $now = current_time('mysql');
1671 $translations_json = wp_json_encode($translations);
1672
1673 // Use REPLACE to insert or update
1674 $wpdb->query($wpdb->prepare(
1675 "REPLACE INTO $table_name (session_id, language_code, translations, created_at, updated_at)
1676 VALUES (%s, %s, %s, %s, %s)",
1677 $session_id,
1678 $language_code,
1679 $translations_json,
1680 $now,
1681 $now
1682 ));
1683 }
1684
1685 /**
1686 * Get saved translation for a session
1687 */
1688 public function mxchat_get_transcript_translation() {
1689 if (!current_user_can('manage_options')) {
1690 wp_send_json_error(['error' => 'Insufficient permissions']);
1691 wp_die();
1692 }
1693
1694 $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
1695
1696 if (empty($session_id)) {
1697 wp_send_json_error(['error' => 'No session ID provided']);
1698 wp_die();
1699 }
1700
1701 global $wpdb;
1702 $table_name = $wpdb->prefix . 'mxchat_transcript_translations';
1703
1704 // Check if table exists
1705 if ($wpdb->get_var("SHOW TABLES LIKE '$table_name'") !== $table_name) {
1706 wp_send_json(['success' => true, 'has_translation' => false]);
1707 wp_die();
1708 }
1709
1710 // Get the most recent translation for this session
1711 $result = $wpdb->get_row($wpdb->prepare(
1712 "SELECT language_code, translations FROM $table_name WHERE session_id = %s ORDER BY updated_at DESC LIMIT 1",
1713 $session_id
1714 ));
1715
1716 if ($result) {
1717 $translations = json_decode($result->translations, true);
1718 wp_send_json([
1719 'success' => true,
1720 'has_translation' => true,
1721 'language' => $result->language_code,
1722 'translations' => $translations
1723 ]);
1724 } else {
1725 wp_send_json(['success' => true, 'has_translation' => false]);
1726 }
1727 wp_die();
1728 }
1729
1730 /**
1731 * Translate text using the user's selected provider and model
1732 */
1733 private function translate_with_provider($provider, $model, $api_key, $system_prompt, $text) {
1734 switch ($provider) {
1735 case 'claude':
1736 return $this->translate_with_claude($api_key, $model, $system_prompt, $text);
1737 case 'grok':
1738 return $this->translate_with_xai($api_key, $model, $system_prompt, $text);
1739 case 'deepseek':
1740 return $this->translate_with_deepseek($api_key, $model, $system_prompt, $text);
1741 case 'gemini':
1742 return $this->translate_with_gemini($api_key, $model, $system_prompt, $text);
1743 case 'openrouter':
1744 return $this->translate_with_openrouter($api_key, $model, $system_prompt, $text);
1745 case 'gpt':
1746 case 'o1':
1747 default:
1748 return $this->translate_with_openai($api_key, $model, $system_prompt, $text);
1749 }
1750 }
1751
1752 /**
1753 * Translate text using OpenAI API
1754 */
1755 private function translate_with_openai($api_key, $model, $system_prompt, $text) {
1756 $response = wp_remote_post('https://api.openai.com/v1/chat/completions', [
1757 'timeout' => 60,
1758 'headers' => [
1759 'Authorization' => 'Bearer ' . $api_key,
1760 'Content-Type' => 'application/json'
1761 ],
1762 'body' => wp_json_encode([
1763 'model' => $model,
1764 'messages' => [
1765 ['role' => 'system', 'content' => $system_prompt],
1766 ['role' => 'user', 'content' => $text]
1767 ],
1768 'temperature' => 0.3
1769 ])
1770 ]);
1771
1772 if (is_wp_error($response)) {
1773 return $response;
1774 }
1775
1776 $body = json_decode(wp_remote_retrieve_body($response), true);
1777
1778 if (isset($body['error'])) {
1779 return new WP_Error('api_error', $body['error']['message']);
1780 }
1781
1782 if (isset($body['choices'][0]['message']['content'])) {
1783 return $body['choices'][0]['message']['content'];
1784 }
1785
1786 return new WP_Error('api_error', 'Invalid API response');
1787 }
1788
1789 /**
1790 * Translate text using Claude API
1791 */
1792 private function translate_with_claude($api_key, $model, $system_prompt, $text) {
1793 $response = wp_remote_post('https://api.anthropic.com/v1/messages', [
1794 'timeout' => 60,
1795 'headers' => [
1796 'x-api-key' => $api_key,
1797 'anthropic-version' => '2023-06-01',
1798 'Content-Type' => 'application/json'
1799 ],
1800 'body' => wp_json_encode([
1801 'model' => $model,
1802 'max_tokens' => 4096,
1803 'system' => $system_prompt,
1804 'messages' => [
1805 ['role' => 'user', 'content' => $text]
1806 ]
1807 ])
1808 ]);
1809
1810 if (is_wp_error($response)) {
1811 return $response;
1812 }
1813
1814 $body = json_decode(wp_remote_retrieve_body($response), true);
1815
1816 if (isset($body['error'])) {
1817 return new WP_Error('api_error', $body['error']['message']);
1818 }
1819
1820 if (isset($body['content'][0]['text'])) {
1821 return $body['content'][0]['text'];
1822 }
1823
1824 return new WP_Error('api_error', 'Invalid API response');
1825 }
1826
1827 /**
1828 * Translate text using xAI (Grok) API
1829 */
1830 private function translate_with_xai($api_key, $model, $system_prompt, $text) {
1831 $response = wp_remote_post('https://api.x.ai/v1/chat/completions', [
1832 'timeout' => 60,
1833 'headers' => [
1834 'Authorization' => 'Bearer ' . $api_key,
1835 'Content-Type' => 'application/json'
1836 ],
1837 'body' => wp_json_encode([
1838 'model' => $model,
1839 'messages' => [
1840 ['role' => 'system', 'content' => $system_prompt],
1841 ['role' => 'user', 'content' => $text]
1842 ],
1843 'temperature' => 0.3
1844 ])
1845 ]);
1846
1847 if (is_wp_error($response)) {
1848 return $response;
1849 }
1850
1851 $body = json_decode(wp_remote_retrieve_body($response), true);
1852
1853 if (isset($body['error'])) {
1854 return new WP_Error('api_error', $body['error']['message']);
1855 }
1856
1857 if (isset($body['choices'][0]['message']['content'])) {
1858 return $body['choices'][0]['message']['content'];
1859 }
1860
1861 return new WP_Error('api_error', 'Invalid API response');
1862 }
1863
1864 /**
1865 * Translate text using DeepSeek API
1866 */
1867 private function translate_with_deepseek($api_key, $model, $system_prompt, $text) {
1868 $response = wp_remote_post('https://api.deepseek.com/v1/chat/completions', [
1869 'timeout' => 60,
1870 'headers' => [
1871 'Authorization' => 'Bearer ' . $api_key,
1872 'Content-Type' => 'application/json'
1873 ],
1874 'body' => wp_json_encode([
1875 'model' => $model,
1876 'messages' => [
1877 ['role' => 'system', 'content' => $system_prompt],
1878 ['role' => 'user', 'content' => $text]
1879 ],
1880 'temperature' => 0.3
1881 ])
1882 ]);
1883
1884 if (is_wp_error($response)) {
1885 return $response;
1886 }
1887
1888 $body = json_decode(wp_remote_retrieve_body($response), true);
1889
1890 if (isset($body['error'])) {
1891 return new WP_Error('api_error', $body['error']['message']);
1892 }
1893
1894 if (isset($body['choices'][0]['message']['content'])) {
1895 return $body['choices'][0]['message']['content'];
1896 }
1897
1898 return new WP_Error('api_error', 'Invalid API response');
1899 }
1900
1901 /**
1902 * Translate text using Google Gemini API
1903 */
1904 private function translate_with_gemini($api_key, $model, $system_prompt, $text) {
1905 $url = 'https://generativelanguage.googleapis.com/v1beta/models/' . $model . ':generateContent?key=' . $api_key;
1906
1907 $response = wp_remote_post($url, [
1908 'timeout' => 60,
1909 'headers' => [
1910 'Content-Type' => 'application/json'
1911 ],
1912 'body' => wp_json_encode([
1913 'contents' => [
1914 [
1915 'parts' => [
1916 ['text' => $system_prompt . "\n\n" . $text]
1917 ]
1918 ]
1919 ],
1920 'generationConfig' => [
1921 'temperature' => 0.3
1922 ]
1923 ])
1924 ]);
1925
1926 if (is_wp_error($response)) {
1927 return $response;
1928 }
1929
1930 $body = json_decode(wp_remote_retrieve_body($response), true);
1931
1932 if (isset($body['error'])) {
1933 return new WP_Error('api_error', $body['error']['message']);
1934 }
1935
1936 if (isset($body['candidates'][0]['content']['parts'][0]['text'])) {
1937 return $body['candidates'][0]['content']['parts'][0]['text'];
1938 }
1939
1940 return new WP_Error('api_error', 'Invalid API response');
1941 }
1942
1943 /**
1944 * Translate text using OpenRouter API
1945 */
1946 private function translate_with_openrouter($api_key, $model, $system_prompt, $text) {
1947 $response = wp_remote_post('https://openrouter.ai/api/v1/chat/completions', [
1948 'timeout' => 60,
1949 'headers' => [
1950 'Authorization' => 'Bearer ' . $api_key,
1951 'Content-Type' => 'application/json',
1952 'HTTP-Referer' => home_url(),
1953 'X-Title' => 'MxChat Translation'
1954 ],
1955 'body' => wp_json_encode([
1956 'model' => $model,
1957 'messages' => [
1958 ['role' => 'system', 'content' => $system_prompt],
1959 ['role' => 'user', 'content' => $text]
1960 ],
1961 'temperature' => 0.3
1962 ])
1963 ]);
1964
1965 if (is_wp_error($response)) {
1966 return $response;
1967 }
1968
1969 $body = json_decode(wp_remote_retrieve_body($response), true);
1970
1971 if (isset($body['error'])) {
1972 return new WP_Error('api_error', $body['error']['message']);
1973 }
1974
1975 if (isset($body['choices'][0]['message']['content'])) {
1976 return $body['choices'][0]['message']['content'];
1977 }
1978
1979 return new WP_Error('api_error', 'Invalid API response');
1980 }
1981
1982 public function mxchat_fetch_chat_history() {
1983 global $wpdb;
1984 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
1985 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
1986
1987 if (!current_user_can('manage_options')) {
1988 wp_send_json_error(['message' => 'Insufficient permissions']);
1989 wp_die();
1990 }
1991
1992 $page = isset($_POST['page']) ? absint($_POST['page']) : 1;
1993 $per_page = isset($_POST['per_page']) ? absint($_POST['per_page']) : 50;
1994 $offset = ($page - 1) * $per_page;
1995 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
1996 $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
1997
1998 // Build search condition
1999 $search_condition = '';
2000 $search_params = [];
2001 if (!empty($search)) {
2002 $search_condition = "WHERE (
2003 session_id LIKE %s
2004 OR user_email LIKE %s
2005 OR user_name LIKE %s
2006 OR user_identifier LIKE %s
2007 OR message LIKE %s
2008 )";
2009 $search_params = array_fill(0, 5, '%' . $wpdb->esc_like($search) . '%');
2010 }
2011
2012 // Get total count
2013 $count_query = !empty($search)
2014 ? $wpdb->prepare("SELECT COUNT(DISTINCT session_id) FROM {$table_name} {$search_condition}", $search_params)
2015 : "SELECT COUNT(DISTINCT session_id) FROM {$table_name}";
2016 $total_sessions = (int) $wpdb->get_var($count_query);
2017
2018 // Get session IDs for current page (sorted by newest or oldest)
2019 $session_query = !empty($search)
2020 ? $wpdb->prepare(
2021 "SELECT DISTINCT t.session_id FROM {$table_name} t {$search_condition}
2022 GROUP BY t.session_id ORDER BY MAX(t.timestamp) {$sort_order} LIMIT %d OFFSET %d",
2023 array_merge($search_params, [$per_page, $offset])
2024 )
2025 : $wpdb->prepare(
2026 "SELECT DISTINCT session_id FROM {$table_name}
2027 GROUP BY session_id ORDER BY MAX(timestamp) {$sort_order} LIMIT %d OFFSET %d",
2028 $per_page, $offset
2029 );
2030 $session_ids = $wpdb->get_col($session_query);
2031
2032 $total_pages = ceil($total_sessions / $per_page);
2033
2034 if (empty($session_ids)) {
2035 wp_send_json([
2036 'success' => true,
2037 'sessions' => [],
2038 'page' => $page,
2039 'total_pages' => 0,
2040 'total_sessions' => 0,
2041 'showing_start' => 0,
2042 'showing_end' => 0
2043 ]);
2044 wp_die();
2045 }
2046
2047 // Check for optional columns/tables
2048 $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
2049 $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
2050
2051 // Build session list data
2052 $sessions = [];
2053 foreach ($session_ids as $session_id) {
2054 // Get session metadata
2055 $session_data = $originating_columns_exist
2056 ? $wpdb->get_row($wpdb->prepare(
2057 "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
2058 FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
2059 $session_id
2060 ))
2061 : $wpdb->get_row($wpdb->prepare(
2062 "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
2063 WHERE session_id = %s LIMIT 1",
2064 $session_id
2065 ));
2066
2067 // Get message count and latest timestamp
2068 $message_stats = $wpdb->get_row($wpdb->prepare(
2069 "SELECT COUNT(*) as count, MAX(timestamp) as latest FROM {$table_name} WHERE session_id = %s",
2070 $session_id
2071 ));
2072
2073 // Get first user message as preview
2074 $first_user_msg = $wpdb->get_var($wpdb->prepare(
2075 "SELECT message FROM {$table_name} WHERE session_id = %s AND role = 'user' ORDER BY timestamp ASC LIMIT 1",
2076 $session_id
2077 ));
2078 $preview = $first_user_msg ? wp_trim_words(wp_strip_all_tags(stripslashes($first_user_msg)), 12, '...') : 'No messages';
2079
2080 // Build display name
2081 $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
2082 $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
2083 $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
2084
2085 $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
2086 $display_sub = $user_email ?: ('ID: ' . $user_identifier);
2087
2088 // Format time - show relative for recent, date for older
2089 $timestamp = strtotime($message_stats->latest . ' UTC');
2090 $now = time();
2091 $diff = $now - $timestamp;
2092 if ($diff < 3600) {
2093 $time_display = floor($diff / 60) . 'm ago';
2094 } elseif ($diff < 86400) {
2095 $time_display = floor($diff / 3600) . 'h ago';
2096 } elseif ($diff < 604800) {
2097 $time_display = floor($diff / 86400) . 'd ago';
2098 } else {
2099 $time_display = wp_date('M j', $timestamp);
2100 }
2101
2102 // Get initials for avatar
2103 $initials = strtoupper(substr($display_name, 0, 2));
2104 if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
2105 $parts = explode(' ', $display_name);
2106 $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
2107 }
2108
2109 $sessions[] = [
2110 'session_id' => $session_id,
2111 'display_name' => $display_name,
2112 'display_sub' => $display_sub,
2113 'initials' => $initials,
2114 'preview' => $preview,
2115 'message_count' => (int) $message_stats->count,
2116 'time_display' => $time_display,
2117 'timestamp' => $message_stats->latest
2118 ];
2119 }
2120
2121 wp_send_json([
2122 'success' => true,
2123 'sessions' => $sessions,
2124 'page' => $page,
2125 'total_pages' => $total_pages,
2126 'total_sessions' => $total_sessions,
2127 'showing_start' => $offset + 1,
2128 'showing_end' => min($offset + $per_page, $total_sessions)
2129 ]);
2130 wp_die();
2131 }
2132
2133 /**
2134 * Fetch single conversation details for split-panel view
2135 */
2136 public function mxchat_fetch_conversation() {
2137 global $wpdb;
2138 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2139 $url_clicks_table = $wpdb->prefix . 'mxchat_url_clicks';
2140
2141 if (!current_user_can('manage_options')) {
2142 wp_send_json_error(['message' => 'Insufficient permissions']);
2143 wp_die();
2144 }
2145
2146 $session_id = isset($_POST['session_id']) ? sanitize_text_field($_POST['session_id']) : '';
2147 if (empty($session_id)) {
2148 wp_send_json_error(['message' => 'No session ID provided']);
2149 wp_die();
2150 }
2151
2152 // Check for optional columns/tables
2153 $url_table_exists = $wpdb->get_var("SHOW TABLES LIKE '$url_clicks_table'") === $url_clicks_table;
2154 $originating_columns_exist = !empty($wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'originating_page_url'"));
2155
2156 // Get session metadata
2157 $session_data = $originating_columns_exist
2158 ? $wpdb->get_row($wpdb->prepare(
2159 "SELECT user_email, user_name, user_identifier, originating_page_url, originating_page_title, timestamp
2160 FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC LIMIT 1",
2161 $session_id
2162 ))
2163 : $wpdb->get_row($wpdb->prepare(
2164 "SELECT user_email, user_name, user_identifier, timestamp FROM {$table_name}
2165 WHERE session_id = %s LIMIT 1",
2166 $session_id
2167 ));
2168
2169 if (!$session_data) {
2170 wp_send_json_error(['message' => 'Session not found']);
2171 wp_die();
2172 }
2173
2174 // Get clicked URLs
2175 $clicked_urls = [];
2176 if ($url_table_exists) {
2177 $url_clicks = $wpdb->get_results($wpdb->prepare(
2178 "SELECT DISTINCT clicked_url FROM {$url_clicks_table}
2179 WHERE session_id = %s ORDER BY click_timestamp ASC",
2180 $session_id
2181 ));
2182 foreach ($url_clicks as $click) {
2183 $clicked_urls[] = $click->clicked_url;
2184 }
2185 }
2186
2187 // Get all messages
2188 $messages = $wpdb->get_results($wpdb->prepare(
2189 "SELECT * FROM {$table_name} WHERE session_id = %s ORDER BY timestamp ASC",
2190 $session_id
2191 ));
2192
2193 // Build user info
2194 $user_email = !empty($session_data->user_email) ? $session_data->user_email : '';
2195 $user_name = !empty($session_data->user_name) ? $session_data->user_name : '';
2196 $user_identifier = !empty($session_data->user_identifier) ? $session_data->user_identifier : 'Guest';
2197
2198 $display_name = $user_name ?: ($user_email ? explode('@', $user_email)[0] : $user_identifier);
2199 $display_sub = $user_email ?: ('ID: ' . $user_identifier);
2200
2201 // Get initials
2202 $initials = strtoupper(substr($display_name, 0, 2));
2203 if (strlen($display_name) > 2 && strpos($display_name, ' ') !== false) {
2204 $parts = explode(' ', $display_name);
2205 $initials = strtoupper(substr($parts[0], 0, 1) . substr(end($parts), 0, 1));
2206 }
2207
2208 // Page info
2209 $page_url = $originating_columns_exist && !empty($session_data->originating_page_url) ? $session_data->originating_page_url : '';
2210 $page_title = '';
2211 if ($page_url) {
2212 $page_title = !empty($session_data->originating_page_title) ? $session_data->originating_page_title : parse_url($page_url, PHP_URL_PATH);
2213 }
2214
2215 // Format messages for output
2216 $formatted_messages = [];
2217 foreach ($messages as $msg) {
2218 $is_user = ($msg->role === 'user');
2219 $is_bot = ($msg->role === 'bot' || $msg->role === 'assistant');
2220
2221 $content = wp_kses(
2222 stripslashes($msg->message),
2223 [
2224 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2225 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2226 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []],
2227 'div' => ['class' => [], 'id' => [], 'data-nonce' => []],
2228 'img' => ['src' => [], 'alt' => [], 'class' => []],
2229 'h3' => ['class' => []],
2230 'h4' => ['class' => []],
2231 'button' => ['type' => [], 'class' => [], 'data-product-id' => [], 'data-nonce' => [], 'data-product-type' => [], 'data-original-text' => [], 'data-mxchat-action' => []],
2232 'select' => ['class' => [], 'data-attribute' => []],
2233 'option' => ['value' => []],
2234 'span' => ['class' => []],
2235 'del' => [], 'ins' => [],
2236 ]
2237 );
2238 $formatted_content = $this->format_transcript_message($content);
2239
2240 $has_rag = $is_bot && !empty($msg->rag_context);
2241
2242 $formatted_messages[] = [
2243 'id' => $msg->id,
2244 'role' => $msg->role,
2245 'is_user' => $is_user,
2246 'is_bot' => $is_bot,
2247 'content' => $formatted_content,
2248 'timestamp' => wp_date('g:i A', strtotime($msg->timestamp . ' UTC')),
2249 'full_timestamp' => wp_date('F j, Y g:i A', strtotime($msg->timestamp . ' UTC')),
2250 'has_rag' => $has_rag
2251 ];
2252 }
2253
2254 // First message timestamp for "started" display
2255 $started = !empty($messages) ? wp_date('M j, Y g:i A', strtotime($messages[0]->timestamp . ' UTC')) : '-';
2256
2257 wp_send_json([
2258 'success' => true,
2259 'session_id' => $session_id,
2260 'user' => [
2261 'name' => $display_name,
2262 'sub' => $display_sub,
2263 'initials' => $initials,
2264 'email' => $user_email,
2265 'identifier' => $user_identifier
2266 ],
2267 'page' => [
2268 'url' => $page_url,
2269 'title' => $page_title
2270 ],
2271 'clicked_urls' => $clicked_urls,
2272 'messages' => $formatted_messages,
2273 'message_count' => count($messages),
2274 'started' => $started
2275 ]);
2276 wp_die();
2277 }
2278
2279
2280 /**
2281 * ALTERNATIVE: Simpler helper method using string replacement
2282 */
2283 private function highlight_clicked_links($message_content, $clicked_urls) {
2284 if (empty($clicked_urls)) {
2285 return wp_kses(
2286 $message_content,
2287 [
2288 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2289 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2290 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
2291 ]
2292 );
2293 }
2294
2295 // First apply standard sanitization
2296 $message_content = wp_kses(
2297 $message_content,
2298 [
2299 'b' => [], 'strong' => [], 'i' => [], 'em' => [], 'u' => [],
2300 'br' => [], 'p' => [], 'ul' => [], 'ol' => [], 'li' => [],
2301 'a' => ['href' => [], 'title' => [], 'target' => [], 'class' => []]
2302 ]
2303 );
2304
2305 // Process each clicked URL
2306 foreach ($clicked_urls as $clicked_url) {
2307 // Try multiple patterns to catch different link formats
2308 $patterns = [
2309 // Standard link format
2310 '/<a([^>]*href=["\']' . preg_quote($clicked_url, '/') . '["\'][^>]*)>/i',
2311 // Link with trailing slash
2312 '/<a([^>]*href=["\']' . preg_quote(rtrim($clicked_url, '/'), '/') . '\/?["\'][^>]*)>/i',
2313 // Encoded entities version
2314 '/<a([^>]*href=["\']' . preg_quote(htmlentities($clicked_url), '/') . '["\'][^>]*)>/i',
2315 ];
2316
2317 foreach ($patterns as $pattern) {
2318 if (preg_match($pattern, $message_content)) {
2319 $message_content = preg_replace_callback(
2320 $pattern,
2321 function($matches) {
2322 $full_match = $matches[0];
2323 $attributes = $matches[1];
2324
2325 // Check if it already has the class
2326 if (strpos($full_match, 'mxchat-clicked-link') !== false) {
2327 return $full_match;
2328 }
2329
2330 // Check if class attribute exists
2331 if (preg_match('/class=["\']([^"\']*)["\']/', $attributes, $class_matches)) {
2332 // Add to existing class
2333 $new_attributes = preg_replace(
2334 '/class=["\']([^"\']*)["\']/',
2335 'class="$1 mxchat-clicked-link"',
2336 $attributes
2337 );
2338 } else {
2339 // Add new class attribute
2340 $new_attributes = $attributes . ' class="mxchat-clicked-link"';
2341 }
2342
2343 // Add title if not present
2344 if (strpos($new_attributes, 'title=') === false) {
2345 $new_attributes .= ' title="User clicked this link"';
2346 }
2347
2348 return '<a' . $new_attributes . '>';
2349 },
2350 $message_content
2351 );
2352
2353 // If we found and replaced, break out of the patterns loop
2354 break;
2355 }
2356 }
2357 }
2358
2359 return $message_content;
2360 }
2361
2362 public function mxchat_create_prompts_page() {
2363 //error_log('=== DEBUG: mxchat_create_prompts_page started ===');
2364
2365 global $wpdb;
2366 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
2367
2368 $knowledge_manager = MxChat_Knowledge_Manager::get_instance();
2369 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
2370
2371 // Display success message if all prompts were deleted
2372 if (isset($_GET['all_deleted']) && $_GET['all_deleted'] === 'true') {
2373 echo '<div class="notice notice-success is-dismissible"><p>' . esc_html__('All knowledge has been deleted successfully.', 'mxchat') . '</p><button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__('Dismiss this notice.', 'mxchat') . '</span></button></div>';
2374 }
2375
2376 // Set up pagination, search query, and content type filter
2377 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field($_GET['_wpnonce']) : '';
2378 $search_query = (!empty($nonce) && wp_verify_nonce($nonce, 'mxchat_prompts_search_nonce') && isset($_GET['search'])) ? sanitize_text_field($_GET['search']) : '';
2379 $content_type_filter = isset($_GET['content_type']) ? sanitize_key($_GET['content_type']) : ''; // ADDED 2.5.6
2380 $current_page = isset($_GET['paged']) ? absint($_GET['paged']) : 1;
2381 $per_page = 25;
2382
2383 //error_log('DEBUG: Search query: ' . $search_query);
2384 //error_log('DEBUG: Content type filter: ' . $content_type_filter);
2385 //error_log('DEBUG: Current page: ' . $current_page);
2386 //error_log('DEBUG: Per page: ' . $per_page);
2387
2388 // ================================
2389 // MULTI-BOT CONFIGURATION
2390 // ================================
2391
2392 // Check for multi-bot and set up bot selection
2393 if (class_exists('MxChat_Multi_Bot_Manager')) {
2394 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
2395 $available_bots = $multi_bot_manager->get_available_bots();
2396
2397 // Get saved bot selection (user-specific first, then site-wide default)
2398 $user_id = get_current_user_id();
2399 $saved_bot_id = get_user_meta($user_id, 'mxchat_selected_knowledge_bot', true);
2400 if (empty($saved_bot_id)) {
2401 $saved_bot_id = get_option('mxchat_current_knowledge_bot', 'default');
2402 }
2403
2404 // Allow URL override but default to saved selection
2405 $current_bot_id = isset($_GET['bot_id']) ? sanitize_key($_GET['bot_id']) : $saved_bot_id;
2406 $multibot_active = true;
2407 } else {
2408 $current_bot_id = 'default';
2409 $multibot_active = false;
2410 }
2411
2412 // ================================
2413 // DATA SOURCE CONFIGURATION
2414 // ================================
2415
2416 // Get bot-specific Pinecone settings
2417 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($current_bot_id);
2418 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
2419 $pinecone_api_key = $pinecone_options['mxchat_pinecone_api_key'] ?? '';
2420
2421 // Get OpenAI Vector Store settings
2422 $vectorstore_options = get_option('mxchat_openai_vectorstore_options', array());
2423 $use_vectorstore = ($vectorstore_options['mxchat_use_openai_vectorstore'] ?? '0') === '1';
2424
2425 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
2426 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Has API Key: ' . (!empty($pinecone_api_key) ? 'YES' : 'NO'));
2427 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
2428 //error_log('DEBUG: Bot ' . $current_bot_id . ' - Namespace: ' . ($pinecone_options['mxchat_pinecone_namespace'] ?? 'NOT SET'));
2429
2430 if ($use_pinecone && !empty($pinecone_api_key)) {
2431 //error_log('DEBUG: Using PINECONE data source with bot-specific config');
2432 // PINECONE DATA SOURCE
2433 $data_source = 'pinecone';
2434
2435 // IMPORTANT: Pass the bot-specific $pinecone_options, not default options!
2436 // UPDATED 2.5.6: Added content_type_filter parameter
2437 $records = $pinecone_manager->mxchat_fetch_pinecone_records($pinecone_options, $search_query, $current_page, $per_page, $current_bot_id, $content_type_filter);
2438
2439 // TEMPORARY DEBUG - Add this right after the fetch call
2440 //error_log('=== DEBUG: Bot switching issue ===');
2441 //error_log('Current bot ID: ' . $current_bot_id);
2442 //error_log('Use Pinecone: ' . ($use_pinecone ? 'YES' : 'NO'));
2443 //error_log('Records returned: ' . count($records['data'] ?? []));
2444 //error_log('Total records: ' . ($records['total'] ?? 0));
2445
2446 // Check first few records to see their bot_id
2447 if (!empty($records['data'])) {
2448 foreach (array_slice($records['data'], 0, 3) as $i => $record) {
2449 $record_bot_id = $record->bot_id ?? 'NOT_SET';
2450 //error_log('Record ' . ($i+1) . ' bot_id: ' . $record_bot_id . ', content preview: ' . substr($record->article_content ?? '', 0, 30) . '...');
2451 }
2452 }
2453 //error_log('=== END DEBUG ===');
2454
2455 $total_records = $records['total'] ?? 0;
2456 $prompts = $records['data'] ?? array();
2457 $total_in_database = $records['total_in_database'] ?? 0;
2458 $showing_recent_only = $records['showing_recent_only'] ?? false;
2459
2460 $total_pages = ceil($total_records / $per_page);
2461
2462 } else {
2463 //error_log('DEBUG: Using WORDPRESS DB data source');
2464 // WORDPRESS DB DATA SOURCE (your existing logic)
2465 $data_source = 'wordpress';
2466
2467 // Initialize these variables for WordPress DB
2468 $total_in_database = 0;
2469 $showing_recent_only = false;
2470
2471 $offset = ($current_page - 1) * $per_page;
2472
2473 // UPDATED 2.5.6: Build WHERE clause for search and content type filtering
2474 $where_clauses = array();
2475 $where_values = array();
2476
2477 if ($search_query) {
2478 $where_clauses[] = "article_content LIKE %s";
2479 $where_values[] = '%' . $wpdb->esc_like($search_query) . '%';
2480 }
2481
2482 if ($content_type_filter) {
2483 $where_clauses[] = "content_type = %s";
2484 $where_values[] = $content_type_filter;
2485 }
2486
2487 $sql_where = "";
2488 if (!empty($where_clauses)) {
2489 $sql_where = "WHERE " . implode(" AND ", $where_clauses);
2490 }
2491
2492 // UPDATED 2.6.3: Count unique entries (by source_url) instead of individual rows
2493 // This ensures pagination shows X entries per page, not X chunks
2494 // Entries with empty source_url are counted individually
2495 if (!empty($where_values)) {
2496 // Count unique source_urls + count of rows with empty source_url
2497 $count_query = $wpdb->prepare(
2498 "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} {$sql_where} AND source_url != '') +
2499 (SELECT COUNT(*) FROM {$table_name} {$sql_where} AND (source_url = '' OR source_url IS NULL))",
2500 array_merge($where_values, $where_values)
2501 );
2502 } else {
2503 $count_query = "SELECT (SELECT COUNT(DISTINCT source_url) FROM {$table_name} WHERE source_url != '') +
2504 (SELECT COUNT(*) FROM {$table_name} WHERE source_url = '' OR source_url IS NULL)";
2505 }
2506 $total_records = $wpdb->get_var($count_query);
2507 $total_pages = ceil($total_records / $per_page);
2508
2509 // UPDATED 2.6.3: Get unique source_urls for pagination, then fetch all their rows
2510 // Step 1: Get the source_urls for this page (distinct URLs ordered by latest timestamp)
2511 if (!empty($where_values)) {
2512 $urls_query = $wpdb->prepare(
2513 "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name} {$sql_where}
2514 GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
2515 array_merge($where_values, array($per_page, $offset))
2516 );
2517 } else {
2518 $urls_query = $wpdb->prepare(
2519 "SELECT source_url, MAX(timestamp) as latest_ts FROM {$table_name}
2520 GROUP BY source_url ORDER BY latest_ts DESC LIMIT %d OFFSET %d",
2521 $per_page, $offset
2522 );
2523 }
2524 $page_urls = $wpdb->get_results($urls_query);
2525
2526 // Step 2: Fetch all rows for these source_urls
2527 $prompts = array();
2528 if (!empty($page_urls)) {
2529 // Build URL order map to preserve newest-first ordering from step 1
2530 $url_order_map = array();
2531 $url_list = array();
2532 $has_empty_url = false;
2533 $order_index = 0;
2534 foreach ($page_urls as $url_row) {
2535 if (empty($url_row->source_url)) {
2536 $has_empty_url = true;
2537 $url_order_map['__empty__'] = $order_index++;
2538 } else {
2539 $url_list[] = $url_row->source_url;
2540 $url_order_map[$url_row->source_url] = $order_index++;
2541 }
2542 }
2543
2544 // Build query to fetch all rows for these URLs
2545 $url_conditions = array();
2546 $url_values = array();
2547
2548 if (!empty($url_list)) {
2549 $placeholders = implode(',', array_fill(0, count($url_list), '%s'));
2550 $url_conditions[] = "source_url IN ($placeholders)";
2551 $url_values = array_merge($url_values, $url_list);
2552 }
2553
2554 if ($has_empty_url) {
2555 $url_conditions[] = "(source_url = '' OR source_url IS NULL)";
2556 }
2557
2558 if (!empty($url_conditions)) {
2559 $url_where = "WHERE (" . implode(" OR ", $url_conditions) . ")";
2560
2561 // Add original filters back
2562 if (!empty($where_clauses)) {
2563 $url_where .= " AND " . implode(" AND ", $where_clauses);
2564 $url_values = array_merge($url_values, $where_values);
2565 }
2566
2567 if (!empty($url_values)) {
2568 $prompts_query = $wpdb->prepare(
2569 "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC",
2570 $url_values
2571 );
2572 } else {
2573 $prompts_query = "SELECT * FROM {$table_name} {$url_where} ORDER BY timestamp DESC";
2574 }
2575
2576 $prompts = $wpdb->get_results($prompts_query);
2577
2578 // Sort prompts by the original URL order (newest first), then by timestamp within each URL group
2579 usort($prompts, function($a, $b) use ($url_order_map) {
2580 $url_a = empty($a->source_url) ? '__empty__' : $a->source_url;
2581 $url_b = empty($b->source_url) ? '__empty__' : $b->source_url;
2582 $order_a = $url_order_map[$url_a] ?? PHP_INT_MAX;
2583 $order_b = $url_order_map[$url_b] ?? PHP_INT_MAX;
2584
2585 // First sort by URL order (newest URLs first)
2586 if ($order_a !== $order_b) {
2587 return $order_a - $order_b;
2588 }
2589
2590 // Within same URL, sort by timestamp DESC (newest chunks first)
2591 return strtotime($b->timestamp) - strtotime($a->timestamp);
2592 });
2593 }
2594 }
2595 }
2596
2597 // ================================
2598 // PAGINATION GENERATION
2599 // ================================
2600
2601 // Generate pagination links
2602 if ($total_pages > 1) {
2603 // Build a clean base URL with only necessary parameters
2604 $pagination_args = array('page' => 'mxchat-prompts');
2605
2606 // Preserve bot_id parameter if multi-bot is active
2607 if ($multibot_active && !empty($current_bot_id) && $current_bot_id !== 'default') {
2608 $pagination_args['bot_id'] = $current_bot_id;
2609 }
2610
2611 // Preserve search query and nonce if present
2612 if ($search_query) {
2613 $pagination_args['search'] = $search_query;
2614 if (!empty($nonce)) {
2615 $pagination_args['_wpnonce'] = $nonce;
2616 }
2617 }
2618
2619 // Preserve content type filter if present
2620 if ($content_type_filter) {
2621 $pagination_args['content_type'] = $content_type_filter;
2622 }
2623
2624 // Build clean base URL
2625 $base_url = add_query_arg($pagination_args, admin_url('admin.php'));
2626
2627 $page_links = paginate_links(array(
2628 'base' => $base_url . '%_%',
2629 'format' => '&paged=%#%',
2630 'prev_text' => __('&laquo; Previous', 'mxchat'),
2631 'next_text' => __('Next &raquo;', 'mxchat'),
2632 'total' => $total_pages,
2633 'current' => $current_page,
2634 'type' => 'plain',
2635 ));
2636 } else {
2637 $page_links = '';
2638 }
2639
2640 // ================================
2641 // PROCESSING STATUS RETRIEVAL
2642 // ================================
2643
2644 // Retrieve processing statuses using queue-based method
2645 $processing_statuses = $knowledge_manager->mxchat_get_processing_statuses();
2646 $pdf_status = $processing_statuses['pdf_status'];
2647 $sitemap_status = $processing_statuses['sitemap_status'];
2648 $is_processing = $processing_statuses['is_processing'];
2649
2650 //error_log('=== DEBUG: mxchat_create_prompts_page data preparation completed ===');
2651
2652 // ================================
2653 // RENDER PAGE WITH NEW SIDEBAR LAYOUT
2654 // ================================
2655
2656 // Include the new knowledge page template
2657 require_once plugin_dir_path(__FILE__) . 'admin-knowledge-page.php';
2658
2659 // Package all page data for the render function
2660 $page_data = array(
2661 'prompts' => $prompts,
2662 'total_records' => $total_records,
2663 'total_pages' => $total_pages,
2664 'current_page' => $current_page,
2665 'per_page' => $per_page,
2666 'page_links' => $page_links,
2667 'search_query' => $search_query,
2668 'content_type_filter' => $content_type_filter,
2669 'data_source' => $data_source,
2670 'use_pinecone' => $use_pinecone,
2671 'use_vectorstore' => $use_vectorstore,
2672 'multibot_active' => $multibot_active,
2673 'current_bot_id' => $current_bot_id,
2674 'pdf_status' => $pdf_status,
2675 'sitemap_status' => $sitemap_status,
2676 'is_processing' => $is_processing,
2677 'total_in_database' => $total_in_database ?? 0,
2678 'showing_recent_only' => $showing_recent_only ?? false,
2679 );
2680
2681 // Render the new sidebar-based page
2682 mxchat_render_knowledge_page($this, $knowledge_manager, $page_data);
2683 }
2684
2685 /**
2686 * Get bot-specific Pinecone configuration
2687 * Used in the knowledge retrieval functions
2688 */
2689 private function get_bot_pinecone_config($bot_id = 'default') {
2690 //error_log("MXCHAT DEBUG: get_bot_pinecone_config called for bot: " . $bot_id);
2691
2692 // If default bot or multi-bot add-on not active, use default Pinecone config
2693 if ($bot_id === 'default' || !class_exists('MxChat_Multi_Bot_Manager')) {
2694 //error_log("MXCHAT DEBUG: Using default Pinecone config (no multi-bot or bot is 'default')");
2695 $addon_options = get_option('mxchat_pinecone_addon_options', array());
2696 $config = array(
2697 'use_pinecone' => (isset($addon_options['mxchat_use_pinecone']) && $addon_options['mxchat_use_pinecone'] === '1'),
2698 'api_key' => $addon_options['mxchat_pinecone_api_key'] ?? '',
2699 'host' => $addon_options['mxchat_pinecone_host'] ?? '',
2700 'namespace' => $addon_options['mxchat_pinecone_namespace'] ?? ''
2701 );
2702 //error_log("MXCHAT DEBUG: Default config - use_pinecone: " . ($config['use_pinecone'] ? 'true' : 'false'));
2703 return $config;
2704 }
2705
2706 //error_log("MXCHAT DEBUG: Calling filter 'mxchat_get_bot_pinecone_config' for bot: " . $bot_id);
2707
2708 // Hook for multi-bot add-on to provide bot-specific Pinecone config
2709 $bot_pinecone_config = apply_filters('mxchat_get_bot_pinecone_config', array(), $bot_id);
2710
2711 if (!empty($bot_pinecone_config)) {
2712 //error_log("MXCHAT DEBUG: Got bot-specific config from filter");
2713 //error_log(" - use_pinecone: " . (isset($bot_pinecone_config['use_pinecone']) ? ($bot_pinecone_config['use_pinecone'] ? 'true' : 'false') : 'not set'));
2714 //error_log(" - host: " . ($bot_pinecone_config['host'] ?? 'not set'));
2715 //error_log(" - namespace: " . ($bot_pinecone_config['namespace'] ?? 'not set'));
2716 } else {
2717 //error_log("MXCHAT DEBUG: Filter returned empty config!");
2718 }
2719
2720 return is_array($bot_pinecone_config) ? $bot_pinecone_config : array();
2721 }
2722
2723 public function mxchat_delete_chat_history() {
2724 if (!current_user_can('manage_options')) {
2725 echo wp_json_encode(['error' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2726 wp_die();
2727 }
2728 check_ajax_referer('mxchat_delete_chat_history', 'security');
2729 global $wpdb;
2730 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2731
2732 if (isset($_POST['delete_session_ids']) && is_array($_POST['delete_session_ids'])) {
2733 $deleted_count = 0;
2734 $translations_table = $wpdb->prefix . 'mxchat_transcript_translations';
2735
2736 foreach ($_POST['delete_session_ids'] as $session_id) {
2737 $session_id_sanitized = sanitize_text_field($session_id);
2738
2739 // Clear relevant cache before deletion
2740 $cache_key = 'chat_session_' . $session_id_sanitized;
2741 wp_cache_delete($cache_key, 'mxchat_chat_sessions');
2742
2743 // Perform the deletion from the database table
2744 $wpdb->delete($table_name, ['session_id' => $session_id_sanitized]);
2745
2746 // Delete any saved translations for this session
2747 if ($wpdb->get_var("SHOW TABLES LIKE '$translations_table'") === $translations_table) {
2748 $wpdb->delete($translations_table, ['session_id' => $session_id_sanitized]);
2749 }
2750
2751 // Delete the corresponding option entry from wp_options table
2752 delete_option("mxchat_history_" . $session_id_sanitized);
2753
2754 // Delete any associated metadata options
2755 delete_option("mxchat_email_" . $session_id_sanitized);
2756 delete_option("mxchat_agent_name_" . $session_id_sanitized);
2757
2758 $deleted_count++;
2759 }
2760
2761 // Optionally, clear a general cache if you have one
2762 wp_cache_delete('all_chat_sessions', 'mxchat_chat_sessions');
2763
2764 echo wp_json_encode([
2765 'success' => sprintf(
2766 esc_html__('%d chat session(s) have been deleted from all storage locations.', 'mxchat'),
2767 $deleted_count
2768 )
2769 ]);
2770 } else {
2771 echo wp_json_encode(['error' => esc_html__('No chat sessions selected for deletion.', 'mxchat')]);
2772 }
2773
2774 wp_die();
2775 }
2776
2777 /**
2778 * Format transcript message content with markdown processing
2779 * Converts markdown links, bold, italic, code blocks, and plain URLs to HTML
2780 */
2781 private function format_transcript_message($text) {
2782 if (empty($text)) {
2783 return '';
2784 }
2785
2786 // Normalize line endings and clean up excessive whitespace
2787 $text = str_replace("\r\n", "\n", $text);
2788 $text = str_replace("\r", "\n", $text);
2789
2790 // Clean up existing <br> tags that may have been saved (legacy data)
2791 // Convert <br>, <br/>, <br /> back to newlines for consistent processing
2792 $text = preg_replace('/<br\s*\/?>\s*/i', "\n", $text);
2793
2794 // Collapse 3+ consecutive newlines to just 2 (paragraph break)
2795 $text = preg_replace('/\n{3,}/', "\n\n", $text);
2796
2797 // Process markdown headers (# Header)
2798 $text = preg_replace_callback('/^(#{1,6})\s+(.+)$/m', function($matches) {
2799 $level = strlen($matches[1]);
2800 $content = esc_html(trim($matches[2]));
2801 return "<h{$level}>{$content}</h{$level}>";
2802 }, $text);
2803
2804 // Process code blocks with triple backticks
2805 $text = preg_replace_callback('/```(\w+)?\n?([\s\S]*?)```/', function($matches) {
2806 $language = !empty($matches[1]) ? ' class="language-' . esc_attr($matches[1]) . '"' : '';
2807 $code = esc_html($matches[2]);
2808 return "<pre><code{$language}>{$code}</code></pre>";
2809 }, $text);
2810
2811 // Process inline code with single backticks
2812 $text = preg_replace('/`([^`]+)`/', '<code>$1</code>', $text);
2813
2814 // Process bold text **text** or __text__
2815 $text = preg_replace('/\*\*(.+?)\*\*/', '<strong>$1</strong>', $text);
2816 $text = preg_replace('/__(.+?)__/', '<strong>$1</strong>', $text);
2817
2818 // Process italic text *text* or _text_ (but not if part of URL)
2819 $text = preg_replace('/(?<![*_\w])\*([^*]+)\*(?![*\w])/', '<em>$1</em>', $text);
2820 $text = preg_replace('/(?<![*_\w])_([^_]+)_(?![*\w])/', '<em>$1</em>', $text);
2821
2822 // Process markdown links [text](url)
2823 $text = preg_replace_callback('/\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/', function($matches) {
2824 $link_text = esc_html($matches[1]);
2825 $url = esc_url($matches[2]);
2826 return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$link_text}</a>";
2827 }, $text);
2828
2829 // Process citation-style brackets [URL]
2830 $text = preg_replace_callback('/\[(https?:\/\/[^\]]+)\]/', function($matches) {
2831 $url = esc_url($matches[1]);
2832 return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$url}</a>";
2833 }, $text);
2834
2835 // Process standalone URLs (not already in links or img src)
2836 $text = preg_replace_callback(
2837 '/(?<!href="|src="|">)(https?:\/\/[^\s<>"]+)(?![^<]*<\/a>)/',
2838 function($matches) {
2839 $url = esc_url($matches[1]);
2840 // Truncate display URL if too long
2841 $display = strlen($matches[1]) > 50 ? substr($matches[1], 0, 47) . '...' : $matches[1];
2842 return "<a href=\"{$url}\" target=\"_blank\" rel=\"noopener\">{$display}</a>";
2843 },
2844 $text
2845 );
2846
2847 // Process mailto links
2848 $text = preg_replace_callback('/\[([^\]]+)\]\((mailto:[^)]+)\)/', function($matches) {
2849 $link_text = esc_html($matches[1]);
2850 $mailto = esc_url($matches[2]);
2851 return "<a href=\"{$mailto}\">{$link_text}</a>";
2852 }, $text);
2853
2854 // Convert paragraphs: split by double newlines, wrap in <p> tags
2855 // This creates proper paragraph structure instead of excessive <br> tags
2856 $paragraphs = preg_split('/\n\n+/', $text);
2857
2858 // Filter out empty paragraphs but preserve content like "0"
2859 $paragraphs = array_values(array_filter(array_map('trim', $paragraphs), function($p) {
2860 return $p !== '';
2861 }));
2862
2863 if (empty($paragraphs)) {
2864 // No content after filtering
2865 return '';
2866 } elseif (count($paragraphs) > 1) {
2867 // Multiple paragraphs - wrap each in <p> tags, convert single newlines to <br>
2868 $formatted_paragraphs = array_map(function($p) {
2869 return nl2br($p);
2870 }, $paragraphs);
2871 $text = '<p>' . implode('</p><p>', $formatted_paragraphs) . '</p>';
2872 } else {
2873 // Single paragraph - just convert newlines to <br>
2874 $text = nl2br($paragraphs[0]);
2875 }
2876
2877 return $text;
2878 }
2879
2880 /**
2881 * AJAX handler to fetch RAG context for a specific message
2882 * Used by the transcript viewer to show retrieved documents
2883 */
2884 public function mxchat_get_rag_context() {
2885 // Check permissions
2886 if (!current_user_can('manage_options')) {
2887 wp_send_json_error(['message' => esc_html__('You do not have sufficient permissions.', 'mxchat')]);
2888 wp_die();
2889 }
2890
2891 // Validate message ID
2892 if (!isset($_POST['message_id']) || empty($_POST['message_id'])) {
2893 wp_send_json_error(['message' => esc_html__('Message ID is required.', 'mxchat')]);
2894 wp_die();
2895 }
2896
2897 $message_id = absint($_POST['message_id']);
2898
2899 global $wpdb;
2900 $table_name = $wpdb->prefix . 'mxchat_chat_transcripts';
2901
2902 // Fetch the RAG context for this message
2903 $result = $wpdb->get_row($wpdb->prepare(
2904 "SELECT rag_context FROM {$table_name} WHERE id = %d",
2905 $message_id
2906 ));
2907
2908 if (!$result || empty($result->rag_context)) {
2909 wp_send_json_error(['message' => esc_html__('No RAG context found for this message.', 'mxchat')]);
2910 wp_die();
2911 }
2912
2913 // Decode the JSON data
2914 $rag_context = json_decode($result->rag_context, true);
2915
2916 if (json_last_error() !== JSON_ERROR_NONE) {
2917 wp_send_json_error(['message' => esc_html__('Invalid RAG context data.', 'mxchat')]);
2918 wp_die();
2919 }
2920
2921 wp_send_json_success($rag_context);
2922 wp_die();
2923 }
2924
2925 public function display_admin_notices() {
2926 // Check if we're on a MXChat admin page
2927 $screen = get_current_screen();
2928 if (!$screen || strpos($screen->base, 'mxchat') === false) {
2929 return;
2930 }
2931
2932 $dismiss_button = '<button type="button" class="notice-dismiss"><span class="screen-reader-text">' . esc_html__('Dismiss this notice.', 'mxchat') . '</span></button>';
2933
2934 // Check for error notices
2935 $error_notice = get_transient('mxchat_admin_notice_error');
2936 if ($error_notice) {
2937 echo '<div class="notice notice-error is-dismissible"><p>' . wp_kses_post($error_notice) . '</p>' . $dismiss_button . '</div>';
2938 delete_transient('mxchat_admin_notice_error');
2939 }
2940
2941 // Check for success notices
2942 $success_notice = get_transient('mxchat_admin_notice_success');
2943 if ($success_notice) {
2944 echo '<div class="notice notice-success is-dismissible"><p>' . wp_kses_post($success_notice) . '</p>' . $dismiss_button . '</div>';
2945 delete_transient('mxchat_admin_notice_success');
2946 }
2947
2948 // Check for info notices
2949 $info_notice = get_transient('mxchat_admin_notice_info');
2950 if ($info_notice) {
2951 echo '<div class="notice notice-info is-dismissible"><p>' . wp_kses_post($info_notice) . '</p>' . $dismiss_button . '</div>';
2952 delete_transient('mxchat_admin_notice_info');
2953 }
2954 }
2955
2956
2957 public function mxchat_create_activation_page() {
2958 // Include the new Pro & Extensions page template
2959 require_once plugin_dir_path(__FILE__) . 'admin-pro-page.php';
2960
2961 // Get addons configuration from the MxChat_Addons class
2962 require_once plugin_dir_path(__FILE__) . 'class-mxchat-addons.php';
2963 $addons_instance = new MxChat_Addons();
2964 $addons_config = $addons_instance->get_addons_config();
2965
2966 // Render the consolidated Pro & Extensions page
2967 mxchat_render_pro_page($this, $addons_config);
2968 }
2969
2970 /**
2971 * Check if current activation is linked to a domain
2972 * This checks YOUR website's database, not the user's local database
2973 */
2974 public function is_current_activation_linked($domain) {
2975 $license_key = get_option('mxchat_activation_key');
2976 $email = get_option('mxchat_pro_email');
2977
2978 if (empty($license_key) || empty($email)) {
2979 return false;
2980 }
2981
2982 // Check with YOUR website's API
2983 $response = wp_remote_post('https://mxchat.ai/mxchat-api/check-domain', array(
2984 'body' => array(
2985 'license_key' => $license_key,
2986 'email' => $email,
2987 'domain' => $domain
2988 ),
2989 'timeout' => 10,
2990 'sslverify' => false
2991 ));
2992
2993 if (is_wp_error($response)) {
2994 return false;
2995 }
2996
2997 $body = json_decode(wp_remote_retrieve_body($response), true);
2998 return isset($body['success']) && $body['success'] && isset($body['data']['linked']) && $body['data']['linked'];
2999 }
3000
3001
3002 public function mxchat_actions_page_html() {
3003 if (!current_user_can('manage_options')) {
3004 return;
3005 }
3006
3007 global $wpdb;
3008 $table_name = $wpdb->prefix . 'mxchat_intents';
3009
3010 // Get stats for dashboard
3011 $total_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name");
3012 $enabled_actions = $wpdb->get_var("SELECT COUNT(*) FROM $table_name WHERE enabled = 1");
3013 $disabled_actions = $total_actions - $enabled_actions;
3014
3015 // Get unique action types count
3016 $action_types_count = $wpdb->get_var("SELECT COUNT(DISTINCT callback_function) FROM $table_name");
3017
3018 // Get action type distribution
3019 $type_distribution_raw = $wpdb->get_results("SELECT callback_function, COUNT(*) as count FROM $table_name GROUP BY callback_function ORDER BY count DESC LIMIT 10");
3020 $available_callbacks = $this->mxchat_get_available_callbacks();
3021 $callback_groups = $this->mxchat_get_available_callbacks(true, true);
3022
3023 $action_type_distribution = array();
3024 foreach ($type_distribution_raw as $row) {
3025 $label = isset($available_callbacks[$row->callback_function]['label'])
3026 ? $available_callbacks[$row->callback_function]['label']
3027 : $row->callback_function;
3028 $action_type_distribution[$label] = $row->count;
3029 }
3030
3031 // Prepare page data
3032 $page_data = array(
3033 'total_actions' => $total_actions,
3034 'enabled_actions' => $enabled_actions,
3035 'disabled_actions' => $disabled_actions,
3036 'action_types_count' => $action_types_count,
3037 'action_type_distribution' => $action_type_distribution,
3038 'available_callbacks' => $available_callbacks,
3039 'callback_groups' => $callback_groups,
3040 );
3041
3042 // Include and render the new template
3043 require_once plugin_dir_path(__FILE__) . 'admin-actions-page.php';
3044 mxchat_render_actions_page($this, $page_data);
3045 }
3046
3047 /**
3048 * LEGACY HTML - Preserved below for reference, to be removed in future update
3049 */
3050 function mxchat_actions_page_legacy_html() {
3051 // This function is deprecated and no longer used
3052 // The new template is in includes/admin-actions-page.php
3053 ?>
3054 <div class="wrap mxchat-wrapper">
3055 <!-- Hero Section -->
3056 <div class="mxchat-hero">
3057 <h1 class="mxchat-main-title">
3058 <span class="mxchat-gradient-text">Actions</span> Manager
3059 </h1>
3060 <p class="mxchat-hero-subtitle">
3061 <?php esc_html_e('Create and manage custom actions to enhance your chatbot\'s capabilities.', 'mxchat'); ?>
3062 </p>
3063 </div>
3064
3065 <!-- Actions Header with Search and Filter -->
3066 <div class="mxchat-actions-header">
3067 <div class="mxchat-actions-filters">
3068 <form method="get" class="mxchat-search-form">
3069 <input type="hidden" name="page" value="mxchat-actions">
3070 <div class="mxchat-search-group">
3071 <span class="dashicons dashicons-search"></span>
3072 <input type="text" name="s" class="mxchat-search-input"
3073 placeholder="<?php esc_attr_e('Search Actions', 'mxchat'); ?>"
3074 value="<?php echo esc_attr($search_term); ?>">
3075 </div>
3076 <select name="callback_filter" class="mxchat-action-filter">
3077 <option value=""><?php esc_html_e('All Action Types', 'mxchat'); ?></option>
3078 <?php foreach ($available_callbacks as $function => $callback_data) :
3079 $label = $callback_data['label']; ?>
3080 <option value="<?php echo esc_attr($function); ?>"
3081 <?php selected($callback_filter, $function); ?>>
3082 <?php echo esc_html($label); ?>
3083 </option>
3084 <?php endforeach; ?>
3085 </select>
3086 <button type="submit" class="mxchat-button-secondary">
3087 <?php esc_html_e('Filter', 'mxchat'); ?>
3088 </button>
3089 </form>
3090 </div>
3091 <div class="mxchat-actions-controls">
3092 <button type="button" id="mxchat-add-action-btn" class="mxchat-button-primary">
3093 <span class="dashicons dashicons-plus-alt"></span>
3094 <?php esc_html_e('Add New Action', 'mxchat'); ?>
3095 </button>
3096 </div>
3097 </div>
3098
3099 <!-- Actions Grid Layout - All actions in a single grid -->
3100 <div class="mxchat-actions-grid">
3101 <div class="mxchat-cards-container">
3102 <?php if (!empty($actions)) : ?>
3103 <?php foreach ($actions as $action) :
3104 $callback_function = $action->callback_function;
3105 $callback_label = isset($available_callbacks[$callback_function]['label'])
3106 ? $available_callbacks[$callback_function]['label']
3107 : $callback_function;
3108 $threshold_value = isset($action->similarity_threshold)
3109 ? round($action->similarity_threshold * 100)
3110 : 85;
3111
3112 // Check if this is a form action
3113 $is_form_action = strpos($action->intent_label, 'Form ') === 0;
3114
3115 // Get action status (enabled/disabled) - default to true if column doesn't exist
3116 $is_enabled = isset($action->enabled) ? (bool)$action->enabled : true;
3117
3118 // Get enabled bots for display
3119 $enabled_bots = [];
3120 if (isset($action->enabled_bots) && !empty($action->enabled_bots)) {
3121 $enabled_bots = json_decode($action->enabled_bots, true);
3122 if (!is_array($enabled_bots)) {
3123 $enabled_bots = ['default'];
3124 }
3125 } else {
3126 $enabled_bots = ['default']; // Backward compatibility
3127 }
3128 ?>
3129 <div class="mxchat-action-card <?php echo $is_form_action ? 'mxchat-form-action' : ''; ?>">
3130 <div class="mxchat-card-header">
3131 <div class="mxchat-card-title"><?php echo esc_html($action->intent_label); ?></div>
3132 <div class="mxchat-card-toggle">
3133 <label class="mxchat-switch">
3134 <input type="checkbox" class="mxchat-action-toggle"
3135 data-action-id="<?php echo esc_attr($action->id); ?>"
3136 <?php checked($is_enabled); ?>>
3137 <span class="mxchat-slider round"></span>
3138 </label>
3139 </div>
3140 </div>
3141
3142 <div class="mxchat-card-body">
3143 <div class="mxchat-card-description">
3144 <strong><?php esc_html_e('Type:', 'mxchat'); ?></strong>
3145 <?php echo esc_html($callback_label); ?>
3146 </div>
3147
3148 <div class="mxchat-card-phrases">
3149 <strong><?php esc_html_e('Trigger phrases:', 'mxchat'); ?></strong>
3150 <div class="mxchat-phrases-preview">
3151 <?php
3152 // Check if the helper function exists, otherwise use a simple substring
3153 if (method_exists($this, 'get_trimmed_phrases')) {
3154 echo esc_html($this->get_trimmed_phrases($action->phrases));
3155 } else {
3156 echo esc_html(strlen($action->phrases) > 100 ?
3157 substr($action->phrases, 0, 97) . '...' :
3158 $action->phrases);
3159 }
3160 ?>
3161 </div>
3162 </div>
3163
3164 <div class="mxchat-threshold-control">
3165 <div class="mxchat-threshold-label">
3166 <?php esc_html_e('Similarity Threshold:', 'mxchat'); ?>
3167 <span class="mxchat-threshold-value"><?php echo esc_html($threshold_value); ?>%</span>
3168 </div>
3169 </div>
3170
3171 <!-- Bot Availability Display (Read-only) -->
3172 <div class="mxchat-card-bots">
3173 <strong><?php esc_html_e('Assigned bots:', 'mxchat'); ?></strong>
3174 <div class="mxchat-bot-badges">
3175 <?php
3176 foreach ($enabled_bots as $bot_id) {
3177 if ($bot_id === 'default') {
3178 echo '<span class="mxchat-bot-badge">' . esc_html__('Default Bot', 'mxchat') . '</span>';
3179 } else {
3180 // Try to get bot name from multi-bot manager
3181 if (class_exists('MxChat_Multi_Bot_Core_Manager')) {
3182 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
3183 $available_bots = $multi_bot_manager->get_available_bots();
3184 $bot_name = isset($available_bots[$bot_id]) ? $available_bots[$bot_id] : $bot_id;
3185 echo '<span class="mxchat-bot-badge">' . esc_html($bot_name) . '</span>';
3186 } else {
3187 echo '<span class="mxchat-bot-badge">' . esc_html($bot_id) . '</span>';
3188 }
3189 }
3190 }
3191 ?>
3192 </div>
3193 </div>
3194 </div>
3195
3196 <div class="mxchat-card-footer">
3197 <?php
3198 // Check if it's a form action
3199 $is_form_action = preg_match('/Form (\d+)/', $action->intent_label, $form_matches);
3200
3201 // Check if it's a recommendation flow action
3202 $is_flow_action = preg_match('/Recommendation Flow (\d+)/', $action->intent_label, $flow_matches);
3203
3204 if ($is_form_action) {
3205 $form_id = isset($form_matches[1]) ? $form_matches[1] : '';
3206 ?>
3207 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-forms&action=edit&form_id=' . $form_id)); ?>"
3208 class="mxchat-button-primary">
3209 <span class="dashicons dashicons-feedback"></span>
3210 <?php esc_html_e('Edit Form', 'mxchat'); ?>
3211 </a>
3212 <?php } elseif ($is_flow_action) {
3213 ?>
3214 <a href="<?php echo esc_url(admin_url('admin.php?page=mxchat-smart-recommender')); ?>"
3215 class="mxchat-button-primary">
3216 <span class="dashicons dashicons-list-view"></span>
3217 <?php esc_html_e('Manage Flows', 'mxchat'); ?>
3218 </a>
3219 <?php } else { ?>
3220 <button type="button"
3221 class="mxchat-button-secondary mxchat-edit-button"
3222 data-action-id="<?php echo esc_attr($action->id); ?>"
3223 data-phrases="<?php echo esc_attr($action->phrases); ?>"
3224 data-label="<?php echo esc_attr($action->intent_label); ?>"
3225 data-threshold="<?php echo esc_attr(round($action->similarity_threshold * 100)); ?>"
3226 data-callback-function="<?php echo esc_attr($action->callback_function); ?>"
3227 data-enabled-bots="<?php echo esc_attr(json_encode($enabled_bots)); ?>">
3228 <span class="dashicons dashicons-edit"></span>
3229 <?php esc_html_e('Edit', 'mxchat'); ?>
3230 </button>
3231 <form method="post"
3232 action="<?php echo esc_url(admin_url('admin-post.php')); ?>"
3233 class="mxchat-delete-form"
3234 onsubmit="return confirm('<?php esc_attr_e('Are you sure you want to delete this action?', 'mxchat'); ?>');">
3235 <?php wp_nonce_field('mxchat_delete_intent_nonce'); ?>
3236 <input type="hidden" name="action" value="mxchat_delete_intent">
3237 <input type="hidden" name="intent_id" value="<?php echo esc_attr($action->id); ?>">
3238 <button type="submit" class="mxchat-button-text mxchat-delete-button">
3239 <span class="dashicons dashicons-trash"></span>
3240 <?php esc_html_e('Delete', 'mxchat'); ?>
3241 </button>
3242 </form>
3243 <?php } ?>
3244 </div>
3245 </div>
3246 <?php endforeach; ?>
3247 <?php else : ?>
3248 <!-- If no actions found -->
3249 <div class="mxchat-no-actions">
3250 <div class="mxchat-empty-state">
3251 <span class="dashicons dashicons-format-chat"></span>
3252 <h2><?php esc_html_e('No actions found', 'mxchat'); ?></h2>
3253 <p><?php esc_html_e('Get started by creating your first action to enhance your chatbot.', 'mxchat'); ?></p>
3254 <button type="button" id="mxchat-create-first-action" class="mxchat-button-primary">
3255 <?php esc_html_e('Create Your First Action', 'mxchat'); ?>
3256 </button>
3257 </div>
3258 </div>
3259 <?php endif; ?>
3260 </div>
3261 </div>
3262
3263 <?php if ($total_pages > 1) : ?>
3264 <div class="mxchat-pagination">
3265 <?php
3266 echo paginate_links(array(
3267 'base' => add_query_arg('paged', '%#%'),
3268 'format' => '',
3269 'prev_text' => __('&laquo; Previous', 'mxchat'),
3270 'next_text' => __('Next &raquo;', 'mxchat'),
3271 'total' => $total_pages,
3272 'current' => $page
3273 ));
3274 ?>
3275 </div>
3276 <?php endif; ?>
3277
3278 <!-- Add/Edit Action Modal with Step-Based Approach -->
3279 <!-- Complete Modal HTML with Defined Groups Variable -->
3280 <div id="mxchat-action-modal" class="mxchat-modal" style="display: none;">
3281 <div class="mxchat-modal-content">
3282 <span class="mxchat-modal-close">&times;</span>
3283
3284 <form id="mxchat-action-form" method="post" action="<?php echo esc_url(admin_url('admin-post.php')); ?>">
3285 <!-- Dynamic nonce field -->
3286 <div id="action-nonce-container">
3287 <?php wp_nonce_field('mxchat_add_intent_nonce', 'add_intent_nonce'); ?>
3288 </div>
3289 <input type="hidden" name="action" id="form_action_type" value="mxchat_add_intent">
3290 <input type="hidden" name="intent_id" id="edit_action_id" value="">
3291 <input type="hidden" name="callback_function" id="callback_function" value="">
3292
3293 <!-- Step 1: Action Type Selection -->
3294 <div id="mxchat-action-step-1" class="mxchat-action-step active">
3295 <div class="mxchat-step-indicator">
3296 <div class="mxchat-step-number">1</div>
3297 <div class="mxchat-step-title"><?php esc_html_e('Select Action Type', 'mxchat'); ?></div>
3298 </div>
3299
3300 <div id="mxchat-action-type-selector" class="mxchat-action-type-selector">
3301 <div class="mxchat-action-type-search">
3302 <span class="dashicons dashicons-search"></span>
3303 <input type="text" id="action-type-search" placeholder="<?php esc_attr_e('Search action types...', 'mxchat'); ?>" class="mxchat-action-type-search-input">
3304 </div>
3305
3306 <?php
3307 // Get the callbacks - IMPORTANT: Define the $groups variable here
3308 $groups = $this->mxchat_get_available_callbacks(true, true);
3309 ?>
3310
3311 <div class="mxchat-action-type-categories">
3312 <button type="button" class="mxchat-category-button active" data-category="all"><?php esc_html_e('All', 'mxchat'); ?></button>
3313 <?php
3314 // Get unique categories from the defined groups
3315 foreach ($groups as $group_label => $group_callbacks) :
3316 $category_slug = sanitize_title($group_label);
3317 ?>
3318 <button type="button" class="mxchat-category-button" data-category="<?php echo esc_attr($category_slug); ?>"><?php echo esc_html($group_label); ?></button>
3319 <?php endforeach; ?>
3320 </div>
3321
3322 <div class="mxchat-action-types-grid">
3323 <?php
3324 // Generate action cards from available callbacks
3325 foreach ($groups as $group_label => $group_callbacks) :
3326 $category_slug = sanitize_title($group_label);
3327
3328 foreach ($group_callbacks as $function => $data) :
3329 $label = $data['label'];
3330 $icon = isset($data['icon']) ? $data['icon'] : 'admin-generic';
3331 $description = isset($data['description']) ? $data['description'] : '';
3332 $is_addon = isset($data['addon']) && $data['addon'] !== false;
3333 $addon_name = isset($data['addon_name']) ? $data['addon_name'] : '';
3334 $is_installed = isset($data['installed']) ? $data['installed'] : true;
3335 $is_promo = !empty($data['addon_promo']) && !$is_installed;
3336
3337 // Determine card status and styling
3338 $card_class = 'mxchat-action-type-card';
3339 $icon_class = 'mxchat-action-type-icon';
3340 $status_badge = '';
3341
3342 if ($is_promo) {
3343 // Promotional card — not selectable, just informational
3344 $card_class .= ' not-installed mxchat-promo-card';
3345 $status_badge = '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
3346 } elseif ($is_addon && !$is_installed) {
3347 // Add-on not installed
3348 $card_class .= ' not-installed';
3349 $status_badge .= '<span class="mxchat-addon-badge">' . esc_html__('Add-on Required', 'mxchat') . '</span>';
3350 }
3351
3352 // Default description if none provided
3353 if (empty($description)) {
3354 $description = sprintf(
3355 esc_html__('Use the %s action in your chatbot', 'mxchat'),
3356 $label
3357 );
3358 }
3359 ?>
3360 <div class="<?php echo esc_attr($card_class); ?>"
3361 data-category="<?php echo esc_attr($category_slug); ?>"
3362 <?php if (!$is_promo) : ?>
3363 data-value="<?php echo esc_attr($function); ?>"
3364 data-label="<?php echo esc_attr($label); ?>"
3365 <?php endif; ?>
3366 data-pro="false"
3367 data-addon="<?php echo esc_attr($is_addon ? $data['addon'] : ''); ?>"
3368 data-installed="<?php echo $is_installed ? 'true' : 'false'; ?>"
3369 <?php if ($is_promo) : ?>data-promo="true"<?php endif; ?>>
3370 <div class="<?php echo esc_attr($icon_class); ?>">
3371 <span class="dashicons dashicons-<?php echo esc_attr($icon); ?>"></span>
3372 </div>
3373 <div class="mxchat-action-type-info">
3374 <h4><?php echo esc_html($label); ?></h4>
3375 <p><?php echo esc_html($description); ?></p>
3376 <?php if (!empty($status_badge)) : ?>
3377 <?php echo $status_badge; ?>
3378 <?php endif; ?>
3379
3380 <?php if ($is_promo || ($is_addon && !$is_installed)) : ?>
3381 <div class="mxchat-addon-info">
3382 <?php echo esc_html(sprintf(
3383 __('Requires %s', 'mxchat'),
3384 $addon_name
3385 )); ?>
3386 <a href="https://mxchat.ai/" target="_blank"><?php esc_html_e('Get Add-on', 'mxchat'); ?></a>
3387 </div>
3388 <?php endif; ?>
3389 </div>
3390 </div>
3391 <?php endforeach;
3392 endforeach; ?>
3393 </div>
3394 </div>
3395
3396 <div class="mxchat-modal-actions">
3397 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
3398 <?php esc_html_e('Cancel', 'mxchat'); ?>
3399 </button>
3400 </div>
3401 </div>
3402
3403 <!-- Step 2: Action Configuration -->
3404 <div id="mxchat-action-step-2" class="mxchat-action-step">
3405 <div class="mxchat-step-indicator">
3406 <div class="mxchat-step-number">2</div>
3407 <div class="mxchat-step-title"><?php esc_html_e('Configure Action', 'mxchat'); ?></div>
3408 </div>
3409
3410 <div class="mxchat-selected-action">
3411 <button type="button" class="mxchat-back-button" id="mxchat-back-to-step-1">
3412 <span class="dashicons dashicons-arrow-left-alt"></span>
3413 <?php esc_html_e('Back to Action Types', 'mxchat'); ?>
3414 </button>
3415 <div class="mxchat-selected-action-info">
3416 <div id="selected-action-icon" class="mxchat-action-type-icon">
3417 <span class="dashicons dashicons-admin-generic"></span>
3418 </div>
3419 <div class="mxchat-selected-action-details">
3420 <h3 id="selected-action-title"><?php esc_html_e('Selected Action', 'mxchat'); ?></h3>
3421 <p id="selected-action-description"><?php esc_html_e('Configure this action for your chatbot', 'mxchat'); ?></p>
3422 </div>
3423 </div>
3424 </div>
3425
3426 <div class="mxchat-form-group">
3427 <label for="intent_label">
3428 <?php esc_html_e('Action Label (For your reference only)', 'mxchat'); ?>
3429 </label>
3430 <input name="intent_label" type="text" id="intent_label" required
3431 class="mxchat-intent-input"
3432 placeholder="<?php esc_attr_e('Example: Newsletter Signup', 'mxchat'); ?>">
3433 </div>
3434
3435 <div class="mxchat-form-group">
3436 <label for="phrases">
3437 <?php esc_html_e('Trigger Phrases (comma-separated)', 'mxchat'); ?>
3438 </label>
3439 <textarea name="phrases" id="action_phrases" rows="5" required
3440 class="mxchat-intent-textarea"
3441 placeholder="<?php esc_attr_e('Example: sign me up, subscribe me, I want to join, add me to the newsletter', 'mxchat'); ?>"></textarea>
3442 </div>
3443
3444 <div class="mxchat-form-group">
3445 <label for="similarity_threshold">
3446 <?php esc_html_e('Similarity Threshold', 'mxchat'); ?>
3447 <span class="mxchat-threshold-value-display">85%</span>
3448 </label>
3449 <div class="mxchat-slider-group modal-slider">
3450 <input type="range"
3451 name="similarity_threshold"
3452 id="similarity_threshold"
3453 min="10"
3454 max="95"
3455 value="85"
3456 class="mxchat-intent-slider"
3457 oninput="document.querySelector('.mxchat-threshold-value-display').textContent = this.value + '%'">
3458 </div>
3459 <div class="mxchat-threshold-hint">
3460 <?php esc_html_e('Lower values (10-30) make the action trigger more easily. Higher values (70-95) require more exact matches.', 'mxchat'); ?>
3461 </div>
3462 </div>
3463
3464 <!-- Bot Selection Section -->
3465 <div class="mxchat-form-group">
3466 <label for="enabled_bots">
3467 <?php esc_html_e('Which bots should we enable this action for?', 'mxchat'); ?>
3468 </label>
3469 <div class="mxchat-bot-selector">
3470 <div class="mxchat-bot-option">
3471 <label class="mxchat-checkbox-label">
3472 <input type="checkbox"
3473 name="enabled_bots[]"
3474 value="default"
3475 id="bot_default"
3476 checked="checked">
3477 <span class="mxchat-checkmark"></span>
3478 <?php esc_html_e('Default Bot', 'mxchat'); ?>
3479 </label>
3480 </div>
3481
3482 <?php if (class_exists('MxChat_Multi_Bot_Core_Manager')) :
3483 $multi_bot_manager = MxChat_Multi_Bot_Core_Manager::get_instance();
3484 $available_bots = $multi_bot_manager->get_available_bots();
3485
3486 foreach ($available_bots as $bot_id => $bot_name) :
3487 if ($bot_id === 'default') continue; // Skip default, already shown above
3488 ?>
3489 <div class="mxchat-bot-option">
3490 <label class="mxchat-checkbox-label">
3491 <input type="checkbox"
3492 name="enabled_bots[]"
3493 value="<?php echo esc_attr($bot_id); ?>"
3494 id="bot_<?php echo esc_attr($bot_id); ?>">
3495 <span class="mxchat-checkmark"></span>
3496 <?php echo esc_html($bot_name); ?>
3497 </label>
3498 </div>
3499 <?php
3500 endforeach;
3501 endif; ?>
3502 </div>
3503 </div>
3504
3505 <div class="mxchat-modal-actions">
3506 <button type="button" class="mxchat-button-secondary mxchat-modal-cancel">
3507 <?php esc_html_e('Cancel', 'mxchat'); ?>
3508 </button>
3509 <button type="submit" class="mxchat-button-primary" id="mxchat-save-action-btn">
3510 <?php esc_html_e('Save Action', 'mxchat'); ?>
3511 </button>
3512 </div>
3513 </div>
3514 </form>
3515 </div>
3516 </div>
3517
3518 <div id="mxchat-action-loading" class="mxchat-action-loading" style="display: none;">
3519 <div class="mxchat-action-loading-spinner"></div>
3520 <div class="mxchat-action-loading-text">
3521 <?php esc_html_e('Saving action, please wait...', 'mxchat'); ?>
3522 </div>
3523 </div>
3524 </div><!-- .mxchat-wrapper -->
3525 <?php
3526 }
3527 private function get_trimmed_phrases($phrases, $max_length = 100) {
3528 if (strlen($phrases) <= $max_length) {
3529 return $phrases;
3530 }
3531
3532 $trimmed = substr($phrases, 0, $max_length);
3533 $last_comma = strrpos($trimmed, ',');
3534
3535 if ($last_comma !== false) {
3536 $trimmed = substr($trimmed, 0, $last_comma);
3537 }
3538
3539 return $trimmed . '...';
3540 }
3541 public function mxchat_add_enabled_column_to_intents() {
3542 global $wpdb;
3543 $table_name = $wpdb->prefix . 'mxchat_intents';
3544
3545 // Check if the column already exists
3546 $columns = $wpdb->get_results("SHOW COLUMNS FROM $table_name LIKE 'enabled'");
3547
3548 if (empty($columns)) {
3549 // Add the column with default value of 1 (enabled)
3550 $wpdb->query("ALTER TABLE $table_name ADD COLUMN enabled TINYINT(1) NOT NULL DEFAULT 1");
3551 }
3552 }
3553
3554 public function mxchat_handle_delete_intent() {
3555 if ( ! current_user_can( 'manage_options' ) ) {
3556 wp_die( esc_html__('Unauthorized user', 'mxchat') );
3557 }
3558
3559 check_admin_referer('mxchat_delete_intent_nonce');
3560
3561 if (isset($_POST['intent_id'])) {
3562 global $wpdb;
3563 $table_name = $wpdb->prefix . 'mxchat_intents';
3564 $intent_id = intval($_POST['intent_id']);
3565
3566 $wpdb->delete($table_name, ['id' => $intent_id], ['%d']);
3567 }
3568
3569 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3570 exit;
3571 }
3572 public function mxchat_handle_edit_intent() {
3573 // Security checks (nonce and permissions)
3574 if (!current_user_can('manage_options')) {
3575 wp_die(esc_html__('Unauthorized user', 'mxchat'));
3576 }
3577 check_admin_referer('mxchat_edit_intent');
3578
3579 // Get POST data
3580 $intent_id = isset($_POST['intent_id']) ? absint($_POST['intent_id']) : 0;
3581 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3582 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3583 $threshold_percentage = isset($_POST['similarity_threshold']) ? intval($_POST['similarity_threshold']) : 85;
3584 $similarity_threshold = min(95, max(10, $threshold_percentage)) / 100; // Convert to 0.10–0.95
3585
3586 // Handle enabled_bots
3587 $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
3588 $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
3589
3590 // Ensure default is always included for backward compatibility
3591 if (!in_array('default', $enabled_bots)) {
3592 $enabled_bots[] = 'default';
3593 }
3594
3595 $enabled_bots_json = json_encode($enabled_bots);
3596
3597 // Validate inputs
3598 if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
3599 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
3600 return;
3601 }
3602
3603 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3604 if (empty($phrases_array)) {
3605 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
3606 return;
3607 }
3608
3609 // Generate embeddings with improved error handling
3610 $vectors = [];
3611 $failed_phrases = [];
3612
3613 foreach ($phrases_array as $phrase) {
3614 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3615 if (is_array($embedding_vector)) {
3616 $vectors[] = $embedding_vector;
3617 } else {
3618 $failed_phrases[] = $phrase;
3619 }
3620 }
3621
3622 if (!empty($failed_phrases)) {
3623 $this->handle_embedding_error(
3624 sprintf(
3625 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
3626 implode(', ', $failed_phrases)
3627 )
3628 );
3629 return;
3630 }
3631
3632 if (empty($vectors)) {
3633 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
3634 return;
3635 }
3636
3637 $combined_vector = $this->mxchat_average_vectors($vectors);
3638 $serialized_vector = maybe_serialize($combined_vector);
3639
3640 // Update the database
3641 global $wpdb;
3642 $table_name = $wpdb->prefix . 'mxchat_intents';
3643
3644 $result = $wpdb->update(
3645 $table_name,
3646 array(
3647 'intent_label' => $intent_label,
3648 'phrases' => implode(', ', $phrases_array),
3649 'embedding_vector' => $serialized_vector,
3650 'similarity_threshold' => $similarity_threshold,
3651 'enabled_bots' => $enabled_bots_json // Include enabled_bots in update
3652 ),
3653 array('id' => $intent_id),
3654 array('%s', '%s', '%s', '%f', '%s'), // Format: string, string, string, float, string
3655 array('%d') // Where format: integer
3656 );
3657
3658 if (false === $result) {
3659 $this->handle_embedding_error(__('Failed to update action in database.', 'mxchat'));
3660 return;
3661 }
3662
3663 // Set success message and redirect
3664 set_transient('mxchat_admin_notice_success', __('Intent updated successfully!', 'mxchat'), 60);
3665
3666 $redirect_url = add_query_arg(
3667 array(
3668 'page' => 'mxchat-actions'
3669 ),
3670 admin_url('admin.php')
3671 );
3672 wp_safe_redirect($redirect_url);
3673 exit;
3674 }
3675 public function mxchat_handle_add_intent() {
3676 if (!current_user_can('manage_options')) {
3677 wp_die(esc_html__('Unauthorized user', 'mxchat'));
3678 }
3679 check_admin_referer('mxchat_add_intent_nonce');
3680 global $wpdb;
3681 $table_name = $wpdb->prefix . 'mxchat_intents';
3682
3683 // Sanitize and get form data
3684 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3685 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3686 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3687
3688 // Get similarity threshold from form (convert percentage to decimal)
3689 $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
3690
3691 // Handle enabled_bots
3692 $enabled_bots = isset($_POST['enabled_bots']) ? $_POST['enabled_bots'] : array('default');
3693 $enabled_bots = array_map('sanitize_text_field', $enabled_bots);
3694
3695 // Ensure default is always included for backward compatibility with existing actions
3696 if (!in_array('default', $enabled_bots)) {
3697 $enabled_bots[] = 'default';
3698 }
3699
3700 $enabled_bots_json = json_encode($enabled_bots);
3701
3702 // Validate required fields
3703 if (empty($intent_label) || empty($callback_function) || empty($phrases_input)) {
3704 $this->handle_embedding_error(__('Invalid input. Please ensure all fields are filled out.', 'mxchat'));
3705 return;
3706 }
3707
3708 // Validate callback function
3709 $available_callbacks = $this->mxchat_get_available_callbacks();
3710 if (!array_key_exists($callback_function, $available_callbacks)) {
3711 $this->handle_embedding_error(__('Invalid callback function selected.', 'mxchat'));
3712 return;
3713 }
3714
3715 // Check if this is an add-on promotional placeholder (not a real action)
3716 if (!empty($available_callbacks[$callback_function]['addon_promo'])) {
3717 $addon_name = isset($available_callbacks[$callback_function]['addon_name']) ? $available_callbacks[$callback_function]['addon_name'] : __('an add-on', 'mxchat');
3718 $this->handle_embedding_error(sprintf(
3719 __('This action requires the %s to be installed and activated.', 'mxchat'),
3720 $addon_name
3721 ));
3722 return;
3723 }
3724
3725 // Process phrases
3726 $phrases_array = array_map('sanitize_text_field', array_filter(array_map('trim', explode(',', $phrases_input))));
3727 if (empty($phrases_array)) {
3728 $this->handle_embedding_error(__('Please enter at least one valid phrase.', 'mxchat'));
3729 return;
3730 }
3731
3732 // Generate embeddings with improved error handling
3733 $vectors = [];
3734 $failed_phrases = [];
3735 foreach ($phrases_array as $phrase) {
3736 $embedding_vector = $this->mxchat_generate_embedding($phrase, $this->options['api_key']);
3737 if (is_array($embedding_vector)) {
3738 $vectors[] = $embedding_vector;
3739 } else {
3740 $failed_phrases[] = $phrase;
3741 }
3742 }
3743
3744 // Check for embedding failures
3745 if (!empty($failed_phrases)) {
3746 $this->handle_embedding_error(
3747 sprintf(
3748 __('Error generating embeddings for phrases: %s. Check your embedding API.', 'mxchat'),
3749 implode(', ', $failed_phrases)
3750 )
3751 );
3752 return;
3753 }
3754
3755 if (empty($vectors)) {
3756 $this->handle_embedding_error(__('No valid embeddings generated. Please check your phrases.', 'mxchat'));
3757 return;
3758 }
3759
3760 // Create combined vector and insert into database
3761 $combined_vector = $this->mxchat_average_vectors($vectors);
3762 $serialized_vector = maybe_serialize($combined_vector);
3763
3764 $result = $wpdb->insert($table_name, [
3765 'intent_label' => $intent_label,
3766 'phrases' => implode(', ', $phrases_array),
3767 'embedding_vector' => $serialized_vector,
3768 'callback_function' => $callback_function,
3769 'similarity_threshold' => $similarity_threshold,
3770 'enabled_bots' => $enabled_bots_json // NEW field
3771 ]);
3772
3773 if ($result === false) {
3774 $this->handle_embedding_error(__('Database error: ', 'mxchat') . $wpdb->last_error);
3775 return;
3776 }
3777
3778 // Set success message and redirect
3779 set_transient('mxchat_admin_notice_success', __('New intent added successfully!', 'mxchat'), 60);
3780 wp_safe_redirect(admin_url('admin.php?page=mxchat-actions'));
3781 exit;
3782 }
3783
3784
3785
3786
3787 private function handle_embedding_error($message, $redirect = true) {
3788 // Store the error message in the existing transient
3789 set_transient('mxchat_admin_notice_error', $message, 60);
3790
3791 if ($redirect) {
3792 // Redirect back to the actions page
3793 $redirect_url = add_query_arg(
3794 array(
3795 'page' => 'mxchat-actions'
3796 ),
3797 admin_url('admin.php')
3798 );
3799 wp_safe_redirect($redirect_url);
3800 exit;
3801 }
3802 }
3803
3804 /**
3805 * AJAX handler to fetch actions list for the new split-panel UI
3806 */
3807 public function mxchat_fetch_actions_list() {
3808 check_ajax_referer('mxchat_actions_nonce', 'security');
3809
3810 if (!current_user_can('manage_options')) {
3811 wp_send_json_error(__('Unauthorized', 'mxchat'));
3812 }
3813
3814 global $wpdb;
3815 $table_name = $wpdb->prefix . 'mxchat_intents';
3816
3817 $page = isset($_POST['page']) ? max(1, intval($_POST['page'])) : 1;
3818 $per_page = isset($_POST['per_page']) ? min(100, max(1, intval($_POST['per_page']))) : 50;
3819 $offset = ($page - 1) * $per_page;
3820 $search = isset($_POST['search']) ? sanitize_text_field($_POST['search']) : '';
3821 $callback_filter = isset($_POST['callback_filter']) ? sanitize_text_field($_POST['callback_filter']) : '';
3822 $sort_order = isset($_POST['sort_order']) && $_POST['sort_order'] === 'asc' ? 'ASC' : 'DESC';
3823
3824 // Build WHERE clause
3825 $where = '1=1';
3826 $params = array();
3827
3828 if ($search) {
3829 $search_like = '%' . $wpdb->esc_like($search) . '%';
3830 $where .= ' AND (intent_label LIKE %s OR phrases LIKE %s)';
3831 $params[] = $search_like;
3832 $params[] = $search_like;
3833 }
3834
3835 if ($callback_filter) {
3836 $where .= ' AND callback_function = %s';
3837 $params[] = $callback_filter;
3838 }
3839
3840 // Get total count
3841 $count_query = "SELECT COUNT(*) FROM $table_name WHERE $where";
3842 if (!empty($params)) {
3843 $count_query = $wpdb->prepare($count_query, $params);
3844 }
3845 $total_actions = $wpdb->get_var($count_query);
3846
3847 // Get actions
3848 $query = "SELECT * FROM $table_name WHERE $where ORDER BY id $sort_order LIMIT %d OFFSET %d";
3849 $all_params = array_merge($params, array($per_page, $offset));
3850 $actions = $wpdb->get_results($wpdb->prepare($query, $all_params));
3851
3852 // Get available callbacks for labels/icons
3853 $available_callbacks = $this->mxchat_get_available_callbacks();
3854
3855 // Format actions for response
3856 $formatted_actions = array();
3857 foreach ($actions as $action) {
3858 $callback_data = isset($available_callbacks[$action->callback_function])
3859 ? $available_callbacks[$action->callback_function]
3860 : array('label' => $action->callback_function, 'icon' => 'admin-generic');
3861
3862 $enabled_bots = json_decode($action->enabled_bots, true);
3863 if (!is_array($enabled_bots)) {
3864 $enabled_bots = array('default');
3865 }
3866
3867 $formatted_actions[] = array(
3868 'id' => intval($action->id),
3869 'label' => $action->intent_label,
3870 'phrases' => $action->phrases,
3871 'callback_function' => $action->callback_function,
3872 'callback_label' => $callback_data['label'],
3873 'icon' => isset($callback_data['icon']) ? $callback_data['icon'] : 'admin-generic',
3874 'threshold' => round($action->similarity_threshold * 100),
3875 'enabled' => (bool) $action->enabled,
3876 'enabled_bots' => $enabled_bots,
3877 );
3878 }
3879
3880 $total_pages = ceil($total_actions / $per_page);
3881 $showing_start = $total_actions > 0 ? $offset + 1 : 0;
3882 $showing_end = min($offset + $per_page, $total_actions);
3883
3884 wp_send_json_success(array(
3885 'actions' => $formatted_actions,
3886 'page' => $page,
3887 'per_page' => $per_page,
3888 'total_actions' => intval($total_actions),
3889 'total_pages' => $total_pages,
3890 'showing_start' => $showing_start,
3891 'showing_end' => $showing_end,
3892 ));
3893 }
3894
3895 /**
3896 * AJAX handler to toggle action enabled status
3897 */
3898 public function mxchat_toggle_action_status() {
3899 check_ajax_referer('mxchat_actions_nonce', 'security');
3900
3901 if (!current_user_can('manage_options')) {
3902 wp_send_json_error(__('Unauthorized', 'mxchat'));
3903 }
3904
3905 $action_id = isset($_POST['action_id']) ? intval($_POST['action_id']) : 0;
3906 $enabled = isset($_POST['enabled']) ? intval($_POST['enabled']) : 0;
3907
3908 if (!$action_id) {
3909 wp_send_json_error(__('Invalid action ID', 'mxchat'));
3910 }
3911
3912 global $wpdb;
3913 $table_name = $wpdb->prefix . 'mxchat_intents';
3914
3915 $result = $wpdb->update(
3916 $table_name,
3917 array('enabled' => $enabled ? 1 : 0),
3918 array('id' => $action_id),
3919 array('%d'),
3920 array('%d')
3921 );
3922
3923 if ($result === false) {
3924 wp_send_json_error(__('Failed to update action status', 'mxchat'));
3925 }
3926
3927 wp_send_json_success(array('enabled' => (bool) $enabled));
3928 }
3929
3930 /**
3931 * AJAX handler to bulk delete actions
3932 */
3933 public function mxchat_bulk_delete_actions() {
3934 check_ajax_referer('mxchat_delete_intent_nonce', 'security');
3935
3936 if (!current_user_can('manage_options')) {
3937 wp_send_json_error(__('Unauthorized', 'mxchat'));
3938 }
3939
3940 $action_ids = isset($_POST['action_ids']) ? array_map('intval', (array) $_POST['action_ids']) : array();
3941
3942 if (empty($action_ids)) {
3943 wp_send_json_error(__('No actions selected', 'mxchat'));
3944 }
3945
3946 global $wpdb;
3947 $table_name = $wpdb->prefix . 'mxchat_intents';
3948
3949 $placeholders = implode(',', array_fill(0, count($action_ids), '%d'));
3950 $query = $wpdb->prepare("DELETE FROM $table_name WHERE id IN ($placeholders)", $action_ids);
3951 $result = $wpdb->query($query);
3952
3953 if ($result === false) {
3954 wp_send_json_error(__('Failed to delete actions', 'mxchat'));
3955 }
3956
3957 wp_send_json_success(array('deleted' => $result));
3958 }
3959
3960 /**
3961 * AJAX handler to add a new intent/action
3962 */
3963 public function mxchat_add_intent_ajax() {
3964 check_ajax_referer('mxchat_add_intent_nonce', 'security');
3965
3966 if (!current_user_can('manage_options')) {
3967 wp_send_json_error(__('Unauthorized', 'mxchat'));
3968 }
3969
3970 global $wpdb;
3971 $table_name = $wpdb->prefix . 'mxchat_intents';
3972
3973 // Sanitize input
3974 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
3975 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
3976 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
3977 $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
3978 $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
3979
3980 // Validate
3981 if (empty($intent_label) || empty($phrases_input) || empty($callback_function)) {
3982 wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
3983 }
3984
3985 // Process phrases
3986 $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
3987 if (empty($phrases_array)) {
3988 wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
3989 }
3990
3991 // Generate embedding (combine phrases into single string for embedding)
3992 $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
3993 if (is_wp_error($embedding_vector)) {
3994 // Fallback: store without embedding
3995 $serialized_vector = null;
3996 } else {
3997 $serialized_vector = maybe_serialize($embedding_vector);
3998 }
3999
4000 // Ensure default bot is included
4001 if (!in_array('default', $enabled_bots)) {
4002 $enabled_bots[] = 'default';
4003 }
4004 $enabled_bots_json = json_encode($enabled_bots);
4005
4006 // Insert
4007 $result = $wpdb->insert(
4008 $table_name,
4009 array(
4010 'intent_label' => $intent_label,
4011 'phrases' => implode(', ', $phrases_array),
4012 'embedding_vector' => $serialized_vector,
4013 'similarity_threshold' => $similarity_threshold,
4014 'callback_function' => $callback_function,
4015 'enabled' => 1,
4016 'enabled_bots' => $enabled_bots_json,
4017 )
4018 );
4019
4020 if ($result === false) {
4021 wp_send_json_error(__('Failed to add action to database.', 'mxchat'));
4022 }
4023
4024 wp_send_json_success(array('id' => $wpdb->insert_id));
4025 }
4026
4027 /**
4028 * AJAX handler to edit an existing intent/action
4029 */
4030 public function mxchat_edit_intent_ajax() {
4031 check_ajax_referer('mxchat_edit_intent', 'security');
4032
4033 if (!current_user_can('manage_options')) {
4034 wp_send_json_error(__('Unauthorized', 'mxchat'));
4035 }
4036
4037 global $wpdb;
4038 $table_name = $wpdb->prefix . 'mxchat_intents';
4039
4040 // Sanitize input
4041 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
4042 $intent_label = isset($_POST['intent_label']) ? sanitize_text_field($_POST['intent_label']) : '';
4043 $phrases_input = isset($_POST['phrases']) ? sanitize_textarea_field($_POST['phrases']) : '';
4044 $callback_function = isset($_POST['callback_function']) ? sanitize_text_field($_POST['callback_function']) : '';
4045 $similarity_threshold = isset($_POST['similarity_threshold']) ? floatval($_POST['similarity_threshold']) / 100 : 0.85;
4046 $enabled_bots = isset($_POST['enabled_bots']) ? array_map('sanitize_text_field', (array) $_POST['enabled_bots']) : array('default');
4047
4048 // Validate
4049 if (!$intent_id || empty($intent_label) || empty($phrases_input)) {
4050 wp_send_json_error(__('Please fill in all required fields.', 'mxchat'));
4051 }
4052
4053 // Process phrases
4054 $phrases_array = array_filter(array_map('trim', preg_split('/[\n,]+/', $phrases_input)));
4055 if (empty($phrases_array)) {
4056 wp_send_json_error(__('Please provide at least one trigger phrase.', 'mxchat'));
4057 }
4058
4059 // Generate new embedding (combine phrases into single string for embedding)
4060 $embedding_vector = $this->mxchat_generate_embedding(implode(' ', $phrases_array));
4061 if (is_wp_error($embedding_vector)) {
4062 // Keep existing embedding
4063 $serialized_vector = null;
4064 $update_data = array(
4065 'intent_label' => $intent_label,
4066 'phrases' => implode(', ', $phrases_array),
4067 'similarity_threshold' => $similarity_threshold,
4068 'callback_function' => $callback_function,
4069 'enabled_bots' => json_encode($enabled_bots),
4070 );
4071 } else {
4072 $serialized_vector = maybe_serialize($embedding_vector);
4073 $update_data = array(
4074 'intent_label' => $intent_label,
4075 'phrases' => implode(', ', $phrases_array),
4076 'embedding_vector' => $serialized_vector,
4077 'similarity_threshold' => $similarity_threshold,
4078 'callback_function' => $callback_function,
4079 'enabled_bots' => json_encode($enabled_bots),
4080 );
4081 }
4082
4083 // Ensure default bot is included
4084 if (!in_array('default', $enabled_bots)) {
4085 $enabled_bots[] = 'default';
4086 }
4087 $update_data['enabled_bots'] = json_encode($enabled_bots);
4088
4089 $result = $wpdb->update(
4090 $table_name,
4091 $update_data,
4092 array('id' => $intent_id),
4093 null,
4094 array('%d')
4095 );
4096
4097 if ($result === false) {
4098 wp_send_json_error(__('Failed to update action.', 'mxchat'));
4099 }
4100
4101 wp_send_json_success(array('updated' => true));
4102 }
4103
4104 /**
4105 * AJAX handler to delete a single intent/action
4106 */
4107 public function mxchat_delete_intent_ajax() {
4108 check_ajax_referer('mxchat_delete_intent_nonce', 'security');
4109
4110 if (!current_user_can('manage_options')) {
4111 wp_send_json_error(__('Unauthorized', 'mxchat'));
4112 }
4113
4114 $intent_id = isset($_POST['intent_id']) ? intval($_POST['intent_id']) : 0;
4115
4116 if (!$intent_id) {
4117 wp_send_json_error(__('Invalid action ID', 'mxchat'));
4118 }
4119
4120 global $wpdb;
4121 $table_name = $wpdb->prefix . 'mxchat_intents';
4122
4123 $result = $wpdb->delete($table_name, array('id' => $intent_id), array('%d'));
4124
4125 if ($result === false) {
4126 wp_send_json_error(__('Failed to delete action', 'mxchat'));
4127 }
4128
4129 wp_send_json_success(array('deleted' => true));
4130 }
4131
4132
4133 /**
4134 * Enhanced get_available_callbacks function with form action exclusion
4135 *
4136 * @param bool $grouped Whether to return callbacks grouped by category
4137 * @param bool $include_all Whether to include all potential actions (even if add-on not installed)
4138 * @return array Callbacks data with icons, descriptions and availability status
4139 */
4140 private function mxchat_get_available_callbacks($grouped = false, $include_all = true) {
4141 // Load WordPress plugin functions if needed
4142 if (!function_exists('get_plugins')) {
4143 require_once ABSPATH . 'wp-admin/includes/plugin.php';
4144 }
4145
4146 // Get active plugins
4147 $active_plugins = get_option('active_plugins', array());
4148
4149 // Functions to exclude from the action selector only if Pro is activated
4150 // If user doesn't have Pro, show these so they can see what they're missing
4151 $excluded_when_pro_active_functions = array(
4152 'mxchat_handle_form_collection' // Forms add-on action
4153 );
4154
4155 // Always excluded functions (regardless of Pro status)
4156 $always_excluded_functions = array();
4157
4158 // Combine exclusion lists based on Pro activation status
4159 $excluded_functions = $always_excluded_functions;
4160 if ($this->is_activated) {
4161 // Only exclude add-on managed functions if Pro is active
4162 $excluded_functions = array_merge($excluded_functions, $excluded_when_pro_active_functions);
4163 }
4164
4165 // Define add-on plugin files and their corresponding action functions
4166 $addon_plugins = array(
4167 'mxchat-woo/mxchat-woo.php' => array(
4168 'functions' => array(
4169 'mxchat_handle_product_recommendations',
4170 'mxchat_handle_order_history',
4171 'mxchat_show_product_card',
4172 'mxchat_add_to_cart',
4173 'mxchat_checkout_redirect'
4174 ),
4175 'name' => __('WooCommerce Add-on', 'mxchat'),
4176 'pro_required' => true
4177 ),
4178 'mxchat-perplexity/mxchat-perplexity.php' => array(
4179 'functions' => array('mxchat_perplexity_research'),
4180 'name' => __('Perplexity Add-on', 'mxchat'),
4181 'pro_required' => true
4182 ),
4183 'mxchat-forms/mxchat-forms.php' => array(
4184 'functions' => array('mxchat_handle_form_collection'),
4185 'name' => __('Forms Add-on', 'mxchat'),
4186 'pro_required' => true
4187 ),
4188 // Add other add-ons and their functions here
4189 );
4190
4191 // Get the functions that are provided by active add-ons
4192 $addon_provided_functions = array();
4193 $addon_function_mapping = array(); // Maps functions to their add-on info
4194
4195 // Check which add-ons are active
4196 foreach ($addon_plugins as $plugin_file => $addon_info) {
4197 $is_active = in_array($plugin_file, $active_plugins);
4198
4199 // For each function in this addon
4200 foreach ($addon_info['functions'] as $function) {
4201 // Consider a function installed only if:
4202 // 1. The add-on is active AND
4203 // 2. Either it doesn't require Pro OR Pro is activated
4204 $is_installed = $is_active && (!$addon_info['pro_required'] || $this->is_activated);
4205
4206 // If the add-on is installed, mark this function as provided by an add-on
4207 if ($is_installed) {
4208 $addon_provided_functions[] = $function;
4209 }
4210
4211 // Store addon info for this function regardless of installation status
4212 $addon_function_mapping[$function] = array(
4213 'addon' => basename(dirname($plugin_file)),
4214 'addon_name' => $addon_info['name'],
4215 'pro_required' => $addon_info['pro_required'],
4216 'is_active' => $is_active,
4217 'is_installed' => $is_installed
4218 );
4219 }
4220 }
4221
4222 // Core callbacks - always available in the base plugin
4223 $core_callbacks = array(
4224 'mxchat_handle_email_capture' => array(
4225 'label' => __('Loops Email Capture', 'mxchat'),
4226 'pro_only' => false,
4227 'group' => __('Customer Engagement', 'mxchat'),
4228 'icon' => 'email-alt',
4229 'description' => __('Collect visitor emails for your mailing list in Loops', 'mxchat'),
4230 'addon' => false, // Not from an add-on
4231 'installed' => true // Always installed with base plugin
4232 ),
4233 'mxchat_handle_search_request' => array(
4234 'label' => __('Brave Web Search', 'mxchat'),
4235 'pro_only' => false,
4236 'group' => __('Search Features', 'mxchat'),
4237 'icon' => 'search',
4238 'description' => __('Let users search the web directly from the chat', 'mxchat'),
4239 'addon' => false,
4240 'installed' => true
4241 ),
4242 'mxchat_handle_image_search_request' => array(
4243 'label' => __('Brave Image Search', 'mxchat'),
4244 'pro_only' => false,
4245 'group' => __('Search Features', 'mxchat'),
4246 'icon' => 'format-image',
4247 'description' => __('Search and display images in the chat conversation', 'mxchat'),
4248 'addon' => false,
4249 'installed' => true
4250 ),
4251 // Pro core features - check is_activated property
4252 'mxchat_generate_image' => array(
4253 'label' => __('Generate Image (OpenAI)', 'mxchat'),
4254 'pro_only' => false,
4255 'group' => __('Other Features', 'mxchat'),
4256 'icon' => 'art',
4257 'description' => __('Create images with GPT Image from OpenAI (requires OpenAI API key)', 'mxchat'),
4258 'addon' => false,
4259 'installed' => true
4260 ),
4261 'mxchat_generate_gemini_image' => array(
4262 'label' => __('Generate Image (Gemini)', 'mxchat'),
4263 'pro_only' => false,
4264 'group' => __('Other Features', 'mxchat'),
4265 'icon' => 'art',
4266 'description' => __('Create images with Imagen from Google (requires Gemini API key)', 'mxchat'),
4267 'addon' => false,
4268 'installed' => true
4269 ),
4270 'mxchat_handle_pdf_discussion' => array(
4271 'label' => __('Chat with PDF', 'mxchat'),
4272 'pro_only' => false,
4273 'group' => __('Other Features', 'mxchat'),
4274 'icon' => 'media-document',
4275 'description' => __('Answer questions about uploaded PDF documents', 'mxchat'),
4276 'addon' => false,
4277 'installed' => true
4278 ),
4279 'mxchat_live_agent_handover' => array(
4280 'label' => __('Slack Live Agent', 'mxchat'),
4281 'pro_only' => false,
4282 'group' => __('Customer Engagement', 'mxchat'),
4283 'icon' => 'admin-users',
4284 'description' => __('Transfer conversation to a human support agent on Slack', 'mxchat'),
4285 'addon' => false,
4286 'installed' => true
4287 ),
4288 'mxchat_telegram_live_agent_handover' => array(
4289 'label' => __('Telegram Live Agent', 'mxchat'),
4290 'pro_only' => false,
4291 'group' => __('Customer Engagement', 'mxchat'),
4292 'icon' => 'format-chat',
4293 'description' => __('Transfer conversation to a human support agent on Telegram', 'mxchat'),
4294 'addon' => false,
4295 'installed' => true
4296 ),
4297 'mxchat_handle_switch_to_chatbot_intent' => array(
4298 'label' => __('Back to Chatbot', 'mxchat'),
4299 'pro_only' => false,
4300 'group' => __('Customer Engagement', 'mxchat'),
4301 'icon' => 'backup',
4302 'description' => __('Return from live agent mode to AI chatbot', 'mxchat'),
4303 'addon' => false,
4304 'installed' => true
4305 ),
4306 );
4307
4308 // Add-on callbacks with placeholders - only include if the add-on is NOT active
4309 // These are promotional/informational only — not selectable as real actions
4310 $addon_callbacks = array(
4311 // WooCommerce Add-on
4312 'mxchat_handle_product_recommendations' => array(
4313 'label' => __('Product Recommendations', 'mxchat'),
4314 'pro_only' => false,
4315 'addon_promo' => true,
4316 'group' => __('WooCommerce Features', 'mxchat'),
4317 'icon' => 'cart',
4318 'description' => __('Suggest products based on customer preferences', 'mxchat'),
4319 ),
4320 'mxchat_handle_order_history' => array(
4321 'label' => __('Order History', 'mxchat'),
4322 'pro_only' => false,
4323 'addon_promo' => true,
4324 'group' => __('WooCommerce Features', 'mxchat'),
4325 'icon' => 'clipboard',
4326 'description' => __('Allow customers to check their order status', 'mxchat'),
4327 ),
4328 'mxchat_show_product_card' => array(
4329 'label' => __('Show Product Card', 'mxchat'),
4330 'pro_only' => false,
4331 'addon_promo' => true,
4332 'group' => __('WooCommerce Features', 'mxchat'),
4333 'icon' => 'products',
4334 'description' => __('Display product information in the chat', 'mxchat'),
4335 ),
4336 'mxchat_add_to_cart' => array(
4337 'label' => __('Add to Cart', 'mxchat'),
4338 'pro_only' => false,
4339 'addon_promo' => true,
4340 'group' => __('WooCommerce Features', 'mxchat'),
4341 'icon' => 'plus-alt',
4342 'description' => __('Add products to cart directly from chat', 'mxchat'),
4343 ),
4344 'mxchat_checkout_redirect' => array(
4345 'label' => __('Proceed to Checkout', 'mxchat'),
4346 'pro_only' => false,
4347 'addon_promo' => true,
4348 'group' => __('WooCommerce Features', 'mxchat'),
4349 'icon' => 'arrow-right-alt',
4350 'description' => __('Redirect customer to checkout page', 'mxchat'),
4351 ),
4352
4353 // Perplexity Add-on
4354 'mxchat_perplexity_research' => array(
4355 'label' => __('Perplexity Research', 'mxchat'),
4356 'pro_only' => false,
4357 'addon_promo' => true,
4358 'group' => __('Search Features', 'mxchat'),
4359 'icon' => 'book-alt',
4360 'description' => __('Allows the chatbot to search the web for accurate, up-to-date answers', 'mxchat'),
4361 ),
4362
4363 // Forms Add-on
4364 'mxchat_handle_form_collection' => array(
4365 'label' => __('Form Collection', 'mxchat'),
4366 'pro_only' => false,
4367 'addon_promo' => true,
4368 'group' => __('Form Features', 'mxchat'),
4369 'icon' => 'feedback',
4370 'description' => __('Collect user information through custom forms in chat', 'mxchat'),
4371 ),
4372 );
4373
4374 // Enhance add-on callbacks with installation status and addon info
4375 foreach ($addon_callbacks as $function => $data) {
4376 if (isset($addon_function_mapping[$function])) {
4377 $addon_info = $addon_function_mapping[$function];
4378
4379 $addon_callbacks[$function]['addon'] = $addon_info['addon'];
4380 $addon_callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4381 $addon_callbacks[$function]['installed'] = $addon_info['is_installed'];
4382
4383 // Set pro_only based on add-on configuration
4384 $addon_callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4385 } else {
4386 $addon_callbacks[$function]['addon'] = 'unknown';
4387 $addon_callbacks[$function]['addon_name'] = __('Unknown Add-on', 'mxchat');
4388 $addon_callbacks[$function]['installed'] = false;
4389 }
4390 }
4391
4392 // Initialize callbacks with core features
4393 $callbacks = $core_callbacks;
4394
4395 // Get callbacks from active add-ons
4396 $active_addon_callbacks = apply_filters('mxchat_available_callbacks', array());
4397
4398 // Add placeholder callbacks only for add-ons that aren't active
4399 if ($include_all) {
4400 foreach ($addon_callbacks as $function => $data) {
4401 // Skip placeholders for functions provided by active add-ons
4402 if (in_array($function, $addon_provided_functions)) {
4403 continue;
4404 }
4405
4406 // Skip excluded functions
4407 if (in_array($function, $excluded_functions)) {
4408 continue;
4409 }
4410
4411 // Add the placeholder
4412 $callbacks[$function] = $data;
4413 }
4414 }
4415
4416 // Add callbacks from active add-ons (will override placeholders)
4417 foreach ($active_addon_callbacks as $function => $data) {
4418 // Skip excluded functions
4419 if (in_array($function, $excluded_functions)) {
4420 continue;
4421 }
4422
4423 // Always include callbacks from add-ons
4424 $callbacks[$function] = $data;
4425
4426 // Ensure they have the proper add-on info
4427 if (isset($addon_function_mapping[$function])) {
4428 $addon_info = $addon_function_mapping[$function];
4429 $callbacks[$function]['addon'] = $addon_info['addon'];
4430 $callbacks[$function]['addon_name'] = $addon_info['addon_name'];
4431 $callbacks[$function]['installed'] = $addon_info['is_installed'];
4432 $callbacks[$function]['pro_only'] = $addon_info['pro_required'];
4433 }
4434 }
4435
4436 // Just before returning callbacks, sort them to prioritize free features
4437 if (!$grouped) {
4438 // Create temporary arrays for sorting
4439 $free_callbacks = array();
4440 $pro_callbacks = array();
4441
4442 // Split callbacks into free and pro
4443 foreach ($callbacks as $key => $data) {
4444 if (isset($data['pro_only']) && $data['pro_only']) {
4445 $pro_callbacks[$key] = $data;
4446 } else {
4447 $free_callbacks[$key] = $data;
4448 }
4449 }
4450
4451 // Merge with free callbacks first
4452 $callbacks = array_merge($free_callbacks, $pro_callbacks);
4453 }
4454
4455 // Return grouped structure if requested
4456 if ($grouped) {
4457 $grouped_callbacks = array();
4458 foreach ($callbacks as $key => $data) {
4459 $group_label = isset($data['group']) ? $data['group'] : __('Other Features', 'mxchat');
4460
4461 // Ensure we carry forward all the new fields in grouped mode
4462 $callback_data = array(
4463 'label' => $data['label'],
4464 'pro_only' => isset($data['pro_only']) ? $data['pro_only'] : false,
4465 'icon' => isset($data['icon']) ? $data['icon'] : 'admin-generic',
4466 'description' => isset($data['description']) ? $data['description'] : __('Custom action for your chatbot', 'mxchat'),
4467 'addon' => isset($data['addon']) ? $data['addon'] : false,
4468 'addon_name' => isset($data['addon_name']) ? $data['addon_name'] : '',
4469 'installed' => isset($data['installed']) ? $data['installed'] : true
4470 );
4471
4472 $grouped_callbacks[$group_label][$key] = $callback_data;
4473 }
4474
4475 // Sort within each group to prioritize free features
4476 foreach ($grouped_callbacks as $group => $items) {
4477 $free_items = array();
4478 $pro_items = array();
4479
4480 foreach ($items as $key => $data) {
4481 if (isset($data['pro_only']) && $data['pro_only']) {
4482 $pro_items[$key] = $data;
4483 } else {
4484 $free_items[$key] = $data;
4485 }
4486 }
4487
4488 $grouped_callbacks[$group] = array_merge($free_items, $pro_items);
4489 }
4490
4491 return $grouped_callbacks;
4492 }
4493
4494 return $callbacks;
4495 }
4496
4497 public function mxchat_page_init() {
4498 register_setting(
4499 'mxchat_option_group',
4500 'mxchat_options',
4501 array($this, 'mxchat_sanitize')
4502 );
4503
4504 register_setting(
4505 'mxchat_option_group',
4506 'mxchat_similarity_threshold',
4507 array(
4508 'type' => 'number',
4509 'sanitize_callback' => function($value) {
4510 $value = absint($value);
4511 return min(max($value, 20), 95);
4512 },
4513 'default' => 80,
4514 )
4515 );
4516
4517 // Chatbot Settings Section
4518 add_settings_section(
4519 'mxchat_chatbot_section',
4520 esc_html__('Chatbot Settings', 'mxchat'),
4521 null,
4522 'mxchat-chatbot'
4523 );
4524
4525 // API Keys Settings Section
4526 add_settings_section(
4527 'mxchat_api_keys_section',
4528 esc_html__('API Keys', 'mxchat'),
4529 array($this, 'mxchat_api_keys_section_callback'),
4530 'mxchat-api-keys'
4531 );
4532
4533 // OpenAI API Key
4534 add_settings_field(
4535 'api_key',
4536 esc_html__('OpenAI API Key', 'mxchat'),
4537 array($this, 'api_key_callback'),
4538 'mxchat-api-keys',
4539 'mxchat_api_keys_section'
4540 );
4541
4542 // X.AI API Key
4543 add_settings_field(
4544 'xai_api_key',
4545 esc_html__('X.AI API Key', 'mxchat'),
4546 array($this, 'xai_api_key_callback'),
4547 'mxchat-api-keys',
4548 'mxchat_api_keys_section'
4549 );
4550
4551 // Claude API Key
4552 add_settings_field(
4553 'claude_api_key',
4554 esc_html__('Claude API Key', 'mxchat'),
4555 array($this, 'claude_api_key_callback'),
4556 'mxchat-api-keys',
4557 'mxchat_api_keys_section'
4558 );
4559
4560 // DeepSeek API Key
4561 add_settings_field(
4562 'deepseek_api_key',
4563 esc_html__('DeepSeek API Key', 'mxchat'),
4564 array($this, 'deepseek_api_key_callback'),
4565 'mxchat-api-keys',
4566 'mxchat_api_keys_section'
4567 );
4568
4569 // Google Gemini API Key
4570 add_settings_field(
4571 'gemini_api_key',
4572 esc_html__('Google Gemini API Key', 'mxchat'),
4573 array($this, 'gemini_api_key_callback'),
4574 'mxchat-api-keys',
4575 'mxchat_api_keys_section'
4576 );
4577
4578 // Voyage AI API Key
4579 add_settings_field(
4580 'voyage_api_key',
4581 esc_html__('Voyage AI API Key', 'mxchat'),
4582 array($this, 'voyage_api_key_callback'),
4583 'mxchat-api-keys',
4584 'mxchat_api_keys_section'
4585 );
4586
4587 // OpenRouter API Key
4588 add_settings_field(
4589 'openrouter_api_key',
4590 esc_html__('OpenRouter API Key', 'mxchat'),
4591 array($this, 'openrouter_api_key_callback'),
4592 'mxchat-api-keys',
4593 'mxchat_api_keys_section'
4594 );
4595
4596 // Loops API Key
4597 add_settings_field(
4598 'loops_api_key',
4599 esc_html__('Loops API Key', 'mxchat'),
4600 array($this, 'mxchat_loops_api_key_callback'),
4601 'mxchat-api-keys',
4602 'mxchat_api_keys_section'
4603 );
4604
4605 // Brave Search API Key
4606 add_settings_field(
4607 'brave_api_key',
4608 __('Brave API Key', 'mxchat'),
4609 array($this, 'mxchat_brave_api_key_callback'),
4610 'mxchat-api-keys',
4611 'mxchat_api_keys_section'
4612 );
4613
4614 // Frontend Debugger Toggle
4615 add_settings_field(
4616 'show_frontend_debugger',
4617 esc_html__('Frontend Debugger', 'mxchat'),
4618 array($this, 'mxchat_frontend_debugger_callback'),
4619 'mxchat-chatbot',
4620 'mxchat_chatbot_section'
4621 );
4622
4623 // Similarity Threshold Slider
4624 add_settings_field(
4625 'similarity_threshold', // Field ID
4626 esc_html__('Similarity Threshold', 'mxchat'), // Field title
4627 array($this, 'mxchat_similarity_threshold_callback'), // Callback function
4628 'mxchat-chatbot', // Page
4629 'mxchat_chatbot_section' // Section
4630 );
4631
4632 // RAG Sources Limit Slider
4633 add_settings_field(
4634 'rag_sources_limit', // Field ID
4635 esc_html__('RAG Sources Limit', 'mxchat'), // Field title
4636 array($this, 'mxchat_rag_sources_limit_callback'), // Callback function
4637 'mxchat-chatbot', // Page
4638 'mxchat_chatbot_section' // Section
4639 );
4640
4641 add_settings_field(
4642 'append_to_body',
4643 esc_html__('Auto-Display Chatbot', 'mxchat'),
4644 array($this, 'mxchat_append_to_body_callback'),
4645 'mxchat-chatbot',
4646 'mxchat_chatbot_section'
4647 );
4648
4649 add_settings_field(
4650 'contextual_awareness_toggle',
4651 esc_html__('Contextual Awareness', 'mxchat'),
4652 array($this, 'mxchat_contextual_awareness_callback'),
4653 'mxchat-chatbot',
4654 'mxchat_chatbot_section'
4655 );
4656
4657 add_settings_field(
4658 'citation_links_toggle',
4659 esc_html__('Citation Links', 'mxchat'),
4660 array($this, 'mxchat_citation_links_toggle_callback'),
4661 'mxchat-chatbot',
4662 'mxchat_chatbot_section'
4663 );
4664
4665 add_settings_field(
4666 'enable_streaming_toggle',
4667 esc_html__('Enable Streaming', 'mxchat'),
4668 array($this, 'enable_streaming_toggle_callback'),
4669 'mxchat-chatbot',
4670 'mxchat_chatbot_section', // Same section as your working toggle
4671 array(
4672 'class' => 'mxchat-setting-row streaming-setting',
4673 'style' => 'display: none;' // Hidden by default, shown when OpenAI/Claude selected
4674 )
4675 );
4676
4677
4678 add_settings_field(
4679 'model',
4680 esc_html__('Chat Model', 'mxchat'),
4681 array($this, 'mxchat_model_callback'),
4682 'mxchat-chatbot',
4683 'mxchat_chatbot_section'
4684 );
4685
4686 add_settings_field(
4687 'embedding_model',
4688 esc_html__('Embedding Model', 'mxchat'),
4689 array($this, 'embedding_model_callback'),
4690 'mxchat-chatbot',
4691 'mxchat_chatbot_section'
4692 );
4693
4694 add_settings_field(
4695 'system_prompt_instructions',
4696 esc_html__('AI Instructions (Behavior)', 'mxchat'),
4697 array($this, 'system_prompt_instructions_callback'),
4698 'mxchat-chatbot',
4699 'mxchat_chatbot_section'
4700 );
4701
4702
4703 add_settings_field(
4704 'top_bar_title',
4705 esc_html__('Top Bar Title', 'mxchat'),
4706 array($this, 'mxchat_top_bar_title_callback'),
4707 'mxchat-chatbot',
4708 'mxchat_chatbot_section'
4709 );
4710
4711 add_settings_field(
4712 'ai_agent_text',
4713 esc_html__('AI Agent Text', 'mxchat'),
4714 array($this, 'mxchat_ai_agent_text_callback'),
4715 'mxchat-chatbot',
4716 'mxchat_chatbot_section'
4717 );
4718
4719 add_settings_field(
4720 'enable_email_block',
4721 esc_html__('Require Email To Chat', 'mxchat'),
4722 array($this, 'enable_email_block_callback'),
4723 'mxchat-chatbot',
4724 'mxchat_chatbot_section'
4725 );
4726
4727 add_settings_field(
4728 'email_blocker_header_content',
4729 esc_html__('Require Email Chat Content', 'mxchat'),
4730 array($this, 'email_blocker_header_content_callback'),
4731 'mxchat-chatbot',
4732 'mxchat_chatbot_section'
4733 );
4734
4735 add_settings_field(
4736 'email_blocker_button_text',
4737 esc_html__('Require Email Chat Button Text', 'mxchat'),
4738 [$this, 'email_blocker_button_text_callback'],
4739 'mxchat-chatbot',
4740 'mxchat_chatbot_section'
4741 );
4742
4743 add_settings_field(
4744 'enable_name_field',
4745 esc_html__('Require Name Field', 'mxchat'),
4746 array($this, 'enable_name_field_callback'),
4747 'mxchat-chatbot',
4748 'mxchat_chatbot_section'
4749 );
4750
4751 add_settings_field(
4752 'name_field_placeholder',
4753 esc_html__('Name Field Placeholder', 'mxchat'),
4754 array($this, 'name_field_placeholder_callback'),
4755 'mxchat-chatbot',
4756 'mxchat_chatbot_section'
4757 );
4758
4759 add_settings_field(
4760 'intro_message',
4761 esc_html__('Introductory Message', 'mxchat'),
4762 array($this, 'mxchat_intro_message_callback'),
4763 'mxchat-chatbot',
4764 'mxchat_chatbot_section'
4765 );
4766
4767 add_settings_field(
4768 'input_copy',
4769 esc_html__('Input Copy', 'mxchat'),
4770 array($this, 'mxchat_input_copy_callback'),
4771 'mxchat-chatbot',
4772 'mxchat_chatbot_section'
4773 );
4774
4775 add_settings_field(
4776 'pre_chat_message',
4777 esc_html__('Chat Teaser Pop-up', 'mxchat'),
4778 array($this, 'mxchat_pre_chat_message_callback'),
4779 'mxchat-chatbot',
4780 'mxchat_chatbot_section'
4781 );
4782
4783 add_settings_field(
4784 'privacy_toggle',
4785 esc_html__('Toggle Privacy Notice', 'mxchat'),
4786 array($this, 'mxchat_privacy_toggle_callback'),
4787 'mxchat-chatbot',
4788 'mxchat_chatbot_section'
4789 );
4790
4791 add_settings_field(
4792 'complianz_toggle',
4793 esc_html__('Enable Complianz', 'mxchat'),
4794 array($this, 'mxchat_complianz_toggle_callback'),
4795 'mxchat-chatbot',
4796 'mxchat_chatbot_section'
4797 );
4798
4799 add_settings_field(
4800 'link_target_toggle',
4801 esc_html__('Open Links in a New Tab', 'mxchat'),
4802 array($this, 'mxchat_link_target_toggle_callback'),
4803 'mxchat-chatbot',
4804 'mxchat_chatbot_section'
4805 );
4806
4807 add_settings_field(
4808 'chat_persistence_toggle',
4809 esc_html__('Enable Chat Persistence', 'mxchat'),
4810 array($this, 'mxchat_chat_persistence_toggle_callback'),
4811 'mxchat-chatbot',
4812 'mxchat_chatbot_section'
4813 );
4814
4815 add_settings_field(
4816 'popular_question_1',
4817 esc_html__('Quick Question 1', 'mxchat'),
4818 array($this, 'mxchat_popular_question_1_callback'),
4819 'mxchat-chatbot',
4820 'mxchat_chatbot_section'
4821 );
4822
4823 add_settings_field(
4824 'popular_question_2',
4825 esc_html__('Quick Question 2', 'mxchat'),
4826 array($this, 'mxchat_popular_question_2_callback'),
4827 'mxchat-chatbot',
4828 'mxchat_chatbot_section'
4829 );
4830
4831 add_settings_field(
4832 'popular_question_3',
4833 esc_html__('Quick Question 3', 'mxchat'),
4834 array($this, 'mxchat_popular_question_3_callback'),
4835 'mxchat-chatbot',
4836 'mxchat_chatbot_section'
4837 );
4838
4839 add_settings_field(
4840 'additional_popular_questions',
4841 esc_html__('Additional Quick Questions', 'mxchat'),
4842 array($this, 'mxchat_additional_popular_questions_callback'),
4843 'mxchat-chatbot',
4844 'mxchat_chatbot_section'
4845 );
4846
4847
4848 add_settings_field(
4849 'rate_limits',
4850 __('Rate Limits Settings', 'mxchat'),
4851 array($this, 'mxchat_rate_limits_callback'),
4852 'mxchat-chatbot',
4853 'mxchat_chatbot_section'
4854 );
4855
4856 // Loops Settings Section
4857 add_settings_section(
4858 'mxchat_loops_section',
4859 esc_html__('Loops Settings', 'mxchat'),
4860 null,
4861 'mxchat-embed'
4862 );
4863
4864 // Loops Settings Fields (API Key moved to API Keys tab)
4865 add_settings_field(
4866 'loops_mailing_list',
4867 esc_html__('Loops Mailing List', 'mxchat'),
4868 array($this, 'mxchat_loops_mailing_list_callback'),
4869 'mxchat-embed',
4870 'mxchat_loops_section'
4871 );
4872
4873 add_settings_field(
4874 'triggered_phrase_response',
4875 esc_html__('Triggered Phrase Response', 'mxchat'),
4876 array($this, 'mxchat_triggered_phrase_response_callback'),
4877 'mxchat-embed',
4878 'mxchat_loops_section'
4879 );
4880
4881 add_settings_field(
4882 'email_capture_response',
4883 esc_html__('Email Capture Response', 'mxchat'),
4884 array($this, 'mxchat_email_capture_response_callback'),
4885 'mxchat-embed',
4886 'mxchat_loops_section'
4887 );
4888
4889 // Brave Search Settings Fields
4890 add_settings_section(
4891 'mxchat_brave_section',
4892 __('Brave Search Settings', 'mxchat'),
4893 array($this, 'mxchat_brave_section_callback'),
4894 'mxchat-embed'
4895 );
4896
4897 // Brave API Key moved to API Keys tab
4898 add_settings_field(
4899 'brave_image_count',
4900 __('Number of Images to Return', 'mxchat'),
4901 array($this, 'mxchat_brave_image_count_callback'),
4902 'mxchat-embed',
4903 'mxchat_brave_section'
4904 );
4905
4906 add_settings_field(
4907 'brave_safe_search',
4908 __('Safe Search', 'mxchat'),
4909 array($this, 'mxchat_brave_safe_search_callback'),
4910 'mxchat-embed',
4911 'mxchat_brave_section'
4912 );
4913
4914 add_settings_field(
4915 'brave_news_count',
4916 __('Number of News Articles', 'mxchat'),
4917 array($this, 'mxchat_brave_news_count_callback'),
4918 'mxchat-embed',
4919 'mxchat_brave_section'
4920 );
4921
4922 add_settings_field(
4923 'brave_country',
4924 __('Country', 'mxchat'),
4925 array($this, 'mxchat_brave_country_callback'),
4926 'mxchat-embed',
4927 'mxchat_brave_section'
4928 );
4929
4930 add_settings_field(
4931 'brave_language',
4932 __('Language', 'mxchat'),
4933 array($this, 'mxchat_brave_language_callback'),
4934 'mxchat-embed',
4935 'mxchat_brave_section'
4936 );
4937
4938 // Chat with PDF Intent Settings Fields
4939 add_settings_section(
4940 'mxchat_pdf_intent_section',
4941 __('Toolbar Settings & Intents', 'mxchat'),
4942 array($this, 'mxchat_pdf_intent_section_callback'),
4943 'mxchat-embed'
4944 );
4945
4946 add_settings_field(
4947 'chat_toolbar_toggle',
4948 __('Show Chat Toolbar', 'mxchat'),
4949 array($this, 'mxchat_chat_toolbar_toggle_callback'),
4950 'mxchat-embed',
4951 'mxchat_pdf_intent_section'
4952 );
4953
4954 // PDF Upload Button Toggle
4955 add_settings_field(
4956 'show_pdf_upload_button',
4957 __('Show PDF Upload Button', 'mxchat'),
4958 array($this, 'mxchat_show_pdf_upload_button_callback'),
4959 'mxchat-embed',
4960 'mxchat_pdf_intent_section'
4961 );
4962
4963 // Word Upload Button Toggle
4964 add_settings_field(
4965 'show_word_upload_button',
4966 __('Show Word Upload Button', 'mxchat'),
4967 array($this, 'mxchat_show_word_upload_button_callback'),
4968 'mxchat-embed',
4969 'mxchat_pdf_intent_section'
4970 );
4971
4972 add_settings_field(
4973 'pdf_intent_trigger_text',
4974 __('Intent Trigger Text', 'mxchat'),
4975 array($this, 'mxchat_pdf_intent_trigger_text_callback'),
4976 'mxchat-embed',
4977 'mxchat_pdf_intent_section'
4978 );
4979
4980 add_settings_field(
4981 'pdf_intent_success_text',
4982 __('Success Text', 'mxchat'),
4983 array($this, 'mxchat_pdf_intent_success_text_callback'),
4984 'mxchat-embed',
4985 'mxchat_pdf_intent_section'
4986 );
4987
4988 add_settings_field(
4989 'pdf_intent_error_text',
4990 __('Error Text', 'mxchat'),
4991 array($this, 'mxchat_pdf_intent_error_text_callback'),
4992 'mxchat-embed',
4993 'mxchat_pdf_intent_section'
4994 );
4995
4996 // Add PDF Maximum Pages Field
4997 add_settings_field(
4998 'pdf_max_pages',
4999 __('Maximum Document Pages', 'mxchat'),
5000 array($this, 'mxchat_pdf_max_pages_callback'),
5001 'mxchat-embed',
5002 'mxchat_pdf_intent_section'
5003 );
5004
5005 // Live Agent Settings Fields
5006 add_settings_section(
5007 'mxchat_live_agent_section',
5008 __('Live Agent Settings', 'mxchat'),
5009 array($this, 'mxchat_live_agent_section_callback'),
5010 'mxchat-embed'
5011 );
5012
5013 // Live Agent Status Fields (add at top of live agent settings)
5014 add_settings_field(
5015 'live_agent_status',
5016 __('Live Agent Status', 'mxchat'),
5017 array($this, 'mxchat_live_agent_status_callback'),
5018 'mxchat-embed',
5019 'mxchat_live_agent_section'
5020 );
5021
5022 add_settings_field(
5023 'live_agent_notification_message',
5024 __('Notification Message', 'mxchat'),
5025 array($this, 'mxchat_live_agent_notification_message_callback'),
5026 'mxchat-embed',
5027 'mxchat_live_agent_section'
5028 );
5029
5030 add_settings_field(
5031 'live_agent_away_message',
5032 __('Away Message', 'mxchat'),
5033 array($this, 'mxchat_live_agent_away_message_callback'),
5034 'mxchat-embed',
5035 'mxchat_live_agent_section'
5036 );
5037
5038 add_settings_field(
5039 'live_agent_user_ids',
5040 __('Slack Agent User IDs', 'mxchat'),
5041 array($this, 'mxchat_live_agent_user_ids_callback'),
5042 'mxchat-embed',
5043 'mxchat_live_agent_section'
5044 );
5045
5046 add_settings_field(
5047 'live_agent_webhook_url',
5048 __('Slack Webhook URL', 'mxchat'),
5049 array($this, 'mxchat_live_agent_webhook_url_callback'),
5050 'mxchat-embed',
5051 'mxchat_live_agent_section'
5052 );
5053
5054 add_settings_field(
5055 'live_agent_secret_key',
5056 __('Slack Secret Key', 'mxchat'),
5057 array($this, 'mxchat_live_agent_secret_key_callback'),
5058 'mxchat-embed',
5059 'mxchat_live_agent_section'
5060 );
5061
5062 // Live Agent Integration Fields
5063 add_settings_field(
5064 'live_agent_bot_token',
5065 __('Slack Bot OAuth Token', 'mxchat'),
5066 array($this, 'mxchat_live_agent_bot_token_callback'),
5067 'mxchat-embed',
5068 'mxchat_live_agent_section'
5069 );
5070
5071 // Telegram Integration Section
5072 add_settings_section(
5073 'mxchat_telegram_section',
5074 __('Telegram Settings', 'mxchat'),
5075 array($this, 'mxchat_telegram_section_callback'),
5076 'mxchat-embed'
5077 );
5078
5079 add_settings_field(
5080 'telegram_status',
5081 __('Live Agent Status', 'mxchat'),
5082 array($this, 'mxchat_telegram_status_callback'),
5083 'mxchat-embed',
5084 'mxchat_telegram_section'
5085 );
5086
5087 add_settings_field(
5088 'telegram_notification_message',
5089 __('Notification Message', 'mxchat'),
5090 array($this, 'mxchat_telegram_notification_message_callback'),
5091 'mxchat-embed',
5092 'mxchat_telegram_section'
5093 );
5094
5095 add_settings_field(
5096 'telegram_away_message',
5097 __('Away Message', 'mxchat'),
5098 array($this, 'mxchat_telegram_away_message_callback'),
5099 'mxchat-embed',
5100 'mxchat_telegram_section'
5101 );
5102
5103 add_settings_field(
5104 'telegram_bot_token',
5105 __('Telegram Bot Token', 'mxchat'),
5106 array($this, 'mxchat_telegram_bot_token_callback'),
5107 'mxchat-embed',
5108 'mxchat_telegram_section'
5109 );
5110
5111 add_settings_field(
5112 'telegram_group_id',
5113 __('Telegram Group ID', 'mxchat'),
5114 array($this, 'mxchat_telegram_group_id_callback'),
5115 'mxchat-embed',
5116 'mxchat_telegram_section'
5117 );
5118
5119 add_settings_field(
5120 'telegram_webhook_secret',
5121 __('Webhook Secret Token', 'mxchat'),
5122 array($this, 'mxchat_telegram_webhook_secret_callback'),
5123 'mxchat-embed',
5124 'mxchat_telegram_section'
5125 );
5126
5127 // General Settings Section
5128 add_settings_section(
5129 'mxchat_general_section',
5130 esc_html__('YouTube Tutorials', 'mxchat'),
5131 null,
5132 'mxchat-general'
5133 );
5134 }
5135
5136 public function mxchat_prompts_page_init() {
5137 register_setting(
5138 'mxchat_prompts_options',
5139 'mxchat_prompts_options',
5140 array(
5141 'type' => 'array',
5142 'description' => __('MXChat Knowledge Base Settings', 'mxchat'),
5143 'default' => array(
5144 'mxchat_auto_sync_posts' => 0,
5145 'mxchat_auto_sync_pages' => 0,
5146 'mxchat_use_pinecone' => 0,
5147 'mxchat_pinecone_api_key' => '',
5148 'mxchat_pinecone_environment' => '',
5149 'mxchat_pinecone_index' => '',
5150 'mxchat_pinecone_host' => '',
5151 ),
5152 'sanitize_callback' => array($this, 'sanitize_prompts_options'),
5153 )
5154 );
5155
5156 add_action('admin_notices', array($this, 'sync_settings_notice'));
5157 }
5158
5159 public function mxchat_transcripts_page_init() {
5160 register_setting(
5161 'mxchat_transcripts_options',
5162 'mxchat_transcripts_options',
5163 array(
5164 'type' => 'array',
5165 'description' => __('MXChat Transcripts Notification Settings', 'mxchat'),
5166 'default' => array(
5167 'mxchat_enable_notifications' => 0,
5168 'mxchat_notification_email' => get_option('admin_email'),
5169 'mxchat_auto_delete_transcripts' => 'never',
5170 ),
5171 'sanitize_callback' => array($this, 'sanitize_transcripts_options'),
5172 )
5173 );
5174
5175 add_settings_section(
5176 'mxchat_transcripts_notification_section',
5177 esc_html__('Chat Notification Settings', 'mxchat'),
5178 array($this, 'mxchat_transcripts_notification_section_callback'),
5179 'mxchat-transcripts'
5180 );
5181
5182 add_settings_field(
5183 'mxchat_enable_notifications',
5184 esc_html__('Enable Chat Notifications', 'mxchat'),
5185 array($this, 'mxchat_enable_notifications_callback'),
5186 'mxchat-transcripts',
5187 'mxchat_transcripts_notification_section'
5188 );
5189
5190 add_settings_field(
5191 'mxchat_notification_email',
5192 esc_html__('Notification Email Address', 'mxchat'),
5193 array($this, 'mxchat_notification_email_callback'),
5194 'mxchat-transcripts',
5195 'mxchat_transcripts_notification_section'
5196 );
5197
5198 add_settings_field(
5199 'mxchat_auto_delete_transcripts',
5200 esc_html__('Auto-Delete Old Transcripts', 'mxchat'),
5201 array($this, 'mxchat_auto_delete_transcripts_callback'),
5202 'mxchat-transcripts',
5203 'mxchat_transcripts_notification_section'
5204 );
5205
5206 add_settings_field(
5207 'mxchat_auto_email_transcript',
5208 esc_html__('Auto-Email Full Transcript', 'mxchat'),
5209 array($this, 'mxchat_auto_email_transcript_callback'),
5210 'mxchat-transcripts',
5211 'mxchat_transcripts_notification_section'
5212 );
5213 }
5214
5215
5216 /**
5217 * Sanitize all prompts options
5218 *
5219 * @param array $input The unsanitized options array
5220 * @return array The sanitized options array
5221 */
5222 public function sanitize_prompts_options($input) {
5223 // Log the incoming input.
5224 //error_log('Sanitizing inputs: ' . print_r($input, true));
5225
5226 $sanitized = array();
5227
5228 // Boolean options
5229 $sanitized['mxchat_auto_sync_posts'] = isset($input['mxchat_auto_sync_posts']) ? 1 : 0;
5230 $sanitized['mxchat_auto_sync_pages'] = isset($input['mxchat_auto_sync_pages']) ? 1 : 0;
5231 $sanitized['mxchat_use_pinecone'] = !empty($input['mxchat_use_pinecone']) ? 1 : 0;
5232
5233 // API Key: if less than 32 characters, flag as invalid.
5234 $api_key = sanitize_text_field($input['mxchat_pinecone_api_key'] ?? '');
5235 if (!empty($api_key) && strlen($api_key) < 32) {
5236 add_settings_error(
5237 'mxchat_prompts_options',
5238 'invalid_api_key',
5239 __('The Pinecone API key appears to be invalid. Please check your API key.', 'mxchat')
5240 );
5241 $existing_options = get_option('mxchat_prompts_options', array());
5242 $sanitized['mxchat_pinecone_api_key'] = $existing_options['mxchat_pinecone_api_key'] ?? '';
5243 } else {
5244 $sanitized['mxchat_pinecone_api_key'] = $api_key;
5245 }
5246
5247 // Environment and Index Name
5248 $sanitized['mxchat_pinecone_environment'] = sanitize_text_field($input['mxchat_pinecone_environment'] ?? '');
5249 $sanitized['mxchat_pinecone_index'] = sanitize_text_field($input['mxchat_pinecone_index'] ?? '');
5250
5251 // Host: Remove protocol and validate format.
5252 $host = sanitize_text_field($input['mxchat_pinecone_host'] ?? '');
5253 $host = preg_replace('#^https?://#', '', $host);
5254 //error_log('Host after removing protocol: ' . $host);
5255 if (!empty($host)) {
5256 if (!preg_match('/^[\w-]+\.svc\.[\w-]+\.pinecone\.io$/', $host)) {
5257 add_settings_error(
5258 'mxchat_prompts_options',
5259 'invalid_host',
5260 __('The Pinecone host appears to be invalid. It should look like "mxchat-vectors-zrmsquq.svc.aped-4627-b74a.pinecone.io"', 'mxchat')
5261 );
5262 $existing_options = get_option('mxchat_prompts_options', array());
5263 $sanitized['mxchat_pinecone_host'] = $existing_options['mxchat_pinecone_host'] ?? '';
5264 } else {
5265 $sanitized['mxchat_pinecone_host'] = $host;
5266 }
5267 } else {
5268 $sanitized['mxchat_pinecone_host'] = '';
5269 }
5270
5271 //error_log('Final sanitized array: ' . print_r($sanitized, true));
5272
5273 return $sanitized;
5274 }
5275
5276 public function sync_settings_notice() {
5277 // Only show notice on our plugin page
5278 if (!isset($_GET['page']) || $_GET['page'] !== 'mxchat-prompts') {
5279 return;
5280 }
5281
5282 // Check if settings were updated
5283 if (isset($_GET['settings-updated'])) {
5284
5285 ?>
5286 <div class="notice notice-success is-dismissible">
5287 <p><?php esc_html_e('Sync settings updated successfully.', 'mxchat'); ?></p>
5288 <button type="button" class="notice-dismiss"><span class="screen-reader-text"><?php esc_html_e('Dismiss this notice.', 'mxchat'); ?></span></button>
5289 </div>
5290 <?php
5291
5292 }
5293 }
5294 // Add this sanitization function to your class
5295 public function sanitize_sync_setting($input) {
5296 return (bool)$input ? __('1', 'mxchat') : __('', 'mxchat');
5297 }
5298
5299 public function mxchat_rate_limits_callback() {
5300 $all_options = get_option('mxchat_options', []);
5301
5302 // Define available rate limits
5303 $rate_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
5304
5305 // Define available timeframes
5306 $timeframes = array(
5307 'hourly' => __('Per Hour', 'mxchat'),
5308 'daily' => __('Per Day', 'mxchat'),
5309 'weekly' => __('Per Week', 'mxchat'),
5310 'monthly' => __('Per Month', 'mxchat')
5311 );
5312
5313 // Get all roles plus a "logged_out" pseudo-role
5314 $roles = wp_roles()->get_names();
5315 $roles['logged_out'] = __('Logged Out Users', 'mxchat');
5316
5317 // Start the wrapper
5318 echo '<div class="pro-feature-wrapper active">';
5319 echo '<div class="mxchat-rate-limits-container">';
5320
5321 echo '<p class="description" style="margin-bottom: 20px;">' .
5322 esc_html__('Set message limits for each user role and customize the experience when users reach those limits. You can use {limit}, {timeframe}, {count}, and {remaining} as placeholders.', 'mxchat') .
5323 '</p>';
5324
5325 // Add markdown link documentation
5326 echo '<div class="notice notice-info inline" style="margin-bottom: 20px; padding: 10px;">';
5327 echo '<p><strong>' . esc_html__('Markdown Links Supported:', 'mxchat') . '</strong></p>';
5328 echo '<p>' . esc_html__('You can include clickable links in your custom messages using markdown syntax:', 'mxchat') . '</p>';
5329 echo '<ul style="margin-left: 20px;">';
5330 echo '<li><code>[Link text](https://example.com)</code> - Creates a clickable link</li>';
5331 echo '<li><code>[Visit our pricing](https://example.com/pricing)</code> - Link with custom text</li>';
5332 echo '<li><code>Plain URLs like https://example.com will also become clickable</code></li>';
5333 echo '</ul>';
5334 echo '</div>';
5335
5336 // Output the controls for each role
5337 foreach ($roles as $role_id => $role_name) {
5338 // Get saved options or defaults
5339 $default_limit = ($role_id === 'logged_out') ? '10' : '100';
5340 $default_timeframe = 'daily';
5341 $default_message = __('Rate limit exceeded. Please try again later.', 'mxchat');
5342
5343 $selected_limit = isset($all_options['rate_limits'][$role_id]['limit'])
5344 ? $all_options['rate_limits'][$role_id]['limit']
5345 : $default_limit;
5346
5347 $selected_timeframe = isset($all_options['rate_limits'][$role_id]['timeframe'])
5348 ? $all_options['rate_limits'][$role_id]['timeframe']
5349 : $default_timeframe;
5350
5351 $custom_message = isset($all_options['rate_limits'][$role_id]['message'])
5352 ? $all_options['rate_limits'][$role_id]['message']
5353 : $default_message;
5354
5355 // Output the row
5356 echo '<div class="mxchat-rate-limit-row mxchat-autosave-section">';
5357
5358 // Role label
5359 echo '<div class="mxchat-rate-limit-role">' . esc_html($role_name) . '</div>';
5360
5361 // Controls section
5362 echo '<div class="mxchat-rate-limit-controls-wrapper">';
5363
5364 // Rate limit and timeframe controls
5365 echo '<div class="mxchat-rate-limit-controls">';
5366
5367 // Limit dropdown
5368 echo '<div>';
5369 echo '<label for="rate_limits_' . esc_attr($role_id) . '_limit">' . esc_html__('Limit:', 'mxchat') . '</label>';
5370 echo '<select
5371 id="rate_limits_' . esc_attr($role_id) . '_limit"
5372 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][limit]"
5373 class="mxchat-autosave-field">';
5374 foreach ($rate_limits as $limit) {
5375 echo '<option value="' . esc_attr($limit) . '" ' . selected($selected_limit, $limit, false) . '>' . esc_html($limit) . '</option>';
5376 }
5377 echo '</select>';
5378 echo '</div>';
5379
5380 // Timeframe dropdown
5381 echo '<div>';
5382 echo '<label for="rate_limits_' . esc_attr($role_id) . '_timeframe">' . esc_html__('Timeframe:', 'mxchat') . '</label>';
5383 echo '<select
5384 id="rate_limits_' . esc_attr($role_id) . '_timeframe"
5385 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][timeframe]"
5386 class="mxchat-autosave-field">';
5387 foreach ($timeframes as $value => $label) {
5388 echo '<option value="' . esc_attr($value) . '" ' . selected($selected_timeframe, $value, false) . '>' . esc_html($label) . '</option>';
5389 }
5390 echo '</select>';
5391 echo '</div>';
5392
5393 echo '</div>'; // End controls
5394
5395 // Custom message textarea
5396 echo '<div class="mxchat-rate-limit-message">';
5397 echo '<label for="rate_limits_' . esc_attr($role_id) . '_message">' . esc_html__('Custom Message:', 'mxchat') . '</label>';
5398 echo '<textarea
5399 id="rate_limits_' . esc_attr($role_id) . '_message"
5400 name="mxchat_options[rate_limits][' . esc_attr($role_id) . '][message]"
5401 class="mxchat-autosave-field"
5402 placeholder="' . esc_attr__('Enter custom message when rate limit is exceeded', 'mxchat') . '">' .
5403 esc_textarea($custom_message) .
5404 '</textarea>';
5405 echo '<p class="description">' .
5406 esc_html__('Example: Rate limit reached! [Visit our pricing page](https://example.com/pricing) to upgrade.', 'mxchat') .
5407 '</p>';
5408 echo '</div>'; // End message
5409
5410 echo '</div>'; // End controls wrapper
5411
5412 echo '</div>'; // End row
5413 }
5414
5415 echo '</div>'; // End container
5416
5417 echo '</div>'; // End pro-feature-wrapper
5418 }
5419
5420 private function mxchat_add_option_field($id, $title, $callback = '') {
5421 add_settings_field(
5422 $id,
5423 __($title, 'mxchat'),
5424 $callback ? array($this, $callback) : array($this, $id . '_callback'),
5425 'mxchat-max',
5426 'mxchat_setting_section_id',
5427 $id === 'model' ? ['label_for' => 'model'] : []
5428 );
5429 }
5430
5431 // API Keys Section Callback
5432 public function mxchat_api_keys_section_callback() {
5433 echo '<p>' . esc_html__('Manage all your API keys in one place. Add the API keys for the services you want to use with your chatbot.', 'mxchat') . '</p>';
5434 }
5435
5436 // OpenAI API Key
5437 public function api_key_callback() {
5438 $apiKey = isset($this->options['api_key']) ? esc_attr($this->options['api_key']) : '';
5439 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5440
5441 echo '<div class="api-key-wrapper">';
5442 echo '<input type="text" id="api_key" name="api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5443 echo '<button type="button" id="toggleApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5444 echo '<p class="description">' . esc_html__('Required for OpenAI GPT models and OpenAI embeddings. Get your API key from OpenAI Platform.', 'mxchat') . '</p>';
5445 echo '</div>';
5446 }
5447
5448 // X.AI API Key
5449 public function xai_api_key_callback() {
5450 $xaiApiKey = isset($this->options['xai_api_key']) ? esc_attr($this->options['xai_api_key']) : '';
5451 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5452
5453 echo '<div class="api-key-wrapper">';
5454 echo '<input type="text" id="xai_api_key" name="xai_api_key" value="' . $xaiApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5455 echo '<button type="button" id="toggleXaiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5456 echo '<p class="description">' . esc_html__('Required for X.AI Grok models. Get your API key from X.AI Console.', 'mxchat') . '</p>';
5457 echo '</div>';
5458 }
5459 // Claude API Key
5460 public function claude_api_key_callback() {
5461 $claudeApiKey = isset($this->options['claude_api_key']) ? esc_attr($this->options['claude_api_key']) : '';
5462 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5463
5464 echo '<div class="api-key-wrapper">';
5465 echo '<input type="text" id="claude_api_key" name="claude_api_key" value="' . $claudeApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5466 echo '<button type="button" id="toggleClaudeApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5467 echo '<p class="description">' . esc_html__('Required for Anthropic Claude models. Get your API key from Anthropic Console.', 'mxchat') . '</p>';
5468 echo '</div>';
5469 }
5470
5471 // DeepSeek API Key
5472 public function deepseek_api_key_callback() {
5473 $apiKey = isset($this->options['deepseek_api_key']) ? esc_attr($this->options['deepseek_api_key']) : '';
5474 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5475
5476 echo '<div class="api-key-wrapper">';
5477 echo '<input type="text" id="deepseek_api_key" name="deepseek_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5478 echo '<button type="button" id="toggleDeepSeekApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5479 echo '<p class="description">' . esc_html__('Required for DeepSeek models. Get your API key from DeepSeek Platform.', 'mxchat') . '</p>';
5480 echo '</div>';
5481 }
5482
5483 // Gemini API Key
5484 public function gemini_api_key_callback() {
5485 $geminiApiKey = isset($this->options['gemini_api_key']) ? esc_attr($this->options['gemini_api_key']) : '';
5486 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5487
5488 echo '<div class="api-key-wrapper">';
5489 echo '<input type="text" id="gemini_api_key" name="gemini_api_key" value="' . $geminiApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5490 echo '<button type="button" id="toggleGeminiApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5491 echo '<p class="description">' . esc_html__('Required for Google Gemini models and embeddings. Get your API key from Google AI Studio.', 'mxchat') . '</p>';
5492 echo '</div>';
5493 }
5494
5495
5496 // OpenRouter API Key
5497 public function openrouter_api_key_callback() {
5498 $openrouterApiKey = isset($this->options['openrouter_api_key']) ? esc_attr($this->options['openrouter_api_key']) : '';
5499 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5500
5501 echo '<div class="api-key-wrapper">';
5502 echo '<input type="text" id="openrouter_api_key" name="openrouter_api_key" value="' . $openrouterApiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5503 echo '<button type="button" id="toggleOpenRouterApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5504 echo '<p class="description">' . esc_html__('Required for OpenRouter models. Get your API key from OpenRouter.ai', 'mxchat') . '</p>';
5505 echo '</div>';
5506 }
5507
5508 // Voyage API Key
5509 public function voyage_api_key_callback() {
5510 $apiKey = isset($this->options['voyage_api_key']) ? esc_attr($this->options['voyage_api_key']) : '';
5511 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5512
5513 echo '<div class="api-key-wrapper">';
5514 echo '<input type="text" id="voyage_api_key" name="voyage_api_key" value="' . $apiKey . '" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="' . $nonce . '" />';
5515 echo '<button type="button" id="toggleVoyageAPIKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5516 echo '<p class="description">' . esc_html__('Required for Voyage AI embedding models. Get your API key from Voyage AI.', 'mxchat') . '</p>';
5517 echo '</div>';
5518 }
5519
5520 public function mxchat_loops_api_key_callback() {
5521 $loops_api_key = isset($this->options['loops_api_key']) ? esc_attr($this->options['loops_api_key']) : '';
5522 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5523
5524 echo '<div class="api-key-wrapper">';
5525 echo sprintf(
5526 '<input type="text" id="loops_api_key" name="loops_api_key" value="%s" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="%s" />',
5527 $loops_api_key,
5528 $nonce
5529 );
5530 echo '<button type="button" id="toggleLoopsApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
5531 echo '</div>';
5532 echo '<p class="description">' . esc_html__('Required for Loops email integration. Get your API key from Loops.so', 'mxchat') . '</p>';
5533 }
5534 public function mxchat_loops_mailing_list_callback() {
5535 // Add error handling and type checking
5536 $loops_api_key = '';
5537 $selected_list = '';
5538 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5539
5540 // Safely get the API key
5541 if (isset($this->options['loops_api_key']) && is_string($this->options['loops_api_key'])) {
5542 $loops_api_key = $this->options['loops_api_key'];
5543 }
5544
5545 // Safely get the selected list
5546 if (isset($this->options['loops_mailing_list']) && is_string($this->options['loops_mailing_list'])) {
5547 $selected_list = $this->options['loops_mailing_list'];
5548 }
5549
5550 if (!empty($loops_api_key)) {
5551 $lists = $this->mxchat_fetch_loops_mailing_lists($loops_api_key);
5552 if (is_array($lists) && !empty($lists)) {
5553 echo '<div class="mxchat-field-wrapper">';
5554 echo '<select id="loops_mailing_list" name="loops_mailing_list" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
5555
5556 // Add a default "Select a list" option
5557 echo '<option value="" ' . selected($selected_list, '', false) . '>' . esc_html__('Select a list', 'mxchat') . '</option>';
5558
5559 foreach ($lists as $list) {
5560 if (is_array($list) && isset($list['id']) && isset($list['name'])) {
5561 echo sprintf(
5562 '<option value="%s" %s>%s</option>',
5563 esc_attr($list['id']),
5564 selected($selected_list, $list['id'], false),
5565 esc_html($list['name'])
5566 );
5567 }
5568 }
5569 echo '</select>';
5570 echo '</div>';
5571 echo '<p class="description">' . esc_html__('Please select a mailing list to use with Loops.', 'mxchat') . '</p>';
5572 } else {
5573 echo '<p class="description">' . esc_html__('No lists found. Please verify your API Key.', 'mxchat') . '</p>';
5574 }
5575 } else {
5576 echo '<p class="description">' . esc_html__('Enter a valid Loops API Key to load mailing lists.', 'mxchat') . '</p>';
5577 }
5578 }
5579 public function mxchat_triggered_phrase_response_callback() {
5580 $default_response = __('Would you like to join our mailing list? Please provide your email below.', 'mxchat');
5581 $triggered_response = isset($this->options['triggered_phrase_response'])
5582 ? $this->options['triggered_phrase_response']
5583 : $default_response;
5584 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5585
5586 echo '<div class="mxchat-field-wrapper">';
5587 echo sprintf(
5588 '<textarea id="triggered_phrase_response" name="triggered_phrase_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
5589 $nonce,
5590 esc_textarea($triggered_response)
5591 );
5592 echo '</div>';
5593 echo '<p class="description">' . esc_html__('Enter the instruction for the AI when a trigger keyword is detected. The AI will use this as guidance to naturally ask for the user\'s email in a conversational way.', 'mxchat') . '</p>';
5594 }
5595
5596 public function mxchat_email_capture_response_callback() {
5597 $default_response = __('Thank you for providing your email! You\'ve been added to our list.', 'mxchat');
5598 $email_capture_response = isset($this->options['email_capture_response'])
5599 ? $this->options['email_capture_response']
5600 : $default_response;
5601 $nonce = wp_create_nonce('mxchat_autosave_nonce');
5602
5603 echo '<div class="mxchat-field-wrapper">';
5604 echo sprintf(
5605 '<textarea id="email_capture_response" name="email_capture_response" rows="3" cols="50" class="mxchat-autosave-field" data-nonce="%s">%s</textarea>',
5606 $nonce,
5607 esc_textarea($email_capture_response)
5608 );
5609 echo '</div>';
5610 echo '<p class="description">' . esc_html__('Enter the instruction for the AI when a user provides their email. The AI will use this as guidance to naturally confirm the email capture in a conversational way.', 'mxchat') . '</p>';
5611 }
5612 public function mxchat_pre_chat_message_callback() {
5613 // Load the entire 'mxchat_options' array
5614 $all_options = get_option('mxchat_options', []);
5615
5616 // Retrieve the saved message or use the default value
5617 $default_message = __('Hey there! Ask me anything!', 'mxchat');
5618 $pre_chat_message = isset($all_options['pre_chat_message']) ? $all_options['pre_chat_message'] : $default_message;
5619
5620 // Output the textarea
5621 printf(
5622 '<textarea id="pre_chat_message" name="pre_chat_message" rows="5" cols="50">%s</textarea>',
5623 esc_textarea($pre_chat_message)
5624 );
5625 }
5626
5627 // Callback for AI Instructions textarea
5628 public function system_prompt_instructions_callback() {
5629 // Retrieve the current value of the system prompt instructions
5630 $instructions = isset($this->options['system_prompt_instructions']) ? esc_textarea($this->options['system_prompt_instructions']) : '';
5631 // Render the textarea field
5632 printf(
5633 '<textarea id="system_prompt_instructions" name="system_prompt_instructions" rows="5" cols="50">%s</textarea>',
5634 $instructions
5635 );
5636 // Personalization hint
5637 echo '<p class="description" style="margin-top: 8px;">';
5638 echo esc_html__('Use {visitor_name} to personalize AI responses when lead capture is enabled.', 'mxchat') . '<br>';
5639 echo '<code style="font-size: 12px;">' . esc_html__('Example: The visitor\'s name is {visitor_name}. Address them by name.', 'mxchat') . '</code>';
5640 echo '</p>';
5641 // Sample instructions button
5642 echo '<div class="mxchat-instructions-container">';
5643 echo '<button type="button" class="mxchat-instructions-btn" id="mxchatViewSampleBtn">';
5644 echo '<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
5645 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5646 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5647 echo '</svg>';
5648 echo esc_html__('View Sample Instructions', 'mxchat');
5649 echo '</button>';
5650 echo '</div>';
5651
5652 // Add modal to WordPress admin footer instead of inline
5653 add_action('admin_footer', array($this, 'render_sample_instructions_modal'));
5654 }
5655
5656 // New method to render modal in admin footer
5657 public function render_sample_instructions_modal() {
5658 static $modal_rendered = false;
5659 if ($modal_rendered) return; // Prevent duplicate modals
5660 $modal_rendered = true;
5661
5662 echo '<div class="mxchat-instructions-modal-overlay" id="mxchatSampleModal">';
5663 echo '<div class="mxchat-instructions-modal-content">';
5664 echo '<div class="mxchat-instructions-modal-header">';
5665 echo '<h3 class="mxchat-instructions-modal-title">';
5666 echo '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
5667 echo '<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>';
5668 echo '<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>';
5669 echo '</svg>';
5670 echo esc_html__('Sample AI Instructions', 'mxchat');
5671 echo '</h3>';
5672 echo '<button type="button" class="mxchat-instructions-modal-close" id="mxchatModalClose">';
5673 echo '<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
5674 echo '<line x1="18" y1="6" x2="6" y2="18"/>';
5675 echo '<line x1="6" y1="6" x2="18" y2="18"/>';
5676 echo '</svg>';
5677 echo '</button>';
5678 echo '</div>';
5679 echo '<div class="mxchat-instructions-modal-body">';
5680 echo '<div class="mxchat-instructions-content">';
5681 echo esc_html('You are an AI Chatbot assistant for this website. Your main goal is to assist visitors with questions and provide helpful information. Here are your key guidelines:
5682
5683 # Response Style - CRITICALLY IMPORTANT
5684 - MAXIMUM LENGTH: 1-3 short sentences per response
5685 - Ultra-concise: Get straight to the answer with no filler
5686 - No introductions like "Sure!" or "I\'d be happy to help"
5687 - No phrases like "based on my knowledge" or "according to information"
5688 - No explanatory text before giving the answer
5689 - No summaries or repetition
5690 - Hyperlink all URLs
5691 - Respond in user\'s language
5692 - Minor chit chat or conversation is okay, but try to keep it focused on [insert topic]
5693
5694 # Knowledge Base Requirements - PREVENT HALLUCINATIONS
5695 - ONLY answer using information explicitly provided in OFFICIAL KNOWLEDGE DATABASE CONTENT sections marked with ===== delimiters
5696 - If required information is NOT in the knowledge database: "I don\'t have enough information in my knowledge base to answer that question accurately."
5697 - NEVER invent or hallucinate URLs, links, product specs, procedures, dates, statistics, names, contacts, or company information
5698 - When knowledge base information is unclear or contradictory, acknowledge the limitation rather than guessing
5699 - Better to admit insufficient information than provide inaccurate answers');
5700 echo '</div>';
5701 echo '<button type="button" class="mxchat-instructions-copy-btn" id="mxchatCopyBtn">';
5702 echo '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">';
5703 echo '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>';
5704 echo '<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>';
5705 echo '</svg>';
5706 echo esc_html__('Copy Instructions', 'mxchat');
5707 echo '</button>';
5708 echo '</div>';
5709 echo '<div class="mxchat-instructions-modal-footer">';
5710 echo '<button type="button" class="mxchat-instructions-btn-secondary" id="mxchatCloseBtn">' . esc_html__('Close', 'mxchat') . '</button>';
5711 echo '</div>';
5712 echo '</div>';
5713 echo '</div>';
5714 }
5715
5716
5717 public function mxchat_model_callback() {
5718 // Define available models grouped by provider
5719 $models = array(
5720 esc_html__('OpenRouter (100+ Models)', 'mxchat') => array(
5721 'openrouter' => esc_html__('OpenRouter - Select after entering API key', 'mxchat'),
5722 ),
5723 esc_html__('Google Gemini Models', 'mxchat') => array(
5724 'gemini-3-pro-preview' => esc_html__('Gemini 3 Pro (Most Intelligent, Multimodal)', 'mxchat'),
5725 'gemini-3-flash-preview' => esc_html__('Gemini 3 Flash (Balanced Speed & Scale)', 'mxchat'),
5726 'gemini-2.5-pro' => esc_html__('Gemini 2.5 Pro (Advanced Thinking)', 'mxchat'),
5727 'gemini-2.5-flash' => esc_html__('Gemini 2.5 Flash (Best Price-Performance)', 'mxchat'),
5728 'gemini-2.5-flash-lite' => esc_html__('Gemini 2.5 Flash-Lite (Ultra Fast)', 'mxchat'),
5729 'gemini-2.0-flash' => esc_html__('Gemini 2.0 Flash (Deprecated Mar 2026)', 'mxchat'),
5730 'gemini-2.0-flash-lite' => esc_html__('Gemini 2.0 Flash-Lite (Deprecated Mar 2026)', 'mxchat'),
5731 'gemini-1.5-pro' => esc_html__('Gemini 1.5 Pro (Deprecated Sep 2025)', 'mxchat'),
5732 'gemini-1.5-flash' => esc_html__('Gemini 1.5 Flash (Deprecated Sep 2025)', 'mxchat'),
5733 ),
5734 esc_html__('X.AI Models', 'mxchat') => array(
5735 'grok-3-beta' => esc_html__('Grok-3 (Powerful)', 'mxchat'),
5736 'grok-3-fast-beta' => esc_html__('Grok-3 Fast (High Performance)', 'mxchat'),
5737 'grok-3-mini-beta' => esc_html__('Grok-3 Mini (Affordable)', 'mxchat'),
5738 'grok-3-mini-fast-beta' => esc_html__('Grok-3 Mini Fast (Quick Response)', 'mxchat'),
5739 'grok-2' => esc_html__('Grok 2', 'mxchat'),
5740 'grok-4-0709' => esc_html__('Grok 4 (Latest Flagship)', 'mxchat'),
5741 'grok-4-1-fast-reasoning' => esc_html__('Grok 4.1 Fast (Reasoning)', 'mxchat'),
5742 'grok-4-1-fast-non-reasoning' => esc_html__('Grok 4.1 Fast (Non-Reasoning)', 'mxchat'),
5743 ),
5744 esc_html__('DeepSeek Models', 'mxchat') => array(
5745 'deepseek-chat' => esc_html__('DeepSeek-V3', 'mxchat'),
5746 ),
5747 esc_html__('Claude Models', 'mxchat') => array(
5748 'claude-opus-4-6' => esc_html__('Claude Opus 4.6 (Most Capable - Recommended)', 'mxchat'),
5749 'claude-sonnet-4-6' => esc_html__('Claude Sonnet 4.6 (Latest Sonnet - Fast & Capable)', 'mxchat'),
5750 'claude-opus-4-5' => esc_html__('Claude Opus 4.5 (Highly Capable)', 'mxchat'),
5751 'claude-sonnet-4-5-20250929' => esc_html__('Claude Sonnet 4.5 (Best for Agents & Coding)', 'mxchat'),
5752 'claude-opus-4-1-20250805' => esc_html__('Claude Opus 4.1 (Exceptional for Complex Tasks)', 'mxchat'),
5753 'claude-haiku-4-5-20251001' => esc_html__('Claude Haiku 4.5 (Fastest & Most Intelligent)', 'mxchat'),
5754 'claude-opus-4-20250514' => esc_html__('Claude 4 Opus (Complex Tasks)', 'mxchat'),
5755 'claude-sonnet-4-20250514' => esc_html__('Claude 4 Sonnet (High Performance)', 'mxchat'),
5756 ),
5757 esc_html__('OpenAI Models', 'mxchat') => array(
5758 'gpt-5.2' => esc_html__('GPT-5.2 (Best General-Purpose & Agentic Model)', 'mxchat'),
5759 'gpt-5.1-chat-latest' => esc_html__('GPT-5.1 Chat Latest (Recommended)', 'mxchat'),
5760 'gpt-5.1-2025-11-13' => esc_html__('GPT-5.1 (Flagship for Coding & Agentic Tasks)', 'mxchat'),
5761 'gpt-5' => esc_html__('GPT-5 (Flagship for Coding, Reasoning & Agents)', 'mxchat'),
5762 'gpt-5-mini' => esc_html__('GPT-5 Mini (Fast and Lightweight)', 'mxchat'),
5763 'gpt-5-nano' => esc_html__('GPT-5 Nano (Fastest & Cheapest for Summarization/Classification)', 'mxchat'),
5764 ),
5765 );
5766
5767 // Retrieve the currently selected model from saved options
5768 $selected_model = isset($this->options['model']) ? esc_attr($this->options['model']) : 'gpt-5.1-chat-latest';
5769
5770 // Begin the select dropdown
5771 echo '<select id="model" name="model">';
5772
5773 // Iterate over groups of models
5774 foreach ($models as $group_label => $group_models) {
5775 echo '<optgroup label="' . esc_attr($group_label) . '">';
5776
5777 foreach ($group_models as $model_value => $model_label) {
5778 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5779 }
5780
5781 echo '</optgroup>';
5782 }
5783
5784 echo '</select>';
5785
5786 // Add a note for OpenRouter
5787 echo '<p class="description" id="openrouter-model-note" style="display:none; color: #d63638; font-weight: 500;">';
5788 echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle;"></span> ';
5789 echo esc_html__('After entering your OpenRouter API key above, click the button below to load available models.', 'mxchat');
5790 echo '</p>';
5791
5792 // API Key Status Messages (hidden by default, shown by JS based on selected model)
5793 $has_openai_key = !empty($this->options['api_key']);
5794 $has_claude_key = !empty($this->options['claude_api_key']);
5795 $has_xai_key = !empty($this->options['xai_api_key']);
5796 $has_deepseek_key = !empty($this->options['deepseek_api_key']);
5797 $has_gemini_key = !empty($this->options['gemini_api_key']);
5798 $has_openrouter_key = !empty($this->options['openrouter_api_key']);
5799
5800 // OpenAI/GPT models
5801 echo '<p class="mxchat-api-status" data-provider="openai" style="display:none;">';
5802 if ($has_openai_key) {
5803 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
5804 } else {
5805 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5806 }
5807 echo '</p>';
5808
5809 // Claude models
5810 echo '<p class="mxchat-api-status" data-provider="claude" style="display:none;">';
5811 if ($has_claude_key) {
5812 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Anthropic (Claude) detected', 'mxchat') . '</span>';
5813 } else {
5814 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Anthropic (Claude) detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5815 }
5816 echo '</p>';
5817
5818 // X.AI models
5819 echo '<p class="mxchat-api-status" data-provider="xai" style="display:none;">';
5820 if ($has_xai_key) {
5821 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for X.AI (Grok) detected', 'mxchat') . '</span>';
5822 } else {
5823 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for X.AI (Grok) detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5824 }
5825 echo '</p>';
5826
5827 // DeepSeek models
5828 echo '<p class="mxchat-api-status" data-provider="deepseek" style="display:none;">';
5829 if ($has_deepseek_key) {
5830 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for DeepSeek detected', 'mxchat') . '</span>';
5831 } else {
5832 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for DeepSeek detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5833 }
5834 echo '</p>';
5835
5836 // Gemini models
5837 echo '<p class="mxchat-api-status" data-provider="gemini" style="display:none;">';
5838 if ($has_gemini_key) {
5839 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
5840 } else {
5841 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5842 }
5843 echo '</p>';
5844
5845 // OpenRouter models
5846 echo '<p class="mxchat-api-status" data-provider="openrouter" style="display:none;">';
5847 if ($has_openrouter_key) {
5848 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenRouter detected', 'mxchat') . '</span>';
5849 } else {
5850 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenRouter detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
5851 }
5852 echo '</p>';
5853
5854 // ADD THESE HIDDEN FIELDS RIGHT HERE:
5855 $openrouter_model = isset($this->options['openrouter_selected_model']) ? esc_attr($this->options['openrouter_selected_model']) : '';
5856 $openrouter_model_name = isset($this->options['openrouter_selected_model_name']) ? esc_attr($this->options['openrouter_selected_model_name']) : '';
5857
5858 echo '<input type="hidden" id="openrouter_selected_model" name="openrouter_selected_model" value="' . $openrouter_model . '" />';
5859 echo '<input type="hidden" id="openrouter_selected_model_name" name="openrouter_selected_model_name" value="' . $openrouter_model_name . '" />';
5860 }
5861
5862 // Update your existing callback method
5863 public function enable_streaming_toggle_callback() {
5864 // Get value from options array, default to 'on'
5865 $enabled = isset($this->options['enable_streaming_toggle']) ? $this->options['enable_streaming_toggle'] : 'on';
5866 $checked = ($enabled === 'on') ? 'checked' : '';
5867
5868 echo '<label class="toggle-switch">';
5869 echo sprintf(
5870 '<input type="checkbox" id="enable_streaming_toggle" name="enable_streaming_toggle" value="on" %s />',
5871 esc_attr($checked)
5872 );
5873 echo '<span class="slider"></span>';
5874 echo '</label>';
5875
5876 // Test button with better styling
5877 echo '<div style="margin-top: 15px;">';
5878 echo '<button type="button" id="mxchat-test-streaming-btn" class="button button-secondary">';
5879 echo '<span class="dashicons dashicons-admin-tools" style="vertical-align: middle; margin-right: 5px;"></span>';
5880 echo esc_html__('Test Streaming Compatibility', 'mxchat');
5881 echo '</button>';
5882 echo '<p id="mxchat-test-streaming-result" style="margin-top:10px; font-weight: bold;"></p>';
5883 echo '</div>';
5884 }
5885
5886 // Web Search toggle callback
5887 public function enable_web_search_toggle_callback() {
5888 // Get value from options array, default to 'off'
5889 $enabled = isset($this->options['enable_web_search']) ? $this->options['enable_web_search'] : 'off';
5890 $checked = ($enabled === 'on') ? 'checked' : '';
5891
5892 // Get current model to determine if we should show/enable the toggle
5893 $current_model = isset($this->options['model']) ? $this->options['model'] : 'gpt-5.1-chat-latest';
5894
5895 // Models that DON'T support web search (per OpenAI docs)
5896 // gpt-5 with minimal reasoning is handled at API level, gpt-4.1-nano doesn't support it
5897 $unsupported_models = array('gpt-4.1-nano');
5898
5899 // Check if current model is an OpenAI model that supports web search
5900 $openai_models = array(
5901 'gpt-5.2', 'gpt-5.1-chat-latest', 'gpt-5.1-2025-11-13', 'gpt-5', 'gpt-5-mini', 'gpt-5-nano'
5902 );
5903
5904 $is_openai = in_array($current_model, $openai_models);
5905 $is_supported = $is_openai && !in_array($current_model, $unsupported_models);
5906
5907 // Wrapper div with data attributes for JS to show/hide
5908 echo '<div id="web-search-toggle-wrapper" data-openai-models="' . esc_attr(implode(',', $openai_models)) . '" data-unsupported-models="' . esc_attr(implode(',', $unsupported_models)) . '"' . (!$is_supported ? ' style="display:none;"' : '') . '>';
5909
5910 echo '<label class="toggle-switch">';
5911 echo sprintf(
5912 '<input type="checkbox" id="enable_web_search" name="enable_web_search" value="on" %s />',
5913 esc_attr($checked)
5914 );
5915 echo '<span class="slider"></span>';
5916 echo '</label>';
5917
5918 echo '</div>';
5919
5920 // Message shown when non-OpenAI model is selected
5921 echo '<p id="web-search-unavailable-message" class="description" style="color: #666;' . ($is_supported ? ' display:none;' : '') . '">';
5922 echo '<span class="dashicons dashicons-info" style="font-size: 16px; vertical-align: middle; margin-right: 4px;"></span>';
5923 echo esc_html__('Web search is only available for OpenAI models.', 'mxchat');
5924 echo '</p>';
5925 }
5926
5927 // AJAX handler to fetch OpenRouter models
5928 public function fetch_openrouter_models() {
5929 check_ajax_referer('mxchat_fetch_openrouter_models', 'nonce');
5930
5931 $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
5932
5933 if (empty($api_key)) {
5934 wp_send_json_error(array('message' => 'API key is required'));
5935 }
5936
5937 $response = wp_remote_get('https://openrouter.ai/api/v1/models', array(
5938 'headers' => array(
5939 'Authorization' => 'Bearer ' . $api_key,
5940 'Content-Type' => 'application/json',
5941 ),
5942 'timeout' => 15,
5943 ));
5944
5945 if (is_wp_error($response)) {
5946 wp_send_json_error(array('message' => $response->get_error_message()));
5947 }
5948
5949 $body = wp_remote_retrieve_body($response);
5950 $data = json_decode($body, true);
5951
5952 if (isset($data['data']) && is_array($data['data'])) {
5953 // Format the models for the frontend
5954 $models = array_map(function($model) {
5955 return array(
5956 'id' => $model['id'],
5957 'name' => $model['name'] ?? $model['id'],
5958 'description' => $model['description'] ?? '',
5959 'context_length' => $model['context_length'] ?? 0,
5960 'pricing' => array(
5961 'prompt' => $model['pricing']['prompt'] ?? 0,
5962 'completion' => $model['pricing']['completion'] ?? 0,
5963 ),
5964 );
5965 }, $data['data']);
5966
5967 wp_send_json_success(array('models' => $models));
5968 } else {
5969 wp_send_json_error(array('message' => 'Invalid response from OpenRouter'));
5970 }
5971 }
5972
5973 // Callback function for embedding model selection
5974 public function embedding_model_callback() {
5975 $models = array(
5976 esc_html__('OpenAI Embeddings', 'mxchat') => array(
5977 'text-embedding-3-small' => esc_html__('TE3 Small (1536, Efficient)', 'mxchat'),
5978 'text-embedding-ada-002' => esc_html__('Ada 2 (1536, Recommended)', 'mxchat'),
5979 'text-embedding-3-large' => esc_html__('TE3 Large (3072, Powerful)', 'mxchat'),
5980 ),
5981 esc_html__('Voyage AI Embeddings', 'mxchat') => array(
5982 'voyage-3-large' => esc_html__('Voyage-3 Large (2048, Most Capable)', 'mxchat'),
5983 ),
5984 esc_html__('Google Gemini Embeddings', 'mxchat') => array(
5985 'gemini-embedding-001' => esc_html__('Gemini Embedding (1536, Stable)', 'mxchat'),
5986 )
5987 );
5988 $selected_model = isset($this->options['embedding_model']) ? esc_attr($this->options['embedding_model']) : 'text-embedding-ada-002';
5989 echo '<select id="embedding_model" name="embedding_model">';
5990 foreach ($models as $group_label => $group_models) {
5991 echo '<optgroup label="' . esc_attr($group_label) . '">';
5992 foreach ($group_models as $model_value => $model_label) {
5993 echo '<option value="' . esc_attr($model_value) . '" ' . selected($selected_model, $model_value, false) . '>' . esc_html($model_label) . '</option>';
5994 }
5995 echo '</optgroup>';
5996 }
5997 echo '</select>';
5998
5999 // API Key Status Messages for Embedding Models
6000 $has_openai_key = !empty($this->options['api_key']);
6001 $has_voyage_key = !empty($this->options['voyage_api_key']);
6002 $has_gemini_key = !empty($this->options['gemini_api_key']);
6003
6004 // OpenAI Embeddings
6005 echo '<p class="mxchat-embedding-api-status" data-provider="openai" style="display:none;">';
6006 if ($has_openai_key) {
6007 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for OpenAI detected', 'mxchat') . '</span>';
6008 } else {
6009 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for OpenAI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6010 }
6011 echo '</p>';
6012
6013 // Voyage AI Embeddings
6014 echo '<p class="mxchat-embedding-api-status" data-provider="voyage" style="display:none;">';
6015 if ($has_voyage_key) {
6016 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Voyage AI detected', 'mxchat') . '</span>';
6017 } else {
6018 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Voyage AI detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6019 }
6020 echo '</p>';
6021
6022 // Gemini Embeddings
6023 echo '<p class="mxchat-embedding-api-status" data-provider="gemini" style="display:none;">';
6024 if ($has_gemini_key) {
6025 echo '<span style="color: #00a32a;">✓ ' . esc_html__('API key for Google Gemini detected', 'mxchat') . '</span>';
6026 } else {
6027 echo '<span style="color: #d63638;">⚠ ' . esc_html__('No API key for Google Gemini detected. Please enter API key in API Keys tab.', 'mxchat') . '</span>';
6028 }
6029 echo '</p>';
6030 }
6031
6032
6033 public function mxchat_top_bar_title_callback() {
6034 // Retrieve the current value of the top bar title from saved options
6035 $top_bar_title = isset($this->options['top_bar_title']) ? esc_attr($this->options['top_bar_title']) : '';
6036
6037 // Render the input field
6038 echo '<input type="text" id="top_bar_title" name="top_bar_title" value="' . $top_bar_title . '" />';
6039 }
6040 public function mxchat_ai_agent_text_callback() {
6041 // Retrieve the current value of the AI agent text from saved options
6042 $ai_agent_text = isset($this->options['ai_agent_text']) ? esc_attr($this->options['ai_agent_text']) : '';
6043 // Render the input field
6044 echo '<input type="text" id="ai_agent_text" name="ai_agent_text" value="' . $ai_agent_text . '" />';
6045 }
6046
6047
6048 public function enable_email_block_callback() {
6049 // Load full plugin options array
6050 $all_options = get_option('mxchat_options', []);
6051
6052 // Get the value, default to 'off'
6053 $enable_email_block = isset($all_options['enable_email_block']) ? $all_options['enable_email_block'] : 'off';
6054
6055 // Check if it's 'on'
6056 $checked = ($enable_email_block === 'on') ? 'checked' : '';
6057
6058 echo '<label class="toggle-switch">';
6059 echo sprintf(
6060 '<input type="checkbox" id="enable_email_block" name="enable_email_block" value="on" %s />',
6061 esc_attr($checked)
6062 );
6063 echo '<span class="slider"></span>';
6064 echo '</label>';
6065 }
6066
6067 public function email_blocker_header_content_callback() {
6068 // Load the entire 'mxchat_options' array
6069 $all_options = get_option('mxchat_options', []);
6070
6071 // Retrieve the saved content or default to empty
6072 $content = isset($all_options['email_blocker_header_content'])
6073 ? $all_options['email_blocker_header_content']
6074 : '';
6075
6076 // Render the textarea - IMPORTANT: name should be just "email_blocker_header_content"
6077 echo '<textarea
6078 id="email_blocker_header_content"
6079 name="email_blocker_header_content"
6080 rows="5"
6081 cols="70"
6082 data-setting="email_blocker_header_content"
6083 >' . esc_textarea($content) . '</textarea>';
6084 }
6085
6086 public function email_blocker_button_text_callback() {
6087 // Load the entire 'mxchat_options' array
6088 $all_options = get_option('mxchat_options', []);
6089
6090 // Retrieve the saved button text or default to empty
6091 $button_text = isset($all_options['email_blocker_button_text'])
6092 ? $all_options['email_blocker_button_text']
6093 : '';
6094
6095 // Use esc_attr to safely render the existing text
6096 echo '<input type="text" id="email_blocker_button_text" name="email_blocker_button_text" value="' . esc_attr($button_text) . '" style="width: 300px;" />';
6097 }
6098
6099 //Enable name field callback
6100 public function enable_name_field_callback() {
6101 // Load full plugin options array
6102 $all_options = get_option('mxchat_options', []);
6103 // Get the value, default to 'off'
6104 $enable_name_field = isset($all_options['enable_name_field']) ? $all_options['enable_name_field'] : 'off';
6105 // Check if it's 'on'
6106 $checked = ($enable_name_field === 'on') ? 'checked' : '';
6107 echo '<label class="toggle-switch">';
6108 echo sprintf(
6109 '<input type="checkbox" id="enable_name_field" name="enable_name_field" value="on" %s />',
6110 esc_attr($checked)
6111 );
6112 echo '<span class="slider"></span>';
6113 echo '</label>';
6114 }
6115 //Name field placeholder callback
6116 public function name_field_placeholder_callback() {
6117 $all_options = get_option('mxchat_options', []);
6118 $placeholder = isset($all_options['name_field_placeholder'])
6119 ? $all_options['name_field_placeholder']
6120 : esc_html__('Enter your name', 'mxchat');
6121
6122 echo '<input type="text" id="name_field_placeholder" name="name_field_placeholder" value="' . esc_attr($placeholder) . '" style="width: 300px;" />';
6123 }
6124
6125
6126
6127 public function mxchat_intro_message_callback() {
6128 // Load the entire 'mxchat_options' array
6129 $all_options = get_option('mxchat_options', []);
6130 // Retrieve the saved intro message or use the default
6131 $default_message = __('Hello! How can I assist you today?', 'mxchat');
6132 $saved_message = isset($all_options['intro_message']) ? $all_options['intro_message'] : $default_message;
6133 // Output the textarea with the saved value without escaping HTML
6134 ?>
6135 <textarea id="intro_message" name="intro_message" rows="5" cols="50"><?php echo $saved_message; ?></textarea>
6136 <p class="description" style="margin-top: 8px;">
6137 <?php esc_html_e('Use {visitor_name} to personalize greetings when lead capture is enabled.', 'mxchat'); ?><br>
6138 <code style="font-size: 12px;"><?php esc_html_e('Example: Hello {visitor_name}! How can I help you today?', 'mxchat'); ?></code>
6139 </p>
6140 <?php
6141 }
6142
6143 public function mxchat_input_copy_callback() {
6144 // Load the entire 'mxchat_options' array
6145 $all_options = get_option('mxchat_options', []);
6146
6147 // Retrieve the saved input copy or use the default value
6148 $default_copy = __('How can I assist?', 'mxchat');
6149 $input_copy = isset($all_options['input_copy']) ? $all_options['input_copy'] : $default_copy;
6150
6151 // Output the input field with the saved value
6152 printf(
6153 '<input type="text" id="input_copy" name="input_copy" value="%s" placeholder="%s" />',
6154 esc_attr($input_copy),
6155 esc_attr__('How can I assist?', 'mxchat')
6156 );
6157 }
6158
6159
6160 public function mxchat_append_to_body_callback() {
6161 // Fetch fresh options to ensure we have the latest saved values
6162 $options = get_option('mxchat_options', array());
6163
6164 // Get value from options array, default to 'off'
6165 $append_to_body = isset($options['append_to_body']) ? $options['append_to_body'] : 'off';
6166 $checked = ($append_to_body === 'on') ? 'checked' : '';
6167
6168 // Get post type visibility settings
6169 $visibility_mode = isset($options['post_type_visibility_mode']) ? $options['post_type_visibility_mode'] : 'all';
6170 $visibility_list = isset($options['post_type_visibility_list']) ? $options['post_type_visibility_list'] : array();
6171 if (!is_array($visibility_list)) {
6172 $visibility_list = array();
6173 }
6174
6175 echo '<div class="mxchat-autosave-section">';
6176
6177 // Main toggle
6178 echo '<label class="toggle-switch">';
6179 echo sprintf(
6180 '<input type="checkbox" id="append_to_body" name="append_to_body" value="on" %s />',
6181 esc_attr($checked)
6182 );
6183 echo '<span class="slider"></span>';
6184 echo '</label>';
6185
6186 // Post Type Visibility Options (only visible when auto-display is ON)
6187 $display_style = ($append_to_body === 'on') ? '' : 'display: none;';
6188 echo '<div id="post-type-visibility-options" class="mxchat-sub-options" style="' . esc_attr($display_style) . '">';
6189
6190 echo '<div class="mxchat-post-type-visibility-header">';
6191 echo '<h4>' . esc_html__('Post Type Visibility', 'mxchat') . '</h4>';
6192 echo '</div>';
6193
6194 // Mode selector (radio buttons)
6195 echo '<div class="mxchat-visibility-mode">';
6196
6197 echo '<label class="mxchat-radio-label">';
6198 echo '<input type="radio" name="post_type_visibility_mode" value="all" ' . checked($visibility_mode, 'all', false) . ' />';
6199 echo '<span>' . esc_html__('Show on all post types', 'mxchat') . '</span>';
6200 echo '</label>';
6201
6202 echo '<label class="mxchat-radio-label">';
6203 echo '<input type="radio" name="post_type_visibility_mode" value="include" ' . checked($visibility_mode, 'include', false) . ' />';
6204 echo '<span>' . esc_html__('Only show on selected post types', 'mxchat') . '</span>';
6205 echo '</label>';
6206
6207 echo '<label class="mxchat-radio-label">';
6208 echo '<input type="radio" name="post_type_visibility_mode" value="exclude" ' . checked($visibility_mode, 'exclude', false) . ' />';
6209 echo '<span>' . esc_html__('Hide on selected post types', 'mxchat') . '</span>';
6210 echo '</label>';
6211
6212 echo '</div>';
6213
6214 // Post type checkboxes (only visible when mode is include or exclude)
6215 $list_display = ($visibility_mode !== 'all') ? '' : 'display: none;';
6216 echo '<div id="post-type-list" class="mxchat-post-type-list" style="' . esc_attr($list_display) . '">';
6217
6218 // Get all public post types
6219 $post_types = get_post_types(array('public' => true), 'objects');
6220
6221 foreach ($post_types as $post_type) {
6222 // Skip attachments
6223 if ($post_type->name === 'attachment') {
6224 continue;
6225 }
6226
6227 $is_checked = in_array($post_type->name, $visibility_list) ? 'checked' : '';
6228
6229 echo '<label class="mxchat-checkbox-label">';
6230 echo '<input type="checkbox" name="post_type_visibility_list[]" value="' . esc_attr($post_type->name) . '" ' . $is_checked . ' />';
6231 echo '<span>' . esc_html($post_type->label) . '</span>';
6232 echo '</label>';
6233 }
6234
6235 echo '</div>'; // End post-type-list
6236 echo '</div>'; // End post-type-visibility-options
6237 echo '</div>'; // End mxchat-autosave-section
6238 }
6239
6240 public function mxchat_frontend_debugger_callback() {
6241 $show_debugger = isset($this->options['show_frontend_debugger']) ? $this->options['show_frontend_debugger'] : 'on';
6242 $checked = ($show_debugger === 'on') ? 'checked' : '';
6243 echo '<label class="toggle-switch">';
6244 echo sprintf(
6245 '<input type="checkbox" id="show_frontend_debugger" name="show_frontend_debugger" value="on" %s />',
6246 esc_attr($checked)
6247 );
6248 echo '<span class="slider"></span>';
6249 echo '</label>';
6250 }
6251
6252 public function mxchat_contextual_awareness_callback() {
6253 // Get value from options array, default to 'off'
6254 $contextual_awareness = isset($this->options['contextual_awareness_toggle']) ? $this->options['contextual_awareness_toggle'] : 'off';
6255 $checked = ($contextual_awareness === 'on') ? 'checked' : '';
6256 echo '<label class="toggle-switch">';
6257 echo sprintf(
6258 '<input type="checkbox" id="contextual_awareness_toggle" name="contextual_awareness_toggle" value="on" %s />',
6259 esc_attr($checked)
6260 );
6261 echo '<span class="slider"></span>';
6262 echo '</label>';
6263 }
6264
6265 public function mxchat_citation_links_toggle_callback() {
6266 // Get value from options array, default to 'on' (enabled by default)
6267 $citation_links = isset($this->options['citation_links_toggle']) ? $this->options['citation_links_toggle'] : 'on';
6268 $checked = ($citation_links === 'on') ? 'checked' : '';
6269 echo '<label class="toggle-switch">';
6270 echo sprintf(
6271 '<input type="checkbox" id="citation_links_toggle" name="citation_links_toggle" value="on" %s />',
6272 esc_attr($checked)
6273 );
6274 echo '<span class="slider"></span>';
6275 echo '</label>';
6276 }
6277
6278 public function mxchat_privacy_toggle_callback() {
6279 // Load from mxchat_options array
6280 $options = get_option('mxchat_options', []);
6281
6282 // Get privacy toggle value with fallback
6283 $privacy_toggle = isset($options['privacy_toggle']) ? $options['privacy_toggle'] : 'off';
6284 $checked = ($privacy_toggle === 'on') ? 'checked' : '';
6285
6286 // Get privacy text with fallback
6287 $privacy_text = isset($options['privacy_text'])
6288 ? $options['privacy_text']
6289 : __('By chatting, you agree to our <a href="https://example.com/privacy-policy" target="_blank">privacy policy</a>.', 'mxchat');
6290
6291 // Output the toggle switch
6292 echo '<label class="toggle-switch">';
6293 echo sprintf(
6294 '<input type="checkbox" id="privacy_toggle" name="privacy_toggle" value="on" %s />',
6295 esc_attr($checked)
6296 );
6297 echo '<span class="slider"></span>';
6298 echo '</label>';
6299
6300 // Output the custom text input field
6301 echo sprintf(
6302 '<textarea id="privacy_text" name="privacy_text" rows="5" cols="50" class="regular-text">%s</textarea>',
6303 esc_textarea($privacy_text)
6304 );
6305 }
6306
6307
6308 public function mxchat_complianz_toggle_callback() {
6309 // Load from mxchat_options array
6310 $options = get_option('mxchat_options', []);
6311
6312 // Get complianz toggle value with fallback
6313 $complianz_toggle = isset($options['complianz_toggle']) ? $options['complianz_toggle'] : 'off';
6314 $checked = ($complianz_toggle === 'on') ? 'checked' : '';
6315
6316 // Output the toggle switch
6317 echo '<label class="toggle-switch">';
6318 echo sprintf(
6319 '<input type="checkbox" id="complianz_toggle" name="complianz_toggle" value="on" %s />',
6320 esc_attr($checked)
6321 );
6322 echo '<span class="slider"></span>';
6323 echo '</label>';
6324 }
6325
6326 public function mxchat_link_target_toggle_callback() {
6327 // Load from mxchat_options array
6328 $options = get_option('mxchat_options', []);
6329
6330 // Get link target toggle value with fallback
6331 $link_target_toggle = isset($options['link_target_toggle']) ? $options['link_target_toggle'] : 'off';
6332 $checked = ($link_target_toggle === 'on') ? 'checked' : '';
6333
6334 // Output the toggle switch
6335 echo '<label class="toggle-switch">';
6336 echo sprintf(
6337 '<input type="checkbox" id="link_target_toggle" name="link_target_toggle" value="on" %s />',
6338 esc_attr($checked)
6339 );
6340 echo '<span class="slider"></span>';
6341 echo '</label>';
6342 }
6343
6344 public function mxchat_chat_persistence_toggle_callback() {
6345 // Load from mxchat_options array
6346 $options = get_option('mxchat_options', []);
6347
6348 // Get chat persistence toggle value with fallback
6349 $chat_persistence_toggle = isset($options['chat_persistence_toggle']) ? $options['chat_persistence_toggle'] : 'off';
6350 $checked = ($chat_persistence_toggle === 'on') ? 'checked' : '';
6351
6352 // Output the toggle switch
6353 echo '<label class="toggle-switch">';
6354 echo sprintf(
6355 '<input type="checkbox" id="chat_persistence_toggle" name="chat_persistence_toggle" value="on" %s />',
6356 esc_attr($checked)
6357 );
6358 echo '<span class="slider"></span>';
6359 echo '</label>';
6360 }
6361
6362 public function mxchat_popular_question_1_callback() {
6363 // Load the full plugin options array
6364 $all_options = get_option('mxchat_options', []);
6365
6366 // Retrieve the specific option for popular_question_1
6367 $popular_question_1 = isset($all_options['popular_question_1']) ? $all_options['popular_question_1'] : '';
6368
6369 // Render the input field
6370 printf(
6371 '<input type="text" id="popular_question_1" name="popular_question_1" value="%s" placeholder="%s" class="regular-text" />',
6372 esc_attr($popular_question_1),
6373 esc_attr__('Enter Quick Question 1', 'mxchat')
6374 );
6375 }
6376
6377
6378 public function mxchat_popular_question_2_callback() {
6379 // Load the full plugin options array
6380 $all_options = get_option('mxchat_options', []);
6381
6382 // Retrieve the specific option for popular_question_2
6383 $popular_question_2 = isset($all_options['popular_question_2']) ? $all_options['popular_question_2'] : '';
6384
6385 // Render the input field
6386 printf(
6387 '<input type="text" id="popular_question_2" name="popular_question_2" value="%s" placeholder="%s" class="regular-text" />',
6388 esc_attr($popular_question_2),
6389 esc_attr__('Enter Quick Question 2', 'mxchat')
6390 );
6391 }
6392
6393
6394 public function mxchat_popular_question_3_callback() {
6395 // Load the full plugin options array
6396 $all_options = get_option('mxchat_options', []);
6397
6398 // Retrieve the specific option for popular_question_3
6399 $popular_question_3 = isset($all_options['popular_question_3']) ? $all_options['popular_question_3'] : '';
6400
6401 // Render the input field
6402 printf(
6403 '<input type="text" id="popular_question_3" name="popular_question_3" value="%s" placeholder="%s" class="regular-text" />',
6404 esc_attr($popular_question_3),
6405 esc_attr(__('Enter Quick Question 3', 'mxchat'))
6406 );
6407 }
6408
6409 public function mxchat_additional_popular_questions_callback() {
6410 $options = get_option('mxchat_options', []);
6411 $additional_questions = isset($options['additional_popular_questions'])
6412 ? $options['additional_popular_questions']
6413 : get_option('additional_popular_questions', array());
6414
6415 echo '<div id="mxchat-additional-questions-container">';
6416 if (!empty($additional_questions)) {
6417 foreach ($additional_questions as $index => $question) {
6418 printf(
6419 '<div class="mxchat-question-row">
6420 <input type="text" name="additional_popular_questions[]"
6421 value="%s"
6422 placeholder="%s"
6423 class="regular-text mxchat-question-input"
6424 data-question-index="%d" />
6425 <button type="button" class="button mxchat-remove-question"
6426 aria-label="%s">%s</button>
6427 </div>',
6428 esc_attr($question),
6429 esc_attr(sprintf(__('Enter Additional Quick Question %d', 'mxchat'), $index + 4)),
6430 $index,
6431 esc_attr(__('Remove question', 'mxchat')),
6432 esc_html__('Remove', 'mxchat')
6433 );
6434 }
6435 } else {
6436 printf(
6437 '<div class="mxchat-question-row">
6438 <input type="text" name="additional_popular_questions[]"
6439 value=""
6440 placeholder="%s"
6441 class="regular-text mxchat-question-input"
6442 data-question-index="0" />
6443 <button type="button" class="button mxchat-remove-question"
6444 aria-label="%s">%s</button>
6445 </div>',
6446 esc_attr(__('Enter Additional Quick Question 4', 'mxchat')),
6447 esc_attr(__('Remove question', 'mxchat')),
6448 esc_html__('Remove', 'mxchat')
6449 );
6450 }
6451 echo '</div>';
6452 printf(
6453 '<button type="button" class="button mxchat-add-question" aria-label="%s">%s</button>',
6454 esc_attr(__('Add question', 'mxchat')),
6455 esc_html__('Add Question', 'mxchat')
6456 );
6457 }
6458
6459 public function mxchat_brave_api_key_callback() {
6460 $brave_api_key = isset($this->options['brave_api_key']) ? esc_attr($this->options['brave_api_key']) : '';
6461 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6462
6463 echo '<div class="api-key-wrapper">';
6464 echo sprintf(
6465 '<input type="text" id="brave_api_key" name="brave_api_key" value="%s" class="regular-text mxchat-autosave-field mxchat-api-key-field" autocomplete="new-password" data-lpignore="true" data-form-type="other" data-nonce="%s" />',
6466 $brave_api_key,
6467 $nonce
6468 );
6469 echo '<button type="button" id="toggleBraveApiKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6470 echo '</div>';
6471 echo '<p class="description">' . __('Required for Brave Search integration. Get your API key from Brave Search API.', 'mxchat') . '</p>';
6472 }
6473
6474 public function mxchat_brave_image_count_callback() {
6475 $brave_image_count = isset($this->options['brave_image_count'])
6476 ? intval($this->options['brave_image_count'])
6477 : 4;
6478 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6479
6480 echo '<div class="mxchat-field-wrapper">';
6481 echo sprintf(
6482 '<input type="number" id="brave_image_count" name="brave_image_count"
6483 value="%d" min="1" max="6" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6484 $brave_image_count,
6485 $nonce
6486 );
6487 echo '</div>';
6488 echo '<p class="description">' . __('Select the number of images to return (1-6).', 'mxchat') . '</p>';
6489 }
6490
6491 public function mxchat_brave_safe_search_callback() {
6492 $brave_safe_search = isset($this->options['brave_safe_search'])
6493 ? esc_attr($this->options['brave_safe_search'])
6494 : 'strict';
6495 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6496
6497 echo '<div class="mxchat-field-wrapper">';
6498 echo '<select id="brave_safe_search" name="brave_safe_search" class="mxchat-autosave-field" data-nonce="' . $nonce . '">';
6499 echo sprintf(
6500 '<option value="strict" %s>%s</option>',
6501 selected($brave_safe_search, 'strict', false),
6502 __('Strict', 'mxchat')
6503 );
6504 echo sprintf(
6505 '<option value="off" %s>%s</option>',
6506 selected($brave_safe_search, 'off', false),
6507 __('Off', 'mxchat')
6508 );
6509 echo '</select>';
6510 echo '</div>';
6511 echo '<p class="description">' .
6512 esc_html__('Set the Safe Search level for image searches. Brave Search only supports "Strict" and "Off" options.', 'mxchat') .
6513 '</p>';
6514 }
6515
6516 public function mxchat_brave_news_count_callback() {
6517 $brave_news_count = isset($this->options['brave_news_count'])
6518 ? intval($this->options['brave_news_count'])
6519 : 3;
6520 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6521
6522 echo '<div class="mxchat-field-wrapper">';
6523 echo sprintf(
6524 '<input type="number" id="brave_news_count" name="brave_news_count"
6525 value="%d" min="1" max="10" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6526 $brave_news_count,
6527 $nonce
6528 );
6529 echo '</div>';
6530 echo '<p class="description">' . esc_html__('Select the number of news articles to retrieve (1-10).', 'mxchat') . '</p>';
6531 }
6532
6533 public function mxchat_brave_country_callback() {
6534 $brave_country = isset($this->options['brave_country'])
6535 ? esc_attr($this->options['brave_country'])
6536 : 'us';
6537 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6538
6539 echo '<div class="mxchat-field-wrapper">';
6540 echo sprintf(
6541 '<input type="text" id="brave_country" name="brave_country"
6542 value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6543 $brave_country,
6544 $nonce
6545 );
6546 echo '</div>';
6547 echo '<p class="description">' . esc_html__('Enter the country code (e.g., "us" for United States).', 'mxchat') . '</p>';
6548 }
6549
6550 public function mxchat_brave_language_callback() {
6551 $brave_language = isset($this->options['brave_language'])
6552 ? esc_attr($this->options['brave_language'])
6553 : 'en';
6554 $nonce = wp_create_nonce('mxchat_autosave_nonce');
6555
6556 echo '<div class="mxchat-field-wrapper">';
6557 echo sprintf(
6558 '<input type="text" id="brave_language" name="brave_language"
6559 value="%s" maxlength="2" class="small-text mxchat-autosave-field" data-nonce="%s" />',
6560 $brave_language,
6561 $nonce
6562 );
6563 echo '</div>';
6564 echo '<p class="description">' . esc_html__('Enter the language code (e.g., "en" for English).', 'mxchat') . '</p>';
6565 }
6566
6567
6568
6569
6570
6571 // Section Callback
6572 public function mxchat_pdf_intent_section_callback() {
6573 echo '<p>' . esc_html__('Configure the intent settings for the Chat with PDF feature.', 'mxchat') . '</p>';
6574 }
6575
6576 public function mxchat_chat_toolbar_toggle_callback() {
6577 // Get chat toolbar toggle value with fallback
6578 $chat_toolbar_toggle = isset($this->options['chat_toolbar_toggle']) ? $this->options['chat_toolbar_toggle'] : 'off';
6579 $checked = ($chat_toolbar_toggle === 'on') ? 'checked' : '';
6580
6581 // Output the toggle switch
6582 echo '<label class="toggle-switch">';
6583 echo sprintf(
6584 '<input type="checkbox" id="chat_toolbar_toggle" name="chat_toolbar_toggle" value="on" %s />',
6585 esc_attr($checked)
6586 );
6587 echo '<span class="slider"></span>';
6588 echo '</label>';
6589
6590 echo '<p class="description">' . esc_html__('Enable to display the chat toolbar, adding two icons below the chatbot input field for uploading PDF and Word documents (default is hidden).', 'mxchat') . '</p>';
6591 }
6592
6593 /**
6594 * Callback for PDF upload button toggle setting
6595 */
6596 public function mxchat_show_pdf_upload_button_callback() {
6597 // Get toggle value with fallback
6598 $show_pdf_button = isset($this->options['show_pdf_upload_button']) ? $this->options['show_pdf_upload_button'] : 'on';
6599 $checked = ($show_pdf_button === 'on') ? 'checked' : '';
6600
6601 // Output the toggle switch
6602 echo '<label class="toggle-switch">';
6603 echo sprintf(
6604 '<input type="checkbox" id="show_pdf_upload_button" name="show_pdf_upload_button" value="on" %s />',
6605 esc_attr($checked)
6606 );
6607 echo '<span class="slider"></span>';
6608 echo '</label>';
6609
6610 echo '<p class="description">' . esc_html__('Enable to show the PDF upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6611 }
6612
6613 /**
6614 * Callback for Word upload button toggle setting
6615 */
6616 public function mxchat_show_word_upload_button_callback() {
6617 // Get toggle value with fallback
6618 $show_word_button = isset($this->options['show_word_upload_button']) ? $this->options['show_word_upload_button'] : 'on';
6619 $checked = ($show_word_button === 'on') ? 'checked' : '';
6620
6621 // Output the toggle switch
6622 echo '<label class="toggle-switch">';
6623 echo sprintf(
6624 '<input type="checkbox" id="show_word_upload_button" name="show_word_upload_button" value="on" %s />',
6625 esc_attr($checked)
6626 );
6627 echo '<span class="slider"></span>';
6628 echo '</label>';
6629
6630 echo '<p class="description">' . esc_html__('Enable to show the Word document upload button in the chatbot toolbar. Disable to hide it.', 'mxchat') . '</p>';
6631 }
6632
6633 public function mxchat_pdf_intent_trigger_text_callback() {
6634 $default_text = __("Please provide the URL to the PDF you'd like to discuss.", 'mxchat');
6635
6636 echo sprintf(
6637 '<textarea id="pdf_intent_trigger_text"
6638 name="pdf_intent_trigger_text"
6639 rows="3"
6640 cols="50"
6641 placeholder="%s">%s</textarea>',
6642 esc_attr__('Enter trigger text', 'mxchat'),
6643 isset($this->options['pdf_intent_trigger_text'])
6644 ? esc_textarea($this->options['pdf_intent_trigger_text'])
6645 : esc_textarea($default_text)
6646 );
6647 echo '<p class="description">' . esc_html__('Text displayed when the intent is triggered.', 'mxchat') . '</p>';
6648 }
6649
6650 public function mxchat_pdf_intent_success_text_callback() {
6651 $default_text = __("I've processed the PDF. What questions do you have about it?", 'mxchat');
6652
6653 echo sprintf(
6654 '<textarea id="pdf_intent_success_text"
6655 name="pdf_intent_success_text"
6656 rows="3"
6657 cols="50"
6658 placeholder="%s">%s</textarea>',
6659 esc_attr__('Enter success text', 'mxchat'),
6660 isset($this->options['pdf_intent_success_text'])
6661 ? esc_textarea($this->options['pdf_intent_success_text'])
6662 : esc_textarea($default_text)
6663 );
6664 echo '<p class="description">' . esc_html__('Text displayed when the intent is successful.', 'mxchat') . '</p>';
6665 }
6666
6667 public function mxchat_pdf_intent_error_text_callback() {
6668 $default_text = __("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat');
6669
6670 echo sprintf(
6671 '<textarea id="pdf_intent_error_text"
6672 name="pdf_intent_error_text"
6673 rows="3"
6674 cols="50"
6675 placeholder="%s">%s</textarea>',
6676 esc_attr__('Enter error text', 'mxchat'),
6677 isset($this->options['pdf_intent_error_text'])
6678 ? esc_textarea($this->options['pdf_intent_error_text'])
6679 : esc_textarea($default_text)
6680 );
6681 echo '<p class="description">' . esc_html__('Text displayed when an error occurs during the intent.', 'mxchat') . '</p>';
6682 }
6683
6684 public function mxchat_pdf_max_pages_callback() {
6685 $max_pages = isset($this->options['pdf_max_pages']) ? intval($this->options['pdf_max_pages']) : 69;
6686
6687 echo sprintf(
6688 '<input type="range"
6689 id="pdf_max_pages"
6690 name="pdf_max_pages"
6691 min="1"
6692 max="69"
6693 value="%d"
6694 class="range-slider" />',
6695 esc_attr($max_pages)
6696 );
6697 echo '<span id="pdf_max_pages_output">' . esc_html($max_pages) . '</span>';
6698 echo '<p class="description">' . esc_html__('Set the maximum number of document pages users can upload for processing. (1-69 pages)', 'mxchat') . '</p>';
6699 }
6700
6701 public function mxchat_live_agent_status_callback() {
6702 // Always get fresh options instead of using cached $this->options
6703 $fresh_options = get_option('mxchat_options');
6704 $status = isset($fresh_options['live_agent_status']) ? $fresh_options['live_agent_status'] : 'off';
6705
6706 echo '<label class="toggle-switch">';
6707 echo sprintf(
6708 '<input type="checkbox" id="live_agent_status" name="live_agent_status" value="on" %s />',
6709 checked($status, 'on', false)
6710 );
6711 echo '<span class="slider"></span>';
6712 echo '</label>';
6713 echo '<label for="live_agent_status" class="mxchat-status-label">';
6714 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
6715 echo '</label>';
6716 }
6717
6718 public function mxchat_live_agent_away_message_callback() {
6719 $message = isset($this->options['live_agent_away_message'])
6720 ? $this->options['live_agent_away_message']
6721 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
6722
6723 printf(
6724 '<textarea id="live_agent_away_message" name="live_agent_away_message" rows="3" cols="50">%s</textarea>',
6725 esc_textarea($message)
6726 );
6727 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
6728 }
6729
6730 public function mxchat_live_agent_notification_message_callback() {
6731 $message = isset($this->options['live_agent_notification_message'])
6732 ? $this->options['live_agent_notification_message']
6733 : __('Live agent has been notified.', 'mxchat');
6734
6735 printf(
6736 '<textarea id="live_agent_notification_message" name="live_agent_notification_message" rows="3" cols="50">%s</textarea>',
6737 esc_textarea($message)
6738 );
6739 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
6740 }
6741
6742 public function mxchat_live_agent_webhook_url_callback() {
6743 $webhook_url = isset($this->options['live_agent_webhook_url'])
6744 ? esc_url($this->options['live_agent_webhook_url'])
6745 : esc_url(get_option('live_agent_webhook_url', ''));
6746
6747 printf(
6748 '<input type="password" id="live_agent_webhook_url" name="live_agent_webhook_url" value="%s" class="regular-text" />',
6749 $webhook_url
6750 );
6751 echo '<button type="button" id="toggleWebhookUrlVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6752 echo '<p class="description">' . esc_html__('Enter your Slack webhook URL for live agent notifications.', 'mxchat') . '</p>';
6753 }
6754
6755 public function mxchat_live_agent_secret_key_callback() {
6756 printf(
6757 '<input type="password" id="live_agent_secret_key" name="live_agent_secret_key" value="%s" class="regular-text" />',
6758 isset($this->options['live_agent_secret_key']) ? esc_attr($this->options['live_agent_secret_key']) : ''
6759 );
6760 echo '<button type="button" id="toggleSecretKeyVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6761 echo '<p class="description">' . esc_html__('Secret key for validating Slack requests. Keep this secure.', 'mxchat') . '</p>';
6762 }
6763
6764 public function mxchat_live_agent_bot_token_callback() {
6765 printf(
6766 '<input type="password" id="live_agent_bot_token" name="live_agent_bot_token" value="%s" class="regular-text" />',
6767 isset($this->options['live_agent_bot_token']) ? esc_attr($this->options['live_agent_bot_token']) : ''
6768 );
6769 echo '<button type="button" id="toggleBotTokenVisibility">' . esc_html__('Show', 'mxchat') . '</button>';
6770 echo '<p class="description">' . esc_html__('Your Slack Bot OAuth Token (starts with xoxb-). Keep this secure.', 'mxchat') . '</p>';
6771 }
6772
6773 public function mxchat_live_agent_user_ids_callback() {
6774 $user_ids = isset($this->options['live_agent_user_ids'])
6775 ? esc_textarea($this->options['live_agent_user_ids'])
6776 : '';
6777
6778 printf(
6779 '<textarea id="live_agent_user_ids" name="live_agent_user_ids" rows="4" class="large-text">%s</textarea>',
6780 $user_ids
6781 );
6782 echo '<p class="description">' . esc_html__('Enter Slack User IDs of agents who should be automatically invited to chat channels (one per line). Find user IDs in Slack profiles under "More" → "Copy member ID". Example: U1234567890', 'mxchat') . '</p>';
6783 }
6784
6785 /**
6786 * Telegram Integration Callbacks
6787 */
6788 public function mxchat_telegram_section_callback() {
6789 echo '<p>' . esc_html__('Configure Telegram integration for live agent support.', 'mxchat') . '</p>';
6790 }
6791
6792 public function mxchat_telegram_status_callback() {
6793 $fresh_options = get_option('mxchat_options');
6794 $status = isset($fresh_options['telegram_status']) ? $fresh_options['telegram_status'] : 'off';
6795
6796 echo '<label class="toggle-switch">';
6797 echo sprintf(
6798 '<input type="checkbox" id="telegram_status" name="telegram_status" value="on" %s />',
6799 checked($status, 'on', false)
6800 );
6801 echo '<span class="slider"></span>';
6802 echo '</label>';
6803 echo '<label for="telegram_status" class="mxchat-status-label">';
6804 echo '<span class="status-text">' . ($status === 'on' ? esc_html__('Online', 'mxchat') : esc_html__('Offline', 'mxchat')) . '</span>';
6805 echo '</label>';
6806 }
6807
6808 public function mxchat_telegram_notification_message_callback() {
6809 $message = isset($this->options['telegram_notification_message'])
6810 ? $this->options['telegram_notification_message']
6811 : __("I've notified a support agent. Please allow a moment for them to respond.", 'mxchat');
6812
6813 printf(
6814 '<textarea id="telegram_notification_message" name="telegram_notification_message" rows="3" cols="50">%s</textarea>',
6815 esc_textarea($message)
6816 );
6817 echo '<p class="description">' . esc_html__('Message shown when live transfer activated.', 'mxchat') . '</p>';
6818 }
6819
6820 public function mxchat_telegram_away_message_callback() {
6821 $message = isset($this->options['telegram_away_message'])
6822 ? $this->options['telegram_away_message']
6823 : __('Sorry, live agents are currently unavailable. I can continue helping you as an AI assistant.', 'mxchat');
6824
6825 printf(
6826 '<textarea id="telegram_away_message" name="telegram_away_message" rows="3" cols="50">%s</textarea>',
6827 esc_textarea($message)
6828 );
6829 echo '<p class="description">' . esc_html__('Message shown when live agents are offline.', 'mxchat') . '</p>';
6830 }
6831
6832 public function mxchat_telegram_bot_token_callback() {
6833 printf(
6834 '<input type="password" id="telegram_bot_token" name="telegram_bot_token" value="%s" class="regular-text" />',
6835 isset($this->options['telegram_bot_token']) ? esc_attr($this->options['telegram_bot_token']) : ''
6836 );
6837 echo '<button type="button" class="button button-secondary" onclick="var f=document.getElementById(\'telegram_bot_token\'); if(f.type===\'password\'){f.type=\'text\';this.textContent=\'' . esc_js(__('Hide', 'mxchat')) . '\';}else{f.type=\'password\';this.textContent=\'' . esc_js(__('Show', 'mxchat')) . '\';}">' . esc_html__('Show', 'mxchat') . '</button>';
6838 echo '<p class="description">' . esc_html__('Your Telegram Bot Token from @BotFather (e.g., 123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ).', 'mxchat') . '</p>';
6839 }
6840
6841 public function mxchat_telegram_group_id_callback() {
6842 printf(
6843 '<input type="text" id="telegram_group_id" name="telegram_group_id" value="%s" class="regular-text" />',
6844 isset($this->options['telegram_group_id']) ? esc_attr($this->options['telegram_group_id']) : ''
6845 );
6846 echo '<p class="description">' . esc_html__('Your Telegram supergroup ID (starts with -100). The group must have forum topics enabled.', 'mxchat') . '</p>';
6847 }
6848
6849 public function mxchat_telegram_webhook_secret_callback() {
6850 $secret = isset($this->options['telegram_webhook_secret']) ? $this->options['telegram_webhook_secret'] : '';
6851
6852 // Auto-generate secret if empty
6853 if (empty($secret)) {
6854 $secret = wp_generate_password(64, false, false);
6855 $options = get_option('mxchat_options', []);
6856 $options['telegram_webhook_secret'] = $secret;
6857 update_option('mxchat_options', $options);
6858 $this->options['telegram_webhook_secret'] = $secret;
6859 }
6860
6861 printf(
6862 '<input type="password" id="telegram_webhook_secret" name="telegram_webhook_secret" value="%s" class="regular-text" />',
6863 esc_attr($secret)
6864 );
6865 echo '<button type="button" class="button button-secondary" onclick="var f=document.getElementById(\'telegram_webhook_secret\'); if(f.type===\'password\'){f.type=\'text\';this.textContent=\'' . esc_js(__('Hide', 'mxchat')) . '\';}else{f.type=\'password\';this.textContent=\'' . esc_js(__('Show', 'mxchat')) . '\';}">' . esc_html__('Show', 'mxchat') . '</button>';
6866 echo '<p class="description">' . esc_html__('Secret token for webhook verification. Use this when setting up your Telegram webhook with the secret_token parameter.', 'mxchat') . '</p>';
6867 }
6868
6869 public function mxchat_similarity_threshold_callback() {
6870 // Load from mxchat_options array
6871 $options = get_option('mxchat_options', []);
6872
6873 // Get value from options array with default of 35
6874 $threshold = isset($options['similarity_threshold']) ? $options['similarity_threshold'] : 35;
6875
6876 echo '<div class="slider-container">';
6877 echo sprintf(
6878 '<input type="range"
6879 id="similarity_threshold"
6880 name="similarity_threshold"
6881 min="20"
6882 max="85"
6883 step="1"
6884 value="%s"
6885 class="range-slider" />',
6886 esc_attr($threshold)
6887 );
6888 echo sprintf(
6889 '<span id="threshold_value" class="range-value">%s</span>',
6890 esc_html($threshold)
6891 );
6892 echo '</div>';
6893 }
6894
6895 public function mxchat_rag_sources_limit_callback() {
6896 // Load from mxchat_options array
6897 $options = get_option('mxchat_options', []);
6898
6899 // Get value from options array with default of 6
6900 $rag_sources_limit = isset($options['rag_sources_limit']) ? intval($options['rag_sources_limit']) : 6;
6901
6902 echo '<div class="slider-container">';
6903 echo sprintf(
6904 '<input type="range"
6905 id="rag_sources_limit"
6906 name="rag_sources_limit"
6907 min="3"
6908 max="10"
6909 step="1"
6910 value="%s"
6911 class="range-slider" />',
6912 esc_attr($rag_sources_limit)
6913 );
6914 echo sprintf(
6915 '<span id="rag_sources_limit_value" class="range-value">%s</span>',
6916 esc_html($rag_sources_limit)
6917 );
6918 echo '</div>';
6919 }
6920
6921 public function mxchat_enqueue_admin_assets() {
6922 // Get plugin version
6923 $version = MXCHAT_VERSION;
6924
6925 // Use file modification time for development (remove in production)
6926 if (defined('WP_DEBUG') && WP_DEBUG) {
6927 $version = filemtime(plugin_dir_path(__FILE__) . '../mxchat-basic.php');
6928 }
6929
6930 $current_page = isset($_GET['page']) ? $_GET['page'] : '';
6931 $plugin_url = plugin_dir_url(__FILE__) . '../';
6932
6933 // Only load on MxChat pages
6934 if (strpos($current_page, 'mxchat') === false) {
6935 return;
6936 }
6937
6938 // Always load these on all MxChat pages
6939 $this->enqueue_core_admin_assets($plugin_url, $version);
6940 $this->enqueue_page_specific_assets($current_page, $plugin_url, $version);
6941 $this->localize_admin_scripts($current_page);
6942 }
6943 private function enqueue_core_admin_assets($plugin_url, $version) {
6944 // Core admin styles
6945 wp_enqueue_style('mxchat-admin-css', $plugin_url . 'css/admin-style.css', array(), $version);
6946 wp_enqueue_style('mxchat-knowledge-css', $plugin_url . 'css/knowledge-style.css', array(), $version);
6947
6948 // New sidebar navigation styles (for main settings page)
6949 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
6950
6951 // Core admin scripts
6952 wp_enqueue_script('mxchat-admin-js', $plugin_url . 'js/mxchat-admin.js', array('jquery'), $version, true);
6953 }
6954 private function enqueue_page_specific_assets($current_page, $plugin_url, $version) {
6955 switch ($current_page) {
6956 case 'mxchat-prompts':
6957 // Knowledge processing page assets
6958 wp_enqueue_style('mxchat-content-selector-css', $plugin_url . 'css/content-selector.css', array(), $version);
6959 wp_enqueue_script('mxchat-content-selector-js', $plugin_url . 'js/content-selector.js', array('jquery'), $version, true);
6960 // Add the knowledge processing script (common script needed for WordPress dismiss functionality)
6961 wp_enqueue_script('mxchat-knowledge-processing', $plugin_url . 'js/knowledge-processing.js', array('jquery', 'common'), $version, true);
6962 break;
6963
6964 case 'mxchat-transcripts':
6965 // Load admin sidebar CSS first (shared styles for sidebar navigation)
6966 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
6967 // Load transcripts-specific styles
6968 wp_enqueue_style('mxchat-chat-transcripts-css', $plugin_url . 'css/chat-transcripts.css', array('mxchat-admin-sidebar-css'), $version);
6969 wp_enqueue_script('mxchat-transcripts-js', $plugin_url . 'js/mxchat_transcripts.js', array('jquery'), $version, true);
6970 break;
6971
6972 case 'mxchat-actions':
6973 // Load admin sidebar CSS first (shared styles for sidebar navigation)
6974 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
6975 // Load actions-specific styles
6976 wp_enqueue_style('mxchat-actions-css', $plugin_url . 'css/actions.css', array('mxchat-admin-sidebar-css'), $version);
6977 wp_enqueue_script('mxchat-actions-js', $plugin_url . 'js/mxchat_actions.js', array('jquery'), $version, true);
6978
6979 // Localize script data for actions page
6980 $is_activated = get_option('mxchat_pro_license_status') === 'active';
6981 wp_localize_script('mxchat-actions-js', 'mxchActionsData', array(
6982 'ajaxUrl' => admin_url('admin-ajax.php'),
6983 'nonce' => wp_create_nonce('mxchat_actions_nonce'),
6984 'addNonce' => wp_create_nonce('mxchat_add_intent_nonce'),
6985 'editNonce' => wp_create_nonce('mxchat_edit_intent'),
6986 'deleteNonce' => wp_create_nonce('mxchat_delete_intent_nonce'),
6987 'toggleNonce' => wp_create_nonce('mxchat_actions_nonce'),
6988 'isActivated' => $is_activated,
6989 'i18n' => array(
6990 'confirmDelete' => __('Are you sure you want to delete this action?', 'mxchat'),
6991 'confirmBulkDelete' => __('Are you sure you want to delete the selected actions?', 'mxchat'),
6992 'saving' => __('Saving...', 'mxchat'),
6993 'saved' => __('Saved successfully!', 'mxchat'),
6994 'error' => __('An error occurred. Please try again.', 'mxchat'),
6995 'proRequired' => __('This feature requires MxChat Pro.', 'mxchat'),
6996 'addonRequired' => __('This feature requires an add-on.', 'mxchat')
6997 )
6998 ));
6999 break;
7000
7001 case 'mxchat-activation':
7002 // Load admin sidebar CSS first (shared styles for sidebar navigation)
7003 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7004 // Load pro page-specific styles
7005 wp_enqueue_style('mxchat-pro-css', $plugin_url . 'css/admin-pro.css', array('mxchat-admin-sidebar-css'), $version);
7006 // Load pro page JavaScript
7007 wp_enqueue_script('mxchat-pro-js', $plugin_url . 'js/mxchat_pro.js', array('jquery'), $version, true);
7008 // Load activation script for license activation/deactivation
7009 wp_enqueue_script('mxchat-activation-js', $plugin_url . 'js/activation-script.js', array('jquery'), $version, true);
7010 break;
7011
7012 case 'mxchat-content':
7013 // Load admin sidebar CSS (shared styles for sidebar navigation)
7014 wp_enqueue_style('mxchat-admin-sidebar-css', $plugin_url . 'css/admin-sidebar.css', array(), $version);
7015 // Load content page-specific styles
7016 wp_enqueue_style('mxchat-content-css', $plugin_url . 'css/admin-content.css', array('mxchat-admin-sidebar-css'), $version);
7017 // Load content page JavaScript
7018 wp_enqueue_script('mxchat-content-js', $plugin_url . 'js/mxchat-content.js', array('jquery'), $version, true);
7019 break;
7020 default:
7021 wp_enqueue_script(
7022 'mxchat-test-streaming-js',
7023 $plugin_url . 'js/mxchat-test-streaming.js',
7024 ['jquery'],
7025 $version,
7026 true
7027 );
7028
7029 wp_localize_script('mxchat-test-streaming-js', 'mxchatTestStreamingAjax', [
7030 'ajax_url' => admin_url('admin-ajax.php'),
7031 'nonce' => wp_create_nonce('mxchat_test_streaming_nonce'),
7032 'settings_nonce' => wp_create_nonce('mxchat_save_setting_nonce')
7033 ]);
7034 break;
7035 }
7036 }
7037 private function localize_admin_scripts($current_page) {
7038 // Base localization data for main admin script
7039 $base_data = array(
7040 'ajax_url' => admin_url('admin-ajax.php'),
7041 'nonce' => wp_create_nonce('mxchat_admin_nonce'),
7042 'admin_url' => admin_url(),
7043 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce'),
7044 'inline_edit_nonce' => wp_create_nonce('mxchat_save_inline_nonce'),
7045 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7046 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7047 'actions_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7048 'add_intent_nonce' => wp_create_nonce('mxchat_add_intent_nonce'),
7049 'edit_intent_nonce' => wp_create_nonce('mxchat_edit_intent'),
7050 'toggle_action_nonce' => wp_create_nonce('mxchat_actions_nonce'),
7051 'fetch_openrouter_models_nonce' => wp_create_nonce('mxchat_fetch_openrouter_models'),
7052 'is_activated' => $this->is_activated ? '1' : '0',
7053 'status_refresh_interval' => 5000,
7054 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7055 'ajaxurl' => admin_url('admin-ajax.php')
7056 );
7057
7058 // Localize main admin script with base data
7059 wp_localize_script('mxchat-admin-js', 'mxchatAdmin', $base_data);
7060
7061 // Page-specific localizations
7062 $this->localize_page_specific_scripts($current_page);
7063 }
7064 private function localize_page_specific_scripts($current_page) {
7065 switch ($current_page) {
7066 case 'mxchat-prompts':
7067 // Status updater localization
7068 wp_localize_script('mxchat-status-updater', 'mxchat_status_data', array(
7069 'ajax_url' => admin_url('admin-ajax.php'),
7070 'nonce' => wp_create_nonce('mxchat_status_nonce')
7071 ));
7072
7073 // Content selector localization
7074 wp_localize_script('mxchat-content-selector-js', 'mxchatSelector', array(
7075 'ajaxurl' => admin_url('admin-ajax.php'),
7076 'nonce' => wp_create_nonce('mxchat_content_selector_nonce'),
7077 'i18n' => array(
7078 'searchPlaceholder' => __('Search posts and pages...', 'mxchat'),
7079 'selectAll' => __('Select All', 'mxchat'),
7080 'process' => __('Process Selected', 'mxchat'),
7081 'cancel' => __('Cancel', 'mxchat'),
7082 'noResults' => __('No content found.', 'mxchat')
7083 )
7084 ));
7085
7086 wp_localize_script('mxchat-knowledge-processing', 'mxchatAdmin', array(
7087 'ajax_url' => admin_url('admin-ajax.php'),
7088 'status_nonce' => wp_create_nonce('mxchat_status_nonce'),
7089 'queue_nonce' => wp_create_nonce('mxchat_queue_nonce'),
7090 'stop_nonce' => wp_create_nonce('mxchat_stop_processing_action'),
7091 'settings_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7092 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7093 'entries_nonce' => wp_create_nonce('mxchat_entries_nonce'),
7094 'admin_url' => admin_url(),
7095 'ajaxurl' => admin_url('admin-ajax.php'),
7096 'status_refresh_interval' => 2000,
7097 'bot_id' => isset($_GET['bot_id']) ? sanitize_text_field($_GET['bot_id']) : 'default'
7098 ));
7099 break;
7100
7101 case 'mxchat-activation':
7102 wp_localize_script('mxchat-activation-js', 'mxchatAdmin', array(
7103 'ajax_url' => admin_url('admin-ajax.php'),
7104 'license_nonce' => wp_create_nonce('mxchat_activate_license_nonce')
7105 ));
7106 break;
7107
7108 case 'mxchat-content':
7109 wp_localize_script('mxchat-content-js', 'mxchatContent', array(
7110 'ajaxUrl' => admin_url('admin-ajax.php'),
7111 'nonce' => wp_create_nonce('mxchat_content_nonce'),
7112 'settingNonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7113 'previewUrl' => home_url('/?p='),
7114 'isActivated' => $this->is_activated(),
7115 'hasAdvancedContent' => apply_filters('mxchat_content_pro_feature', false, 'seo_readability'),
7116 'hasGSC' => apply_filters('mxchat_content_pro_feature', false, 'gsc_integration'),
7117 'seoOptimize' => array(
7118 'meta_description' => ($options['seo_optimize_meta_desc'] ?? 'on') === 'on',
7119 'seo_title' => ($options['seo_optimize_seo_title'] ?? 'on') === 'on',
7120 'slug' => ($options['seo_optimize_slug'] ?? 'on') === 'on',
7121 'readability' => ($options['seo_optimize_readability'] ?? 'on') === 'on',
7122 'internal_links' => ($options['seo_optimize_internal_links'] ?? 'on') === 'on',
7123 'img_alt' => ($options['seo_optimize_img_alt'] ?? 'on') === 'on',
7124 'featured_img' => ($options['seo_optimize_featured_img'] ?? 'on') === 'on',
7125 ),
7126 'i18n' => array(
7127 'generating' => __('Generating...', 'mxchat'),
7128 'planning' => __('Planning content structure...', 'mxchat'),
7129 'images' => __('Generating images...', 'mxchat'),
7130 'writing' => __('Writing full content...', 'mxchat'),
7131 'creating' => __('Creating WordPress post...', 'mxchat'),
7132 'done' => __('Content generated successfully!', 'mxchat'),
7133 'error' => __('An error occurred. Please try again.', 'mxchat'),
7134 'editSuccess' => __('Content updated successfully.', 'mxchat'),
7135 'promptEmpty' => __('Please enter a prompt.', 'mxchat'),
7136 )
7137 ));
7138 break;
7139
7140 case 'mxchat-transcripts':
7141 // Get chart data for localization
7142 $chart_data = $this->get_transcripts_chart_data();
7143
7144 wp_localize_script('mxchat-transcripts-js', 'mxchatAdmin', array(
7145 'ajax_url' => admin_url('admin-ajax.php'),
7146 'export_nonce' => wp_create_nonce('mxchat_export_transcripts'),
7147 'delete_nonce' => wp_create_nonce('mxchat_delete_chat_history'),
7148 'setting_nonce' => wp_create_nonce('mxchat_save_setting_nonce'),
7149 'translate_nonce' => wp_create_nonce('mxchat_translate_messages')
7150 ));
7151
7152 // Localize chart data separately - use array_values to ensure proper JSON array encoding
7153 wp_localize_script('mxchat-transcripts-js', 'mxchatChartData', array(
7154 'labels' => array_values($chart_data['labels']),
7155 'chats' => array_values($chart_data['chats']),
7156 'messages' => array_values($chart_data['messages'])
7157 ));
7158 break;
7159
7160 case 'mxchat-settings':
7161 default:
7162 // Color picker and settings localization
7163 wp_localize_script('mxchat-color-picker', 'mxchatStyleSettings', array(
7164 'ajax_url' => admin_url('admin-ajax.php'),
7165 'link_target_toggle' => $this->options['link_target_toggle'] ?? 'off',
7166 'user_message_bg_color' => $this->options['user_message_bg_color'] ?? '#fff',
7167 'user_message_font_color' => $this->options['user_message_font_color'] ?? '#212121',
7168 'bot_message_bg_color' => $this->options['bot_message_bg_color'] ?? '#212121',
7169 'bot_message_font_color' => $this->options['bot_message_font_color'] ?? '#fff',
7170 'top_bar_bg_color' => $this->options['top_bar_bg_color'] ?? '#212121',
7171 'send_button_font_color' => $this->options['send_button_font_color'] ?? '#212121',
7172 'close_button_color' => $this->options['close_button_color'] ?? '#fff',
7173 'chatbot_background_color' => $this->options['chatbot_background_color'] ?? '#212121',
7174 'icon_color' => $this->options['icon_color'] ?? '#fff',
7175 'chat_input_font_color' => $this->options['chat_input_font_color'] ?? '#212121',
7176 'pre_chat_message' => $this->options['pre_chat_message'] ?? esc_html__('Hey there! Ask me anything!', 'mxchat'),
7177 'rate_limit_message' => $this->options['rate_limit_message'] ?? esc_html__('Rate limit exceeded. Please try again later.', 'mxchat'),
7178 'loops_api_key' => $this->options['loops_api_key'] ?? '',
7179 'loops_mailing_list' => $this->options['loops_mailing_list'] ?? '',
7180 'triggered_phrase_response' => $this->options['triggered_phrase_response'] ?? esc_html__('Would you like to join our mailing list? Please provide your email below.', 'mxchat'),
7181 'email_capture_response' => $this->options['email_capture_response'] ?? esc_html__('Thank you for providing your email! You\'ve been added to our list.', 'mxchat'),
7182 'pdf_intent_trigger_text' => $this->options['pdf_intent_trigger_text'] ?? esc_html__("Please provide the URL to the PDF you'd like to discuss.", 'mxchat'),
7183 'pdf_intent_success_text' => $this->options['pdf_intent_success_text'] ?? esc_html__("I've processed the PDF. What questions do you have about it?", 'mxchat'),
7184 'pdf_intent_error_text' => $this->options['pdf_intent_error_text'] ?? esc_html__("Sorry, I couldn't process the PDF. Please ensure it's a valid file.", 'mxchat'),
7185 'pdf_max_pages' => $this->options['pdf_max_pages'] ?? 69,
7186 'live_agent_webhook_url' => $this->options['live_agent_webhook_url'] ?? '',
7187 'live_agent_secret_key' => $this->options['live_agent_secret_key'] ?? '',
7188 'live_agent_bot_token' => $this->options['live_agent_bot_token'] ?? '',
7189 'live_agent_message_bg_color' => $this->options['live_agent_message_bg_color'] ?? '#ffffff',
7190 'live_agent_message_font_color' => $this->options['live_agent_message_font_color'] ?? '#333333',
7191 'chat_toolbar_toggle' => $this->options['chat_toolbar_toggle'] ?? 'off',
7192 'show_pdf_upload_button' => $this->options['show_pdf_upload_button'] ?? 'on',
7193 'show_word_upload_button' => $this->options['show_word_upload_button'] ?? 'on',
7194 'mode_indicator_bg_color' => $this->options['mode_indicator_bg_color'] ?? '#767676',
7195 'mode_indicator_font_color' => $this->options['mode_indicator_font_color'] ?? '#ffffff',
7196 'toolbar_icon_color' => $this->options['toolbar_icon_color'] ?? '#212121',
7197 ));
7198 break;
7199 }
7200
7201 // Additional localization that was in the original code
7202 wp_localize_script('mxchat-admin-js', 'mxchatPromptsAdmin', array(
7203 'ajax_url' => admin_url('admin-ajax.php'),
7204 'prompts_setting_nonce' => wp_create_nonce('mxchat_prompts_setting_nonce'),
7205 ));
7206 }
7207
7208 public function mxchat_sanitize($input) {
7209 $new_input = array();
7210
7211 if (isset($input['api_key'])) {
7212 $new_input['api_key'] = sanitize_text_field($input['api_key']);
7213 }
7214
7215 if (isset($input['similarity_threshold'])) {
7216 $new_input['similarity_threshold'] = absint($input['similarity_threshold']); // Ensure it's an integer
7217 $new_input['similarity_threshold'] = min(max($new_input['similarity_threshold'], 20), 85); // Enforce range
7218 }
7219
7220 if (isset($input['rag_sources_limit'])) {
7221 $new_input['rag_sources_limit'] = absint($input['rag_sources_limit']); // Ensure it's an integer
7222 $new_input['rag_sources_limit'] = min(max($new_input['rag_sources_limit'], 3), 10); // Enforce range 3-10
7223 }
7224
7225 if (isset($input['xai_api_key'])) {
7226 $new_input['xai_api_key'] = sanitize_text_field($input['xai_api_key']);
7227 }
7228
7229 if (isset($input['claude_api_key'])) {
7230 $new_input['claude_api_key'] = sanitize_text_field($input['claude_api_key']);
7231 }
7232
7233 if (isset($input['enable_streaming_toggle'])) {
7234 $new_input['enable_streaming_toggle'] = ($input['enable_streaming_toggle'] === 'on') ? 'on' : 'off';
7235 } else {
7236 // If checkbox not checked, it won't be in $input, so set to 'off'
7237 $new_input['enable_streaming_toggle'] = 'off';
7238 }
7239
7240 if (isset($input['enable_web_search'])) {
7241 $new_input['enable_web_search'] = ($input['enable_web_search'] === 'on') ? 'on' : 'off';
7242 } else {
7243 // If checkbox not checked, it won't be in $input, so set to 'off'
7244 $new_input['enable_web_search'] = 'off';
7245 }
7246
7247 if (isset($input['deepseek_api_key'])) {
7248 $new_input['deepseek_api_key'] = sanitize_text_field($input['deepseek_api_key']);
7249 }
7250
7251 if (isset($input['gemini_api_key'])) {
7252 $new_input['gemini_api_key'] = sanitize_text_field($input['gemini_api_key']);
7253 }
7254
7255 if (isset($input['enable_woocommerce_integration'])) {
7256 $new_input['enable_woocommerce_integration'] = $input['enable_woocommerce_integration'] === 'on' ? 'on' : 'off';
7257 }
7258
7259 if (isset($input['privacy_toggle'])) {
7260 $new_input['privacy_toggle'] = $input['privacy_toggle'];
7261 }
7262
7263 if (isset($input['complianz_toggle'])) {
7264 $new_input['complianz_toggle'] = $input['complianz_toggle'];
7265 }
7266
7267 // Handle custom privacy text input
7268 if (isset($input['privacy_text'])) {
7269 // Allow basic HTML for links
7270 $new_input['privacy_text'] = wp_kses_post($input['privacy_text']);
7271 }
7272
7273 if (isset($input['system_prompt_instructions'])) {
7274 $new_input['system_prompt_instructions'] = sanitize_textarea_field($input['system_prompt_instructions']);
7275 }
7276
7277 if (isset($input['mxchat_pro_email'])) {
7278 $new_input['mxchat_pro_email'] = sanitize_email($input['mxchat_pro_email']);
7279 }
7280
7281 if (isset($input['mxchat_activation_key'])) {
7282 $new_input['mxchat_activation_key'] = sanitize_text_field($input['mxchat_activation_key']);
7283 }
7284
7285 if (isset($input['append_to_body'])) {
7286 $new_input['append_to_body'] = $input['append_to_body'] === 'on' ? 'on' : 'off';
7287 }
7288
7289 // Post type visibility settings
7290 if (isset($input['post_type_visibility_mode'])) {
7291 $allowed_modes = array('all', 'include', 'exclude');
7292 $new_input['post_type_visibility_mode'] = in_array($input['post_type_visibility_mode'], $allowed_modes)
7293 ? $input['post_type_visibility_mode']
7294 : 'all';
7295 }
7296
7297 if (isset($input['post_type_visibility_list'])) {
7298 if (is_array($input['post_type_visibility_list'])) {
7299 $new_input['post_type_visibility_list'] = array_map('sanitize_key', $input['post_type_visibility_list']);
7300 } else {
7301 $new_input['post_type_visibility_list'] = array();
7302 }
7303 }
7304
7305 if (isset($input['contextual_awareness_toggle'])) {
7306 $new_input['contextual_awareness_toggle'] = $input['contextual_awareness_toggle'] === 'on' ? 'on' : 'off';
7307 }
7308
7309 if (isset($input['show_frontend_debugger'])) {
7310 $new_input['show_frontend_debugger'] = $input['show_frontend_debugger'] === 'on' ? 'on' : 'off';
7311 }
7312
7313 if (isset($input['citation_links_toggle'])) {
7314 $new_input['citation_links_toggle'] = $input['citation_links_toggle'] === 'on' ? 'on' : 'off';
7315 }
7316
7317 if (isset($input['top_bar_title'])) {
7318 $new_input['top_bar_title'] = sanitize_text_field($input['top_bar_title']);
7319 }
7320
7321 if (isset($input['ai_agent_text'])) {
7322 $new_input['ai_agent_text'] = sanitize_text_field($input['ai_agent_text']);
7323 }
7324
7325 if (isset($input['enable_email_block'])) {
7326 $new_input['enable_email_block'] = sanitize_text_field($input['enable_email_block']);
7327 }
7328
7329 if (isset($input['email_blocker_header_content'])) {
7330 // wp_kses_post() allows standard HTML tags permitted by WordPress
7331 $new_input['email_blocker_header_content'] = wp_kses_post($input['email_blocker_header_content']);
7332 }
7333 if (isset($input['email_blocker_button_text'])) {
7334 $new_input['email_blocker_button_text'] = sanitize_text_field($input['email_blocker_button_text']);
7335 }
7336 // Sanitize name field toggle
7337 if (isset($input['enable_name_field'])) {
7338 $new_input['enable_name_field'] = ($input['enable_name_field'] === 'on') ? 'on' : 'off';
7339 } else {
7340 $new_input['enable_name_field'] = 'off';
7341 }
7342 // Sanitize name field placeholder
7343 if (isset($input['name_field_placeholder'])) {
7344 $new_input['name_field_placeholder'] = sanitize_text_field($input['name_field_placeholder']);
7345 }
7346 if (isset($input['intro_message'])) {
7347 $new_input['intro_message'] = wp_kses_post($input['intro_message']); // Use wp_kses_post instead
7348 }
7349
7350 if (isset($input['input_copy'])) {
7351 $new_input['input_copy'] = sanitize_text_field($input['input_copy']);
7352 }
7353
7354 if (isset($input['rate_limit_message'])) {
7355 $new_input['rate_limit_message'] = sanitize_text_field($input['rate_limit_message']);
7356 }
7357
7358 // Handle the new rate limits format
7359 if (isset($input['rate_limits']) && is_array($input['rate_limits'])) {
7360 $new_input['rate_limits'] = array();
7361 $allowed_limits = array('1', '3', '5', '10', '15', '20', '50', '100', 'unlimited');
7362 $allowed_timeframes = array('hourly', 'daily', 'weekly', 'monthly');
7363
7364 foreach ($input['rate_limits'] as $role_id => $settings) {
7365 $new_input['rate_limits'][$role_id] = array();
7366
7367 // Sanitize limit
7368 if (isset($settings['limit'])) {
7369 $limit = sanitize_text_field($settings['limit']);
7370 if (in_array($limit, $allowed_limits, true)) {
7371 $new_input['rate_limits'][$role_id]['limit'] = $limit;
7372 } else {
7373 $new_input['rate_limits'][$role_id]['limit'] = ($role_id === 'logged_out') ? '10' : '100'; // Default
7374 }
7375 }
7376
7377 // Sanitize timeframe
7378 if (isset($settings['timeframe'])) {
7379 $timeframe = sanitize_text_field($settings['timeframe']);
7380 if (in_array($timeframe, $allowed_timeframes, true)) {
7381 $new_input['rate_limits'][$role_id]['timeframe'] = $timeframe;
7382 } else {
7383 $new_input['rate_limits'][$role_id]['timeframe'] = 'daily'; // Default
7384 }
7385 }
7386
7387 // Sanitize message
7388 if (isset($settings['message'])) {
7389 $new_input['rate_limits'][$role_id]['message'] = sanitize_textarea_field($settings['message']);
7390 }
7391 }
7392 }
7393
7394 if (isset($input['pre_chat_message'])) {
7395 $new_input['pre_chat_message'] = sanitize_textarea_field($input['pre_chat_message']);
7396 }
7397
7398 if (isset($input['voyage_api_key'])) {
7399 $new_input['voyage_api_key'] = sanitize_text_field($input['voyage_api_key']);
7400 }
7401
7402 // Add to your sanitize function
7403 if (isset($input['embedding_model'])) {
7404 $allowed_models = array(
7405 'text-embedding-ada-002',
7406 'text-embedding-3-small',
7407 'text-embedding-3-large',
7408 'voyage-3-large',
7409 'gemini-embedding-001'
7410 );
7411 if (in_array($input['embedding_model'], $allowed_models)) {
7412 $new_input['embedding_model'] = sanitize_text_field($input['embedding_model']);
7413 }
7414 }
7415
7416 if (isset($input['model'])) {
7417 if ($input['model'] === 'openrouter') {
7418 $new_input['model'] = 'openrouter';
7419 } else {
7420 $allowed_models = array(
7421 'gemini-3-pro-preview',
7422 'gemini-3-flash-preview',
7423 'gemini-2.5-pro',
7424 'gemini-2.5-flash',
7425 'gemini-2.5-flash-lite',
7426 'gemini-2.0-flash',
7427 'gemini-2.0-flash-lite',
7428 'gemini-1.5-pro',
7429 'gemini-1.5-flash',
7430 'grok-4-0709',
7431 'grok-4-1-fast-reasoning',
7432 'grok-4-1-fast-non-reasoning',
7433 'grok-3-beta',
7434 'grok-3-fast-beta',
7435 'grok-3-mini-beta',
7436 'grok-3-mini-fast-beta',
7437 'grok-2',
7438 'deepseek-chat',
7439 'claude-opus-4-6',
7440 'claude-sonnet-4-6',
7441 'claude-opus-4-5',
7442 'claude-sonnet-4-5-20250929',
7443 'claude-opus-4-1-20250805',
7444 'claude-haiku-4-5-20251001',
7445 'claude-opus-4-20250514',
7446 'claude-sonnet-4-20250514',
7447 'gpt-5.2',
7448 'gpt-5.1-chat-latest',
7449 'gpt-5.1-2025-11-13',
7450 'gpt-5',
7451 'gpt-5-mini',
7452 'gpt-5-nano',
7453 );
7454
7455 if (in_array($input['model'], $allowed_models)) {
7456 $new_input['model'] = sanitize_text_field($input['model']);
7457 } else {
7458 // Fallback for any deprecated model
7459 $new_input['model'] = 'gpt-5.1-chat-latest';
7460 }
7461 }
7462 }
7463
7464 if (isset($input['openrouter_selected_model'])) {
7465 // Just sanitize it, don't validate against a whitelist
7466 $new_input['openrouter_selected_model'] = sanitize_text_field($input['openrouter_selected_model']);
7467 }
7468
7469 // ADD THIS:
7470 if (isset($input['openrouter_selected_model_name'])) {
7471 $new_input['openrouter_selected_model_name'] = sanitize_text_field($input['openrouter_selected_model_name']);
7472 }
7473
7474 if (isset($input['openrouter_api_key'])) {
7475 $new_input['openrouter_api_key'] = sanitize_text_field($input['openrouter_api_key']);
7476 }
7477
7478
7479
7480 if (isset($input['woocommerce_consumer_key'])) {
7481 $new_input['woocommerce_consumer_key'] = sanitize_text_field($input['woocommerce_consumer_key']);
7482 }
7483
7484 if (isset($input['woocommerce_consumer_secret'])) {
7485 $new_input['woocommerce_consumer_secret'] = sanitize_text_field($input['woocommerce_consumer_secret']);
7486 }
7487
7488
7489 // Sanitize link_target_toggle
7490 if (isset($input['link_target_toggle'])) {
7491 $new_input['link_target_toggle'] = $input['link_target_toggle'] === 'on' ? 'on' : 'off';
7492 }
7493
7494 // Sanitize Loops API Key
7495 if (isset($input['loops_api_key'])) {
7496 $new_input['loops_api_key'] = sanitize_text_field($input['loops_api_key']);
7497 }
7498
7499 if (isset($input['chat_persistence_toggle'])) {
7500 $new_input['chat_persistence_toggle'] = $input['chat_persistence_toggle'] === 'on' ? 'on' : 'off';
7501 }
7502
7503 if (isset($input['popular_question_1'])) {
7504 $new_input['popular_question_1'] = sanitize_text_field($input['popular_question_1']);
7505 }
7506
7507 if (isset($input['popular_question_2'])) {
7508 $new_input['popular_question_2'] = sanitize_text_field($input['popular_question_2']);
7509 }
7510
7511 if (isset($input['popular_question_3'])) {
7512 $new_input['popular_question_3'] = sanitize_text_field($input['popular_question_3']);
7513 }
7514
7515 if (isset($input['additional_popular_questions']) && is_array($input['additional_popular_questions'])) {
7516 $new_input['additional_popular_questions'] = array_map('sanitize_text_field', $input['additional_popular_questions']);
7517 }
7518
7519 // Sanitize Loops Mailing List
7520 if (isset($input['loops_mailing_list'])) {
7521 $new_input['loops_mailing_list'] = sanitize_text_field($input['loops_mailing_list']);
7522 }
7523
7524 // Sanitize Triggered Phrase Response
7525 if (isset($input['triggered_phrase_response'])) {
7526 $new_input['triggered_phrase_response'] = wp_kses_post($input['triggered_phrase_response']);
7527 }
7528 if (isset($input['email_capture_response'])) {
7529 $new_input['email_capture_response'] = wp_kses_post($input['email_capture_response']);
7530 }
7531
7532 // Sanitize Brave Search Settings
7533 if (isset($input['brave_api_key'])) {
7534 $new_input['brave_api_key'] = sanitize_text_field($input['brave_api_key']);
7535 }
7536
7537 if (isset($input['brave_image_count'])) {
7538 $image_count = intval($input['brave_image_count']);
7539 $new_input['brave_image_count'] = ($image_count >=1 && $image_count <=6) ? $image_count : 4;
7540 }
7541
7542 if (isset($input['brave_safe_search'])) {
7543 $allowed = array('strict', 'off');
7544 $new_input['brave_safe_search'] = in_array($input['brave_safe_search'], $allowed, true) ? $input['brave_safe_search'] : 'strict';
7545 }
7546
7547 if (isset($input['brave_news_count'])) {
7548 $news_count = intval($input['brave_news_count']);
7549 $new_input['brave_news_count'] = ($news_count >=1 && $news_count <=10) ? $news_count : 3;
7550 }
7551
7552 if (isset($input['brave_country'])) {
7553 $new_input['brave_country'] = sanitize_text_field($input['brave_country']);
7554 }
7555
7556 if (isset($input['brave_language'])) {
7557 $new_input['brave_language'] = sanitize_text_field($input['brave_language']);
7558 }
7559
7560 if (isset($input['chat_toolbar_toggle'])) {
7561 $new_input['chat_toolbar_toggle'] = $input['chat_toolbar_toggle'] === 'on' ? 'on' : 'off';
7562 }
7563
7564 // Sanitize PDF upload button toggle
7565 if (isset($input['show_pdf_upload_button'])) {
7566 $new_input['show_pdf_upload_button'] = $input['show_pdf_upload_button'] === 'on' ? 'on' : 'off';
7567 } else {
7568 $new_input['show_pdf_upload_button'] = 'off'; // If checkbox is unchecked
7569 }
7570
7571 // Sanitize Word upload button toggle
7572 if (isset($input['show_word_upload_button'])) {
7573 $new_input['show_word_upload_button'] = $input['show_word_upload_button'] === 'on' ? 'on' : 'off';
7574 } else {
7575 $new_input['show_word_upload_button'] = 'off'; // If checkbox is unchecked
7576 }
7577
7578 if (isset($input['pdf_intent_trigger_text'])) {
7579 $new_input['pdf_intent_trigger_text'] = sanitize_text_field($input['pdf_intent_trigger_text']);
7580 }
7581
7582 if (isset($input['pdf_intent_success_text'])) {
7583 $new_input['pdf_intent_success_text'] = sanitize_text_field($input['pdf_intent_success_text']);
7584 }
7585
7586 if (isset($input['pdf_intent_error_text'])) {
7587 $new_input['pdf_intent_error_text'] = sanitize_text_field($input['pdf_intent_error_text']);
7588 }
7589
7590 if (isset($input['pdf_max_pages'])) {
7591 $new_input['pdf_max_pages'] = intval($input['pdf_max_pages']);
7592 if ($new_input['pdf_max_pages'] < 1 || $new_input['pdf_max_pages'] > 69) {
7593 $new_input['pdf_max_pages'] = 69; // Default to 69 if out of range
7594 }
7595 }
7596
7597 if (isset($input['live_agent_webhook_url'])) {
7598 $new_input['live_agent_webhook_url'] = esc_url_raw($input['live_agent_webhook_url']);
7599 }
7600 if (isset($input['live_agent_secret_key'])) {
7601 $new_input['live_agent_secret_key'] = sanitize_text_field($input['live_agent_secret_key']);
7602 }
7603
7604 // Live Agent Integration
7605 if (isset($input['live_agent_bot_token'])) {
7606 $new_input['live_agent_bot_token'] = sanitize_text_field($input['live_agent_bot_token']);
7607 }
7608
7609 if (isset($input['live_agent_user_ids'])) {
7610 $new_input['live_agent_user_ids'] = sanitize_textarea_field($input['live_agent_user_ids']);
7611 }
7612
7613 if (isset($input['live_agent_status'])) {
7614 $new_input['live_agent_status'] = ($input['live_agent_status'] === 'on') ? 'on' : 'off';
7615 }
7616 if (isset($input['live_agent_away_message'])) {
7617 $new_input['live_agent_away_message'] = sanitize_textarea_field($input['live_agent_away_message']);
7618 }
7619 if (isset($input['live_agent_notification_message'])) {
7620 $new_input['live_agent_notification_message'] = sanitize_textarea_field($input['live_agent_notification_message']);
7621 }
7622
7623 // Telegram Integration
7624 if (isset($input['telegram_status'])) {
7625 $new_input['telegram_status'] = ($input['telegram_status'] === 'on') ? 'on' : 'off';
7626 }
7627 if (isset($input['telegram_bot_token'])) {
7628 $new_input['telegram_bot_token'] = sanitize_text_field($input['telegram_bot_token']);
7629 }
7630 if (isset($input['telegram_group_id'])) {
7631 $new_input['telegram_group_id'] = sanitize_text_field($input['telegram_group_id']);
7632 }
7633 if (isset($input['telegram_webhook_secret'])) {
7634 $new_input['telegram_webhook_secret'] = sanitize_text_field($input['telegram_webhook_secret']);
7635 }
7636 if (isset($input['telegram_notification_message'])) {
7637 $new_input['telegram_notification_message'] = sanitize_textarea_field($input['telegram_notification_message']);
7638 }
7639 if (isset($input['telegram_away_message'])) {
7640 $new_input['telegram_away_message'] = sanitize_textarea_field($input['telegram_away_message']);
7641 }
7642
7643 // Sanitize script loading strategy
7644 if (isset($input['script_loading_strategy'])) {
7645 $allowed_strategies = array('default', 'defer', 'delay_1s', 'delay_3s', 'delay_5s', 'on_interaction');
7646 $new_input['script_loading_strategy'] = in_array($input['script_loading_strategy'], $allowed_strategies)
7647 ? $input['script_loading_strategy']
7648 : 'default';
7649 }
7650
7651 // Sanitize debug mode
7652 if (isset($input['debug_mode'])) {
7653 $new_input['debug_mode'] = ($input['debug_mode'] === 'on') ? 'on' : 'off';
7654 }
7655
7656 // Content Generator Settings
7657 if (isset($input['content_model'])) {
7658 $new_input['content_model'] = sanitize_text_field($input['content_model']);
7659 }
7660 if (isset($input['content_image_model'])) {
7661 $new_input['content_image_model'] = sanitize_text_field($input['content_image_model']);
7662 }
7663 if (isset($input['content_enable_images'])) {
7664 $new_input['content_enable_images'] = ($input['content_enable_images'] === 'on') ? 'on' : 'off';
7665 }
7666 if (isset($input['content_use_placeholders'])) {
7667 $new_input['content_use_placeholders'] = ($input['content_use_placeholders'] === 'on') ? 'on' : 'off';
7668 }
7669 if (isset($input['content_internal_linking'])) {
7670 $new_input['content_internal_linking'] = ($input['content_internal_linking'] === 'on') ? 'on' : 'off';
7671 }
7672 if (isset($input['content_tool_use'])) {
7673 $new_input['content_tool_use'] = ($input['content_tool_use'] === 'on') ? 'on' : 'off';
7674 }
7675
7676 // SEO Optimize toggle fields
7677 foreach (array('seo_optimize_meta_desc', 'seo_optimize_seo_title', 'seo_optimize_slug', 'seo_optimize_readability', 'seo_optimize_internal_links', 'seo_optimize_img_alt', 'seo_optimize_featured_img') as $seo_key) {
7678 if (isset($input[$seo_key])) {
7679 $new_input[$seo_key] = ($input[$seo_key] === 'on') ? 'on' : 'off';
7680 }
7681 }
7682
7683 // Preserve content generator settings when saving from main settings page
7684 // (where content fields are not in the form submission)
7685 $existing = get_option('mxchat_options', array());
7686 foreach (array('content_model', 'content_image_model', 'content_enable_images', 'content_use_placeholders', 'content_internal_linking', 'content_tool_use', 'seo_optimize_meta_desc', 'seo_optimize_seo_title', 'seo_optimize_slug', 'seo_optimize_readability', 'seo_optimize_internal_links', 'seo_optimize_img_alt', 'seo_optimize_featured_img') as $key) {
7687 if (!isset($new_input[$key]) && isset($existing[$key])) {
7688 $new_input[$key] = $existing[$key];
7689 }
7690 }
7691
7692 return $new_input;
7693 }
7694
7695 /**
7696 * Log a debug message if debug mode is enabled
7697 *
7698 * @param string $type The type of log entry (settings_save, api_error, activation, etc.)
7699 * @param string $message The log message
7700 * @param array $data Optional additional data to log
7701 * @return bool Whether the message was logged
7702 */
7703 public static function mxchat_log_debug( $type, $message, $data = array() ) {
7704 $options = get_option( 'mxchat_options', array() );
7705
7706 // Check if debug mode is enabled
7707 if ( ! isset( $options['debug_mode'] ) || $options['debug_mode'] !== 'on' ) {
7708 return false;
7709 }
7710
7711 // Get current log
7712 $log = get_option( 'mxchat_debug_log', array() );
7713 if ( ! is_array( $log ) ) {
7714 $log = array();
7715 }
7716
7717 // Add new entry
7718 $entry = array(
7719 'time' => current_time( 'Y-m-d H:i:s' ),
7720 'type' => sanitize_key( $type ),
7721 'message' => sanitize_text_field( $message ),
7722 );
7723
7724 if ( ! empty( $data ) ) {
7725 $entry['data'] = $data;
7726 }
7727
7728 // Add to beginning of array (newest first)
7729 array_unshift( $log, $entry );
7730
7731 // Keep only last 100 entries
7732 if ( count( $log ) > 100 ) {
7733 $log = array_slice( $log, 0, 100 );
7734 }
7735
7736 // Save log
7737 update_option( 'mxchat_debug_log', $log, false );
7738
7739 return true;
7740 }
7741
7742 /**
7743 * Get the debug log entries
7744 *
7745 * @return array The debug log entries
7746 */
7747 public static function mxchat_get_debug_log() {
7748 $log = get_option( 'mxchat_debug_log', array() );
7749 return is_array( $log ) ? $log : array();
7750 }
7751
7752 /**
7753 * Clear the debug log
7754 *
7755 * @return bool Whether the log was cleared
7756 */
7757 public static function mxchat_clear_debug_log() {
7758 return delete_option( 'mxchat_debug_log' );
7759 }
7760
7761 /**
7762 * Export settings as JSON with masked API keys
7763 *
7764 * @return array The sanitized settings array
7765 */
7766 public static function mxchat_export_settings() {
7767 $options = get_option( 'mxchat_options', array() );
7768
7769 if ( ! is_array( $options ) ) {
7770 return array();
7771 }
7772
7773 // List of API key fields to mask
7774 $api_key_fields = array(
7775 'api_key',
7776 'xai_api_key',
7777 'claude_api_key',
7778 'deepseek_api_key',
7779 'voyage_api_key',
7780 'gemini_api_key',
7781 'openrouter_api_key',
7782 'loops_api_key',
7783 'brave_api_key',
7784 'live_agent_secret_key',
7785 'live_agent_bot_token',
7786 'telegram_bot_token',
7787 'telegram_webhook_secret',
7788 'woocommerce_consumer_key',
7789 'woocommerce_consumer_secret',
7790 );
7791
7792 // Mask API keys (show only last 4 characters)
7793 foreach ( $api_key_fields as $field ) {
7794 if ( isset( $options[ $field ] ) && ! empty( $options[ $field ] ) ) {
7795 $value = $options[ $field ];
7796 if ( strlen( $value ) > 4 ) {
7797 $options[ $field ] = str_repeat( '*', strlen( $value ) - 4 ) . substr( $value, -4 );
7798 } else {
7799 $options[ $field ] = '****';
7800 }
7801 }
7802 }
7803
7804 // Add metadata
7805 $export = array(
7806 'plugin_version' => defined( 'MXCHAT_VERSION' ) ? MXCHAT_VERSION : 'unknown',
7807 'export_date' => current_time( 'Y-m-d H:i:s' ),
7808 'wordpress_version' => get_bloginfo( 'version' ),
7809 'php_version' => phpversion(),
7810 'settings' => $options,
7811 );
7812
7813 return $export;
7814 }
7815
7816 /**
7817 * Reset all settings to defaults
7818 *
7819 * @return bool Whether the reset was successful
7820 */
7821 public static function mxchat_reset_all_settings() {
7822 // Delete main options
7823 $deleted = delete_option( 'mxchat_options' );
7824
7825 // Also clear the debug log
7826 delete_option( 'mxchat_debug_log' );
7827
7828 // Log the reset (will create new log since we just cleared it)
7829 // We need to temporarily enable debug mode to log this
7830 $temp_options = array( 'debug_mode' => 'on' );
7831 update_option( 'mxchat_options', $temp_options );
7832
7833 self::mxchat_log_debug( 'reset', 'All settings have been reset to defaults' );
7834
7835 // Now delete again to trigger re-initialization
7836 delete_option( 'mxchat_options' );
7837
7838 return $deleted;
7839 }
7840
7841 // Method to append the chatbot to the body
7842 public function mxchat_append_chatbot_to_body() {
7843 $options = get_option('mxchat_options');
7844 if (isset($options['append_to_body']) && $options['append_to_body'] === 'on') {
7845 echo do_shortcode('[mxchat_chatbot floating="yes"]');
7846 }
7847 }
7848
7849
7850
7851
7852
7853 private function mxchat_fetch_loops_mailing_lists($api_key) {
7854 $url = 'https://app.loops.so/api/v1/lists';
7855 $response = wp_remote_get($url, array(
7856 'headers' => array(
7857 'Authorization' => 'Bearer ' . $api_key,
7858 'Content-Type' => 'application/json'
7859 )
7860 ));
7861
7862 if (is_wp_error($response)) {
7863 return array();
7864 }
7865
7866 $body = wp_remote_retrieve_body($response);
7867 $lists = json_decode($body, true);
7868
7869 return isset($lists) && is_array($lists) ? $lists : array();
7870 }
7871
7872 function mxchat_calculate_cosine_similarity($vec1, $vec2) {
7873 if (empty($vec1) || empty($vec2)) {
7874 return 0.0;
7875 }
7876
7877 $dot_product = 0.0;
7878 $norm_a = 0.0;
7879 $norm_b = 0.0;
7880
7881 for ($i = 0; $i < count($vec1); $i++) {
7882 $dot_product += $vec1[$i] * $vec2[$i];
7883 $norm_a += pow($vec1[$i], 2);
7884 $norm_b += pow($vec2[$i], 2);
7885 }
7886
7887 if ($norm_a == 0.0 || $norm_b == 0.0) {
7888 return 0.0;
7889 } else {
7890 return $dot_product / (sqrt($norm_a) * sqrt($norm_b));
7891 }
7892 }
7893
7894 /**
7895 * Validates nonce and deletes all chat prompts
7896 */
7897 public function mxchat_handle_delete_all_prompts() {
7898 //error_log('=== DELETE ALL DEBUG START ===');
7899
7900 // Verify nonce
7901 if (!isset($_POST['mxchat_delete_all_prompts_nonce']) || !wp_verify_nonce($_POST['mxchat_delete_all_prompts_nonce'], 'mxchat_delete_all_prompts_action')) {
7902 //error_log('DEBUG: Nonce verification failed');
7903 wp_die(__('Nonce verification failed.', 'mxchat'));
7904 }
7905
7906 // Check permissions
7907 if (!current_user_can('manage_options')) {
7908 //error_log('DEBUG: Permission check failed');
7909 wp_die(__('You do not have sufficient permissions to delete all prompts.', 'mxchat'));
7910 }
7911
7912 // Get bot_id from POST data
7913 $bot_id = isset($_POST['bot_id']) ? sanitize_text_field($_POST['bot_id']) : 'default';
7914 //error_log('DEBUG: Bot ID from POST: ' . $bot_id);
7915
7916 $success = true;
7917 $error_messages = array();
7918
7919 // Get bot-specific Pinecone configuration
7920 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
7921 $pinecone_options = $pinecone_manager->mxchat_get_bot_pinecone_options($bot_id);
7922
7923 //error_log('DEBUG: Pinecone options retrieved:');
7924 //error_log(' - Use Pinecone: ' . ($pinecone_options['mxchat_use_pinecone'] ?? 'NOT SET'));
7925 //error_log(' - API Key: ' . (empty($pinecone_options['mxchat_pinecone_api_key']) ? 'EMPTY' : 'SET'));
7926 //error_log(' - Host: ' . ($pinecone_options['mxchat_pinecone_host'] ?? 'NOT SET'));
7927
7928 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
7929
7930 if ($use_pinecone && !empty($pinecone_options['mxchat_pinecone_api_key'])) {
7931 //error_log('[MXCHAT-DELETE] Pinecone is enabled for bot ' . $bot_id . ' - deleting all from Pinecone');
7932
7933 // Delete from bot-specific Pinecone index
7934 $result = $pinecone_manager->mxchat_delete_all_from_pinecone($pinecone_options);
7935
7936 //error_log('DEBUG: Delete all result: ' . ($result['success'] ? 'SUCCESS' : 'FAILED'));
7937 if (!$result['success']) {
7938 //error_log('DEBUG: Delete all error: ' . $result['message']);
7939 }
7940
7941 if (!$result['success']) {
7942 $success = false;
7943 $error_messages[] = $result['message'];
7944 }
7945
7946 // No cache clearing needed since we removed caching
7947
7948 } else {
7949 //error_log('[MXCHAT-DELETE] Pinecone not enabled for bot ' . $bot_id . ' - deleting from WordPress database');
7950
7951 // Delete from WordPress database
7952 global $wpdb;
7953 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
7954
7955 // If multi-bot is active and we have a specific bot, delete only that bot's content
7956 if ($bot_id !== 'default' && class_exists('MxChat_Multi_Bot_Manager')) {
7957 $result = $wpdb->delete(
7958 $table_name,
7959 array('bot_id' => $bot_id),
7960 array('%s')
7961 );
7962 } else {
7963 // Delete all content for default bot
7964 $result = $wpdb->query("DELETE FROM {$table_name}");
7965 }
7966
7967 if ($result === false) {
7968 $success = false;
7969 $error_messages[] = 'Failed to delete from WordPress database';
7970 }
7971 }
7972
7973 // No cache clearing needed since we removed caching
7974
7975 //error_log('=== DELETE ALL DEBUG END ===');
7976
7977 // Redirect back with a success message and bot_id
7978 $redirect_url = add_query_arg(array(
7979 'page' => 'mxchat-prompts',
7980 'bot_id' => $bot_id,
7981 'all_deleted' => $success ? 'true' : 'false'
7982 ), admin_url('admin.php'));
7983
7984 wp_safe_redirect($redirect_url);
7985 exit;
7986 }
7987
7988
7989
7990 /**
7991 * Handles deletion prompt with nonce validation
7992 */
7993 public function mxchat_handle_delete_prompt() {
7994 // Sanitize and validate nonce
7995 $nonce = isset($_GET['_wpnonce']) ? sanitize_text_field(wp_unslash($_GET['_wpnonce'])) : '';
7996 if (empty($nonce) || !wp_verify_nonce($nonce, 'mxchat_delete_prompt_nonce')) {
7997 wp_die(esc_html__('Nonce verification failed.', 'mxchat'));
7998 }
7999
8000 // Check permissions
8001 if (!current_user_can('manage_options')) {
8002 wp_die(esc_html__('You do not have sufficient permissions to delete prompts.', 'mxchat'));
8003 }
8004
8005 // Get ID and source parameters
8006 $id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : '';
8007 $source = isset($_GET['source']) ? sanitize_text_field($_GET['source']) : '';
8008
8009 if (empty($id)) {
8010 wp_die(esc_html__('Invalid prompt ID.', 'mxchat'));
8011 }
8012
8013 $success = false;
8014 $error_message = '';
8015
8016 // Check if Pinecone is enabled and determine source automatically if not specified
8017 $pinecone_options = get_option('mxchat_pinecone_addon_options', array());
8018 $use_pinecone = ($pinecone_options['mxchat_use_pinecone'] ?? '0') === '1';
8019
8020 // If source is not specified, determine based on Pinecone configuration
8021 if (empty($source)) {
8022 $source = $use_pinecone ? 'pinecone' : 'wordpress';
8023 }
8024
8025 if ($source === 'pinecone' || $use_pinecone) {
8026 //error_log('[MXCHAT-DELETE] Deleting from Pinecone, ID: ' . $id);
8027
8028 // Handle Pinecone deletion
8029 if (empty($pinecone_options['mxchat_pinecone_host']) ||
8030 empty($pinecone_options['mxchat_pinecone_api_key'])) {
8031 wp_die(esc_html__('Pinecone configuration is missing.', 'mxchat'));
8032 }
8033
8034 // Delete from Pinecone using the vector ID directly
8035 $pinecone_manager = MxChat_Pinecone_Manager::get_instance();
8036 $result = $pinecone_manager->mxchat_delete_from_pinecone_by_vector_id(
8037 $id,
8038 $pinecone_options['mxchat_pinecone_api_key'],
8039 $pinecone_options['mxchat_pinecone_host']
8040 );
8041 if ($result['success']) {
8042 $success = true;
8043
8044 // Remove from vector cache
8045 $pinecone_manager->mxchat_remove_from_pinecone_vector_cache($id);
8046 $pinecone_manager->mxchat_remove_from_processed_content_caches($id);
8047
8048 set_transient('mxchat_admin_notice_success',
8049 esc_html__('Vector deleted successfully from Pinecone.', 'mxchat'), 30);
8050 } else {
8051 $error_message = $result['message'];
8052 set_transient('mxchat_admin_notice_error',
8053 esc_html__('Failed to delete from Pinecone: ', 'mxchat') . esc_html($error_message), 30);
8054 }
8055 } else {
8056 //error_log('[MXCHAT-DELETE] Deleting from WordPress database, ID: ' . $id);
8057
8058 // Handle WordPress database deletion
8059 global $wpdb;
8060 $table_name = $wpdb->prefix . 'mxchat_system_prompt_content';
8061
8062 // Clear cache and delete prompt
8063 wp_cache_delete('prompt_' . $id, 'mxchat_prompts');
8064
8065 $result = $wpdb->delete(
8066 $table_name,
8067 array('id' => intval($id)),
8068 array('%d')
8069 );
8070
8071 if ($result !== false) {
8072 $success = true;
8073 set_transient('mxchat_admin_notice_success',
8074 esc_html__('Entry deleted successfully.', 'mxchat'), 30);
8075 } else {
8076 set_transient('mxchat_admin_notice_error',
8077 esc_html__('Failed to delete entry from database.', 'mxchat'), 30);
8078 }
8079 }
8080
8081 // Redirect back to the prompts page
8082 wp_safe_redirect(add_query_arg(
8083 array(
8084 'page' => 'mxchat-prompts',
8085 'deleted' => $success ? 'true' : 'false'
8086 ),
8087 admin_url('admin.php')
8088 ));
8089 exit;
8090 }
8091
8092
8093
8094
8095 /**
8096 * Averages multiple vectors into a single vector
8097 */
8098 private function mxchat_average_vectors($vectors) {
8099 $vector_length = count($vectors[0]);
8100 $sum_vector = array_fill(0, $vector_length, 0);
8101
8102 foreach ($vectors as $vector) {
8103 for ($i = 0; $i < $vector_length; $i++) {
8104 $sum_vector[$i] += $vector[$i];
8105 }
8106 }
8107
8108 // Divide each component by the number of vectors to get the average
8109 $num_vectors = count($vectors);
8110 for ($i = 0; $i < $vector_length; $i++) {
8111 $sum_vector[$i] /= $num_vectors;
8112 }
8113
8114 return $sum_vector;
8115 }
8116
8117
8118
8119
8120 /**
8121 * Generates embeddings from input text for MXChat
8122 */
8123 public function mxchat_generate_embedding($text) {
8124 // Enable detailed logging for debugging
8125 //error_log('[MXCHAT-EMBED] Starting embedding generation. Text length: ' . strlen($text) . ' bytes');
8126 //error_log('[MXCHAT-EMBED] Text preview: ' . substr($text, 0, 100) . '...');
8127
8128 $options = get_option('mxchat_options');
8129 $selected_model = $options['embedding_model'] ?? 'text-embedding-ada-002';
8130 //error_log('[MXCHAT-EMBED] Selected embedding model: ' . $selected_model);
8131
8132 // Determine provider and endpoint
8133 if (strpos($selected_model, 'voyage') === 0) {
8134 $api_key = $options['voyage_api_key'] ?? '';
8135 $endpoint = 'https://api.voyageai.com/v1/embeddings';
8136 $provider_name = 'Voyage AI';
8137 //error_log('[MXCHAT-EMBED] Using Voyage AI API');
8138 } elseif (strpos($selected_model, 'gemini-embedding') === 0) {
8139 $api_key = $options['gemini_api_key'] ?? '';
8140 $endpoint = 'https://generativelanguage.googleapis.com/v1beta/models/' . $selected_model . ':embedContent';
8141 $provider_name = 'Google Gemini';
8142 //error_log('[MXCHAT-EMBED] Using Google Gemini API');
8143 } else {
8144 $api_key = $options['api_key'] ?? '';
8145 $endpoint = 'https://api.openai.com/v1/embeddings';
8146 $provider_name = 'OpenAI';
8147 //error_log('[MXCHAT-EMBED] Using OpenAI API');
8148 }
8149
8150 //error_log('[MXCHAT-EMBED] Using endpoint: ' . $endpoint);
8151
8152 if (empty($api_key)) {
8153 $error_message = sprintf('Missing %s API key. Please configure your API key in the MxChat settings.', $provider_name);
8154 //error_log('[MXCHAT-EMBED] Error: ' . $error_message);
8155 return $error_message;
8156 }
8157
8158 // Check if text is too long (for OpenAI, roughly estimate tokens as words/0.75)
8159 $estimated_tokens = ceil(str_word_count($text) / 0.75);
8160 //error_log('[MXCHAT-EMBED] Estimated token count: ~' . $estimated_tokens);
8161
8162 if ($estimated_tokens > 8000 && strpos($selected_model, 'voyage') === false && strpos($selected_model, 'gemini-embedding') === false) {
8163 //error_log('[MXCHAT-EMBED] Warning: Text may exceed OpenAI token limits (8K for most models)');
8164 // Consider truncating text here
8165 }
8166
8167 // Prepare request body based on provider
8168 if (strpos($selected_model, 'gemini-embedding') === 0) {
8169 // Gemini API format
8170 $request_body = array(
8171 'model' => 'models/' . $selected_model,
8172 'content' => array(
8173 'parts' => array(
8174 array('text' => $text)
8175 )
8176 )
8177 );
8178
8179 // Set output dimensionality to 1536 for consistency with other models
8180 $request_body['outputDimensionality'] = 1536;
8181 } else {
8182 // OpenAI/Voyage API format
8183 $request_body = array(
8184 'model' => $selected_model,
8185 'input' => $text
8186 );
8187
8188 // Add output_dimension for voyage-3-large model
8189 if ($selected_model === 'voyage-3-large') {
8190 $request_body['output_dimension'] = 2048;
8191 }
8192 }
8193
8194 //error_log('[MXCHAT-EMBED] Request prepared with model: ' . $selected_model);
8195
8196 // Prepare headers based on provider
8197 if (strpos($selected_model, 'gemini-embedding') === 0) {
8198 // Gemini uses API key as query parameter
8199 $endpoint .= '?key=' . $api_key;
8200 $headers = array(
8201 'Content-Type' => 'application/json'
8202 );
8203 } else {
8204 // OpenAI/Voyage use Bearer token
8205 $headers = array(
8206 'Authorization' => 'Bearer ' . $api_key,
8207 'Content-Type' => 'application/json'
8208 );
8209 }
8210
8211 // Make API request
8212 //error_log('[MXCHAT-EMBED] Sending API request to: ' . $endpoint);
8213 $response = wp_remote_post($endpoint, array(
8214 'body' => wp_json_encode($request_body),
8215 'headers' => $headers,
8216 'timeout' => 60 // Increased timeout for large inputs
8217 ));
8218
8219 // Handle wp_remote_post errors
8220 if (is_wp_error($response)) {
8221 $error_message = $response->get_error_message();
8222 //error_log('[MXCHAT-EMBED] WP Remote Post Error: ' . $error_message);
8223 return 'Connection error: ' . $error_message;
8224 }
8225
8226 // Get and check HTTP response code
8227 $http_code = wp_remote_retrieve_response_code($response);
8228 //error_log('[MXCHAT-EMBED] API Response Code: ' . $http_code);
8229
8230 if ($http_code !== 200) {
8231 $error_body = wp_remote_retrieve_body($response);
8232 //error_log('[MXCHAT-EMBED] API Error Response Body: ' . $error_body);
8233
8234 // Try to parse error for more details
8235 $error_json = json_decode($error_body, true);
8236 if (json_last_error() === JSON_ERROR_NONE && isset($error_json['error'])) {
8237 $error_type = $error_json['error']['type'] ?? 'unknown';
8238 $error_message = $error_json['error']['message'] ?? 'No message';
8239 //error_log('[MXCHAT-EMBED] API Error Type: ' . $error_type);
8240 //error_log('[MXCHAT-EMBED] API Error Message: ' . $error_message);
8241
8242 // Customize error message for common API errors
8243 if ($error_type === 'invalid_request_error' && strpos($error_message, 'API key') !== false) {
8244 $error_message = sprintf('Invalid %s API key. Please check your API key in the MxChat settings.', $provider_name);
8245 } elseif ($error_type === 'authentication_error') {
8246 $error_message = sprintf('%s authentication failed. Please verify your API key in the MxChat settings.', $provider_name);
8247 }
8248
8249 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
8250 return $error_message;
8251 }
8252
8253 $error_message = sprintf("API Error (HTTP %d): Unable to generate embedding", $http_code);
8254 //error_log('[MXCHAT-EMBED] Returning error: ' . $error_message);
8255 return $error_message;
8256 }
8257
8258 // Parse response body
8259 $response_body = wp_remote_retrieve_body($response);
8260 //error_log('[MXCHAT-EMBED] Received response length: ' . strlen($response_body) . ' bytes');
8261
8262 $response_data = json_decode($response_body, true);
8263
8264 if (json_last_error() !== JSON_ERROR_NONE) {
8265 $error = json_last_error_msg();
8266 //error_log('[MXCHAT-EMBED] JSON Parse Error: ' . $error);
8267 //error_log('[MXCHAT-EMBED] Response preview: ' . substr($response_body, 0, 200));
8268 return "Failed to parse API response: $error";
8269 }
8270
8271 // Handle different response formats based on provider
8272 if (strpos($selected_model, 'gemini-embedding') === 0) {
8273 // Gemini API response format
8274 if (isset($response_data['embedding']['values'])) {
8275 $embedding_dimensions = count($response_data['embedding']['values']);
8276 //error_log('[MXCHAT-EMBED] Successfully extracted Gemini embedding with ' . $embedding_dimensions . ' dimensions');
8277
8278 // Check if embedding dimensions are as expected (should be 1536)
8279 if ($embedding_dimensions !== 1536) {
8280 //error_log('[MXCHAT-EMBED] Warning: Unexpected Gemini embedding dimensions: ' . $embedding_dimensions);
8281 }
8282
8283 return $response_data['embedding']['values'];
8284 } else {
8285 //error_log('[MXCHAT-EMBED] Error: No embedding found in Gemini response');
8286 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
8287
8288 if (isset($response_data['error'])) {
8289 $error_message = "Gemini API Error in response: " . wp_json_encode($response_data['error']);
8290 //error_log('[MXCHAT-EMBED] ' . $error_message);
8291 return $error_message;
8292 }
8293
8294 $error_message = "Invalid Gemini API response format: No embedding found";
8295 //error_log('[MXCHAT-EMBED] ' . $error_message);
8296 return $error_message;
8297 }
8298 } else {
8299 // OpenAI/Voyage API response format
8300 if (isset($response_data['data'][0]['embedding'])) {
8301 $embedding_dimensions = count($response_data['data'][0]['embedding']);
8302 //error_log('[MXCHAT-EMBED] Successfully extracted embedding with ' . $embedding_dimensions . ' dimensions');
8303
8304 // Check if embedding dimensions are as expected
8305 if (($selected_model === 'text-embedding-ada-002' && $embedding_dimensions !== 1536) ||
8306 ($selected_model === 'voyage-3-large' && $embedding_dimensions !== 2048)) {
8307 //error_log('[MXCHAT-EMBED] Warning: Unexpected embedding dimensions');
8308 }
8309
8310 return $response_data['data'][0]['embedding'];
8311 } else {
8312 //error_log('[MXCHAT-EMBED] Error: No embedding found in response');
8313 //error_log('[MXCHAT-EMBED] Response structure: ' . wp_json_encode(array_keys($response_data)));
8314
8315 if (isset($response_data['error'])) {
8316 $error_message = "API Error in response: " . wp_json_encode($response_data['error']);
8317 //error_log('[MXCHAT-EMBED] ' . $error_message);
8318 return $error_message;
8319 }
8320
8321 $error_message = "Invalid API response format: No embedding found";
8322 //error_log('[MXCHAT-EMBED] ' . $error_message);
8323 return $error_message;
8324 }
8325 }
8326 }
8327
8328
8329
8330 }
8331 ?>
8332