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

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

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