PluginProbe
MxChat – AI Chatbot & Content Generation for WordPress / 3.0.3
MxChat – AI Chatbot & Content Generation for WordPress v3.0.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.0.3, at includes/class-mxchat-admin.php

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