| @@ -375,9 +375,8 @@ | ||
| 375 | 375 | public function get_prompt($keyword){ |
| 376 | 376 | $openai_include_keyword = get_option( 'openai_include_keyword'); |
| 377 | 377 | $openai_exclude_keyword = get_option( 'openai_exclude_keyword'); |
| 378 | 378 | $qcld_openai_prompt = get_option('qcld_openai_prompt',true); |
| 379 | - | |
| 380 | 379 | } |
| 381 | 380 | public function include_exclude_prompt($keyword){ |
| 382 | 381 | $openai_include_keyword = strtolower(get_option('openai_include_keyword')); |
| 383 | 382 | $openai_exclude_keyword = strtolower(get_option('openai_exclude_keyword')); |
| @@ -434,9 +433,9 @@ | ||
| 434 | 433 | @ini_set( 'implicit_flush', 1 ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged |
| 435 | 434 | while ( ob_get_level() ) { |
| 436 | 435 | ob_end_clean(); |
| 437 | 436 | } |
| 438 | - ob_implicit_flush( 1 ); | |
| 437 | + ob_implicit_flush( true ); | |
| 439 | 438 | |
| 440 | 439 | header( 'Content-Type: text/event-stream' ); |
| 441 | 440 | header( 'Cache-Control: no-cache' ); |
| 442 | 441 | header( 'Connection: keep-alive' ); |
| @@ -450,8 +449,27 @@ | ||
| 450 | 449 | wp_die(); |
| 451 | 450 | } |
| 452 | 451 | |
| 453 | 452 | $system_content = get_option( 'qcld_openai_system_content', 'You are a helpful assistant.' ); |
| 453 | + | |
| 454 | + // AI Interactive Form | |
| 455 | + if (get_option('enable_ai_interactive_form') == '1') { | |
| 456 | + $saved_ai_forms = get_option('wpbot_ai_forms', array()); | |
| 457 | + if (!empty($saved_ai_forms) && is_array($saved_ai_forms)) { | |
| 458 | + $system_content .= "\n\nYou must handle the following interactive forms when the user asks for them:\n"; | |
| 459 | + foreach ($saved_ai_forms as $form) { | |
| 460 | + $system_content .= "\nForm Title: " . $form['title'] . "\nInstructions: " . $form['prompt'] . "\n"; | |
| 461 | + } | |
| 462 | + $system_content .= "\n\nCRITICAL INSTRUCTIONS FOR INTERACTIVE FORMS:\n"; | |
| 463 | + $system_content .= "When a user triggers an interactive form, you must act as a step-by-step data collection agent.\n"; | |
| 464 | + $system_content .= "1. DO NOT ask all questions at once. Ask exactly ONE question at a time.\n"; | |
| 465 | + $system_content .= "2. Wait for the user's response before asking the next question.\n"; | |
| 466 | + $system_content .= "3. Once all necessary information is collected for the form, you MUST output a final JSON block summarizing the collected data, wrapped EXACTLY in these delimiters:\n"; | |
| 467 | + $system_content .= "__AI_FORM_DATA__{ \"form_title\": \"<Form Title>\", \"data\": { \"Question 1\": \"Answer 1\", \"Question 2\": \"Answer 2\" } }__AI_FORM_DATA_END__\n"; | |
| 468 | + $system_content .= "Do not include any other text after this JSON block once the form is complete.\n"; | |
| 469 | + $system_content .= "4. If the user provides an invalid, irrelevant, or nonsensical answer to your question, DO NOT apologize or state that you lack information. Instead, respond with 'Invalid answer found' and ask the exact same question again."; | |
| 470 | + } | |
| 471 | + } | |
| 454 | 472 | |
| 455 | 473 | // RAG integration |
| 456 | 474 | if ( get_option( 'is_page_rag_enabled' ) == '1' ) { |
| 457 | 475 | if ( class_exists( 'Qcld_Bot_Rag' ) ) { |
| @@ -490,12 +508,30 @@ | ||
| 490 | 508 | $model = get_option( 'openai_engines', 'gpt-4o' ); |
| 491 | 509 | } |
| 492 | 510 | |
| 493 | 511 | $messages = [ |
| 494 | - [ 'role' => 'system', 'content' => $system_content ], | |
| 495 | - [ 'role' => 'user', 'content' => $keyword ], | |
| 512 | + [ 'role' => 'system', 'content' => $system_content ] | |
| 496 | 513 | ]; |
| 497 | 514 | |
| 515 | + $history_added = false; | |
| 516 | + if (isset($_POST['ai_history'])) { | |
| 517 | + $ai_history = json_decode(stripslashes($_POST['ai_history']), true); | |
| 518 | + if (is_array($ai_history) && !empty($ai_history)) { | |
| 519 | + foreach ($ai_history as $hist_msg) { | |
| 520 | + if (isset($hist_msg['role']) && isset($hist_msg['content'])) { | |
| 521 | + $messages[] = [ | |
| 522 | + 'role' => sanitize_text_field($hist_msg['role']), | |
| 523 | + 'content' => sanitize_text_field($hist_msg['content']) | |
| 524 | + ]; | |
| 525 | + } | |
| 526 | + } | |
| 527 | + $history_added = true; | |
| 528 | + } | |
| 529 | + } | |
| 530 | + if (!$history_added) { | |
| 531 | + $messages[] = [ 'role' => 'user', 'content' => $keyword ]; | |
| 532 | + } | |
| 533 | + | |
| 498 | 534 | $headers = [ |
| 499 | 535 | 'Content-Type: application/json', |
| 500 | 536 | 'Authorization: Bearer ' . $api_key, |
| 501 | 537 | ]; |
| @@ -505,8 +541,9 @@ | ||
| 505 | 541 | 'messages' => $messages, |
| 506 | 542 | 'stream' => true, |
| 507 | 543 | ] ); |
| 508 | 544 | |
| 545 | + // phpcs:disable WordPress.WP.AlternativeFunctions.curl_curl_init, WordPress.WP.AlternativeFunctions.curl_curl_setopt, WordPress.WP.AlternativeFunctions.curl_curl_exec, WordPress.WP.AlternativeFunctions.curl_curl_errno, WordPress.WP.AlternativeFunctions.curl_curl_error, WordPress.WP.AlternativeFunctions.curl_curl_close -- SSE streaming requires cURL write callback. | |
| 509 | 546 | $ch = curl_init( 'https://api.openai.com/v1/chat/completions' ); |
| 510 | 547 | curl_setopt( $ch, CURLOPT_POST, true ); |
| 511 | 548 | curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers ); |
| 512 | 549 | curl_setopt( $ch, CURLOPT_POSTFIELDS, $post_data ); |
| @@ -521,8 +558,9 @@ | ||
| 521 | 558 | echo 'data: [ERROR] ' . esc_html( curl_error( $ch ) ) . "\n\n"; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- curl_error is already escaped above |
| 522 | 559 | flush(); |
| 523 | 560 | } |
| 524 | 561 | curl_close( $ch ); |
| 562 | + // phpcs:enable WordPress.WP.AlternativeFunctions.curl_curl_init, WordPress.WP.AlternativeFunctions.curl_curl_setopt, WordPress.WP.AlternativeFunctions.curl_curl_exec, WordPress.WP.AlternativeFunctions.curl_curl_errno, WordPress.WP.AlternativeFunctions.curl_curl_error, WordPress.WP.AlternativeFunctions.curl_curl_close | |
| 525 | 563 | do_action( 'qcld_openai_user_rate_cal', 1 ); |
| 526 | 564 | exit; |
| 527 | 565 | } |
| 528 | 566 | |
| @@ -543,13 +581,59 @@ | ||
| 543 | 581 | $gptkeyword = []; |
| 544 | 582 | $keyword = sanitize_text_field(wp_unslash($_POST['keyword'])); |
| 545 | 583 | $relevant_pagelink = $this->relevant_pagelink($keyword); |
| 546 | 584 | |
| 585 | + // Check if from AI Actions Playground | |
| 586 | + $is_playground = isset($_POST['is_ai_actions_playground']) && intval($_POST['is_ai_actions_playground']) === 1; | |
| 587 | + $active_ai_action = isset($_POST['active_ai_action']) ? sanitize_text_field(wp_unslash($_POST['active_ai_action'])) : ''; | |
| 588 | + | |
| 589 | + $raw_ai_history = isset($_POST['ai_history']) ? json_decode(stripslashes($_POST['ai_history']), true) : array(); | |
| 590 | + $is_ai_action = $is_playground || !empty($active_ai_action) || Qcld_WPBot_Common_Functions::is_ai_action_in_progress($raw_ai_history, $keyword); | |
| 591 | + | |
| 547 | 592 | // Build context-aware system instructions |
| 548 | - $system_content = get_option('qcld_openai_system_content'); | |
| 593 | + $system_content = $is_playground ? 'You are a helpful AI assistant. You MUST strictly follow the interactive form instructions if the user asks for them.' : get_option('qcld_openai_system_content'); | |
| 549 | 594 | |
| 595 | + // AI Interactive Form | |
| 596 | + $saved_ai_forms = get_option('wpbot_ai_forms', array()); | |
| 597 | + $active_interactive_forms = array(); | |
| 598 | + | |
| 599 | + if (!empty($saved_ai_forms) && is_array($saved_ai_forms)) { | |
| 600 | + foreach ($saved_ai_forms as $form) { | |
| 601 | + if (!isset($form['interactive']) || $form['interactive'] == 1) { | |
| 602 | + $active_interactive_forms[] = $form; | |
| 603 | + } | |
| 604 | + } | |
| 605 | + } | |
| 606 | + | |
| 607 | + if (!empty($active_interactive_forms)) { | |
| 608 | + $system_content .= "\n\nYou must handle the following interactive forms when the user asks for them:\n"; | |
| 609 | + foreach ($active_interactive_forms as $form) { | |
| 610 | + $system_content .= "\nForm Title: " . $form['title'] . "\nInstructions: " . $form['prompt'] . "\n"; | |
| 611 | + } | |
| 612 | + $system_content .= "\n\nCRITICAL INSTRUCTIONS FOR INTERACTIVE FORMS:\n"; | |
| 613 | + $system_content .= "When a user triggers an interactive form, you must act as a step-by-step data collection agent.\n"; | |
| 614 | + $system_content .= "1. DO NOT ask all questions at once. Ask exactly ONE question at a time.\n"; | |
| 615 | + $system_content .= "2. Wait for the user's response before asking the next question.\n"; | |
| 616 | + $system_content .= "3. Once all necessary information is collected for the form, you MUST output a final JSON block summarizing the collected data. The keys inside the \"data\" object MUST be dynamically named based on the specific questions you asked during the form collection (e.g., \"Full Name\", \"Company Size\", \"Email\", etc.). The final JSON block must be wrapped EXACTLY in these delimiters:\n"; | |
| 617 | + $system_content .= "__AI_FORM_DATA__{ \"form_title\": \"<Form Title>\", \"data\": { \"<Generated Key 1>\": \"Answer 1\", \"<Generated Key 2>\": \"Answer 2\" } }__AI_FORM_DATA_END__\n"; | |
| 618 | + $system_content .= "Do not include any other text after this JSON block once the form is complete.\n"; | |
| 619 | + $system_content .= "4. If the user provides an invalid, irrelevant, or nonsensical answer to your question, DO NOT apologize or state that you lack information. Instead, respond with 'Invalid answer found' and ask the exact same question again."; | |
| 620 | + } | |
| 621 | + | |
| 622 | + if ($is_ai_action) { | |
| 623 | + $system_content .= "\n\nCRITICAL ACTIVE FORM COLLECTION INSTRUCTION:\n" . | |
| 624 | + "You are currently conducting an interactive step-by-step form data collection.\n" . | |
| 625 | + "RULES FOR ACTIVE FORM COLLECTION:\n" . | |
| 626 | + "1. The user's message is an answer to your last question (e.g. name, email, dates, guests, room type, phone number, etc.).\n" . | |
| 627 | + "2. Review the conversation history carefully. Notice which questions from the form have ALREADY been asked and answered.\n" . | |
| 628 | + "3. NEVER repeat questions that have already been answered earlier in the conversation.\n" . | |
| 629 | + "4. Ask the NEXT missing question in the form sequence, exactly ONE question at a time.\n" . | |
| 630 | + "5. Once ALL questions for this form have been answered, DO NOT ask any more questions or restart the form. Immediately output the final summary JSON block wrapped in __AI_FORM_DATA__{ \"form_title\": \"...\", \"data\": { ... } }__AI_FORM_DATA_END__.\n" . | |
| 631 | + "6. DO NOT apologize or state that you lack information in documentation. Just proceed with the form collection."; | |
| 632 | + } | |
| 633 | + | |
| 550 | 634 | // RAG Integration |
| 551 | - if (get_option('is_page_rag_enabled') == '1') { | |
| 635 | + if (!$is_playground && !$is_ai_action && get_option('is_page_rag_enabled') == '1') { | |
| 552 | 636 | $rag_context_text = Qcld_Bot_Rag::instance()->run_rag_search($keyword); |
| 553 | 637 | if (!empty($rag_context_text) && $rag_context_text != "No knowledge base found.") { |
| 554 | 638 | $rag_context = "Relevant Knowledge Base Information:\n"; |
| 555 | 639 | $rag_context .= $rag_context_text; |
| @@ -557,9 +641,9 @@ | ||
| 557 | 641 | $system_content .= "\n\n" . $rag_context; |
| 558 | 642 | } |
| 559 | 643 | } |
| 560 | 644 | |
| 561 | - if ( get_option('context_awareness_enabled') == '1' ) { | |
| 645 | + if ( !$is_playground && get_option('context_awareness_enabled') == '1' ) { | |
| 562 | 646 | $site_name = get_bloginfo('name'); |
| 563 | 647 | $site_desc = get_bloginfo('description'); |
| 564 | 648 | |
| 565 | 649 | // Get current page URL and title more reliably |
| @@ -640,9 +724,9 @@ | ||
| 640 | 724 | } |
| 641 | 725 | |
| 642 | 726 | $relevant_pagelink = array_slice($relevant_pagelink, 0, 5, true); |
| 643 | 727 | |
| 644 | - if( (get_option('page_suggestion_enabled') == '1') && count($relevant_pagelink) > 0 ){ | |
| 728 | + if( !$is_ai_action && (get_option('page_suggestion_enabled') == '1') && count($relevant_pagelink) > 0 ){ | |
| 645 | 729 | |
| 646 | 730 | $relevant_post_link = get_option('qlcd_wp_chatbot_relevant_post_link_openai'); |
| 647 | 731 | |
| 648 | 732 | if(is_array($relevant_post_link )){ |
| @@ -655,36 +739,39 @@ | ||
| 655 | 739 | $relevant_pagelinks = ''; |
| 656 | 740 | } |
| 657 | 741 | |
| 658 | 742 | |
| 659 | - array_push( $gptkeyword, array( | |
| 743 | + $gptkeyword[] = array( | |
| 660 | 744 | "role" => "system", |
| 661 | - "content" => $system_content | |
| 662 | - )); | |
| 663 | - array_push($gptkeyword, array( | |
| 664 | - "role" => "user", | |
| 665 | - "content" => $keyword | |
| 666 | - )); | |
| 667 | - if(((get_option('openai_include_keyword') != '') || (get_option('openai_exclude_keyword') != '')) && (get_option('qcld_openai_relevant_enabled') == '1') ){ | |
| 668 | - $prompts = $this->include_exclude_prompt($keyword); | |
| 745 | + "content" => $system_content | |
| 746 | + ); | |
| 669 | 747 | |
| 670 | - $gptkeyword = []; | |
| 671 | - array_push($gptkeyword, array( | |
| 748 | + $history_added = false; | |
| 749 | + if (isset($_POST['ai_history'])) { | |
| 750 | + $ai_history = json_decode(stripslashes($_POST['ai_history']), true); | |
| 751 | + if (is_array($ai_history) && !empty($ai_history)) { | |
| 752 | + foreach ($ai_history as $hist_msg) { | |
| 753 | + if (isset($hist_msg['role']) && isset($hist_msg['content'])) { | |
| 754 | + $gptkeyword[] = array( | |
| 755 | + "role" => sanitize_text_field($hist_msg['role']), | |
| 756 | + "content" => sanitize_text_field($hist_msg['content']) | |
| 757 | + ); | |
| 758 | + } | |
| 759 | + } | |
| 760 | + $history_added = true; | |
| 761 | + } | |
| 762 | + } | |
| 763 | + if (!$history_added) { | |
| 764 | + $gptkeyword[] = array( | |
| 672 | 765 | "role" => "user", |
| 673 | - "content" => $prompts, | |
| 674 | - )); | |
| 675 | - }else if(((get_option('openai_include_keyword') != '') || (get_option('openai_exclude_keyword') != '')) && (get_option('qcld_openai_relevant_enabled') == '0')){ | |
| 766 | + "content" => $keyword | |
| 767 | + ); | |
| 768 | + } | |
| 769 | + if(((get_option('openai_include_keyword') != '') || (get_option('openai_exclude_keyword') != '')) && (get_option('qcld_openai_relevant_enabled') == '0')){ | |
| 676 | 770 | if($this->qcld_include_keyword_exist($keyword) == false){ |
| 677 | - | |
| 678 | 771 | $response['message'] = 'Sorry, No result found!'; |
| 679 | 772 | wp_send_json( $response ); |
| 680 | - }else{ | |
| 681 | - array_push($gptkeyword, array( | |
| 682 | - "role" => "user", | |
| 683 | - "content" => $keyword | |
| 684 | - )); | |
| 685 | 773 | } |
| 686 | - | |
| 687 | 774 | } |
| 688 | 775 | $res = $OpenAI->gptcomplete( |
| 689 | 776 | $gptkeyword |
| 690 | 777 | ); |
| @@ -689,25 +776,23 @@ | ||
| 689 | 776 | $gptkeyword |
| 690 | 777 | ); |
| 691 | 778 | $mess = json_decode($res); |
| 692 | 779 | $Qcld_Parsedown = new Qcld_Parsedown(); |
| 693 | - $msg = $mess->output[0]->content[0]->text; | |
| 694 | - if( $msg == null || empty($msg) ){ | |
| 780 | + $msg = isset($mess->output[0]->content[0]->text) ? $mess->output[0]->content[0]->text : ''; | |
| 781 | + if( empty($msg) && isset($mess->output[1]->content[0]->text) ){ | |
| 695 | 782 | $msg = $mess->output[1]->content[0]->text; |
| 696 | 783 | } |
| 697 | 784 | $msg = $Qcld_Parsedown->text($msg); |
| 698 | 785 | |
| 699 | - $response['message'] = $msg ; | |
| 700 | - if(($response['message'] == 'DUH.') || ($response['message'] == 'DUH')){ | |
| 786 | + if(($msg == 'DUH.') || ($msg == 'DUH')){ | |
| 701 | 787 | $response['message'] = 'Sorry, No result found!'; |
| 702 | 788 | }else{ |
| 703 | - $Qcld_Parsedown = new Qcld_Parsedown(); | |
| 704 | - $msg = $mess->output[0]->content[0]->text; | |
| 705 | - if( $msg == null || empty($msg) ){ | |
| 706 | - $msg = $mess->output[1]->content[0]->text; | |
| 789 | + if ($is_ai_action || strpos($msg, 'AI_FORM_DATA') !== false) { | |
| 790 | + $msg = $this->format_and_save_ai_form_response($msg); | |
| 791 | + $response['message'] = $msg; | |
| 792 | + } else { | |
| 793 | + $response['message'] = $msg . $relevant_pagelinks; | |
| 707 | 794 | } |
| 708 | - $msg = $Qcld_Parsedown->text($msg); | |
| 709 | - $response['message'] = $msg . $relevant_pagelinks; | |
| 710 | 795 | } |
| 711 | 796 | do_action('qcld_openai_user_rate_cal', 1); |
| 712 | 797 | wp_send_json( $response ); |
| 713 | 798 | //} |
| @@ -761,8 +846,9 @@ | ||
| 761 | 846 | |
| 762 | 847 | $conversation_continuity = sanitize_text_field(wp_unslash($_POST['conversation_continuity'])); |
| 763 | 848 | $qcld_openai_system_content = sanitize_textarea_field(wp_unslash($_POST['qcld_openai_system_content'])); |
| 764 | 849 | $qcld_openai_append_content = sanitize_text_field(wp_unslash($_POST['qcld_openai_append_content'])); |
| 850 | + $email_addresses = isset($_POST['email_addresses']) ? array_map(function($email){ return sanitize_textarea_field(wp_unslash($email)); }, (array)$_POST['email_addresses']) : []; | |
| 765 | 851 | |
| 766 | 852 | /* Customized by Kadir on 05-12-2023 : To set empty value for API field */ |
| 767 | 853 | $disable_ss = isset( $_POST['disable_ss'] ) ? sanitize_text_field(wp_unslash($_POST['disable_ss'])) : ''; |
| 768 | 854 | |
| @@ -805,11 +891,13 @@ | ||
| 805 | 891 | |
| 806 | 892 | update_option('ai_enabled',$ai_enabled); |
| 807 | 893 | update_option('is_stream_enabled', $is_stream_enabled); |
| 808 | 894 | if( $ai_enabled == 1 ){ |
| 809 | - | |
| 810 | 895 | update_option('qcld_openrouter_enabled',0); |
| 896 | + update_option('qcld_grok_enabled',0); | |
| 811 | 897 | update_option('qcld_gemini_enabled',0); |
| 898 | + update_option('qcld_claude_enabled',0); | |
| 899 | + update_option('disable_wp_chatbot_site_search',1); | |
| 812 | 900 | } |
| 813 | 901 | |
| 814 | 902 | update_option('qcld_openai_relevant_enabled',$is_relevant_enabled); |
| 815 | 903 | update_option('page_suggestion_enabled',$suggestion_enabled); |
| @@ -912,9 +1000,8 @@ | ||
| 912 | 1000 | update_option('rag_embed_meta', $rag_embed_meta); |
| 913 | 1001 | update_option('rag_embed_meta_keys', $rag_embed_meta_keys); |
| 914 | 1002 | update_option('rag_auto_sync_enabled', $rag_auto_sync_enabled); |
| 915 | 1003 | update_option('rag_embed_cpts', $rag_embed_cpts); |
| 916 | - | |
| 917 | 1004 | wp_send_json( array('status' => 'success') ); |
| 918 | 1005 | }else{ |
| 919 | 1006 | if( !get_option('open_ai_api_key') || !get_option('qcld_gemini_api_key') ){ |
| 920 | 1007 | wp_send_json_success(array('status' => 'error', 'message' => esc_html__('RAG cannot be enabled without an API key.', 'chatbot'))); |
| @@ -1201,8 +1288,108 @@ | ||
| 1201 | 1288 | } |
| 1202 | 1289 | wp_send_json_error($msg); |
| 1203 | 1290 | } |
| 1204 | 1291 | wp_die(); |
| 1292 | + } | |
| 1293 | + | |
| 1294 | + public function format_and_save_ai_form_response($msg) { | |
| 1295 | + if (empty($msg) || (strpos($msg, 'AI_FORM_DATA') === false)) { | |
| 1296 | + return $msg; | |
| 1297 | + } | |
| 1298 | + | |
| 1299 | + $pattern = '/(?:<[^>]+>)*\s*(?:__|<strong>|<b>)?AI_FORM_DATA(?:__|<\/strong>|<\/b>)?[\s\S]*?(?:__|<strong>|<b>)?AI_FORM_DATA_END(?:__|<\/strong>|<\/b>)?\s*(?:<\/[^>]+>)*/i'; | |
| 1300 | + | |
| 1301 | + if (preg_match($pattern, $msg, $matches)) { | |
| 1302 | + $block = $matches[0]; | |
| 1303 | + if (preg_match('/\{[\s\S]*\}/', $block, $json_matches)) { | |
| 1304 | + $json_str = trim($json_matches[0]); | |
| 1305 | + $data = json_decode($json_str, true); | |
| 1306 | + | |
| 1307 | + if ($data && isset($data['form_title'])) { | |
| 1308 | + $post_title = sanitize_text_field($data['form_title']) . ' - ' . current_time('mysql'); | |
| 1309 | + $post_id = wp_insert_post(array( | |
| 1310 | + 'post_title' => $post_title, | |
| 1311 | + 'post_type' => 'wpbot_form_entry', | |
| 1312 | + 'post_status' => 'publish' | |
| 1313 | + )); | |
| 1314 | + | |
| 1315 | + if ($post_id && isset($data['data']) && is_array($data['data'])) { | |
| 1316 | + $email_body = "<h2>" . esc_html__('New AI Chat Submission', 'chatbot') . "</h2>"; | |
| 1317 | + $email_body .= "<p><strong>" . esc_html__('Chat', 'chatbot') . ":</strong> " . sanitize_text_field($data['form_title']) . "</p>"; | |
| 1318 | + $email_body .= "<table border='1' cellpadding='10' cellspacing='0' style='border-collapse: collapse; width: 100%; max-width: 600px; font-family: sans-serif;'>"; | |
| 1319 | + | |
| 1320 | + // Extract user's email for Reply-To (not From, to avoid SMTP rejection) | |
| 1321 | + $reply_to = ''; | |
| 1322 | + | |
| 1323 | + foreach ($data['data'] as $key => $value) { | |
| 1324 | + update_post_meta($post_id, sanitize_text_field($key), sanitize_text_field($value)); | |
| 1325 | + $clean_key = ucwords(str_replace(array('-', '_'), ' ', sanitize_text_field($key))); | |
| 1326 | + $email_body .= "<tr><td style='background: #f4f4f4; width: 40%;'><strong>" . esc_html($clean_key) . "</strong></td><td>" . esc_html(sanitize_text_field($value)) . "</td></tr>"; | |
| 1327 | + | |
| 1328 | + // Extract email from value — handles plain, [bracketed], and combined answers | |
| 1329 | + if (preg_match('/\[?([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})\]?/', $value, $email_match)) { | |
| 1330 | + $candidate = sanitize_email(trim($email_match[1])); | |
| 1331 | + if (is_email($candidate)) { | |
| 1332 | + $reply_to = $candidate; | |
| 1333 | + } | |
| 1334 | + } | |
| 1335 | + } | |
| 1336 | + $email_body .= "</table>"; | |
| 1337 | + | |
| 1338 | + $email_enabled = true; | |
| 1339 | + $per_action_emails = ''; | |
| 1340 | + $saved_forms = get_option('wpbot_ai_forms', array()); | |
| 1341 | + $ai_form_title = strtolower( trim( sanitize_text_field( $data['form_title'] ) ) ); | |
| 1342 | + if (is_array($saved_forms)) { | |
| 1343 | + foreach ($saved_forms as $form) { | |
| 1344 | + if ( strtolower( trim( $form['title'] ) ) === $ai_form_title ) { | |
| 1345 | + if (isset($form['email'])) { | |
| 1346 | + $email_enabled = $form['email'] == 1; | |
| 1347 | + } | |
| 1348 | + $per_action_emails = isset($form['email_addresses']) ? trim($form['email_addresses']) : ''; | |
| 1349 | + //error_log('[WPBot AI] Matched form: "' . $form['title'] . '" | email_addresses stored: "' . $per_action_emails . '"'); | |
| 1350 | + break; | |
| 1351 | + } | |
| 1352 | + } | |
| 1353 | + } | |
| 1354 | + | |
| 1355 | + if ($email_enabled) { | |
| 1356 | + // Per-action email_addresses → fallback to qlcd_wp_chatbot_admin_email → fallback to WP admin_email | |
| 1357 | + if (!empty($per_action_emails)) { | |
| 1358 | + $to = array_map('trim', explode(',', $per_action_emails)); | |
| 1359 | + } else { | |
| 1360 | + $default = get_option('qlcd_wp_chatbot_admin_email', ''); | |
| 1361 | + $to = !empty($default) ? array_map('trim', explode(',', $default)) : get_option('admin_email'); | |
| 1362 | + } | |
| 1363 | + $subject = sanitize_text_field($data['form_title']) . " - " . esc_html__('New AI Chat Submission', 'chatbot'); | |
| 1364 | + $headers = array('Content-Type: text/html; charset=UTF-8'); | |
| 1365 | + if (!empty($reply_to)) { | |
| 1366 | + $headers[] = 'Reply-To: ' . $reply_to; | |
| 1367 | + } | |
| 1368 | + wp_mail($to, $subject, $email_body, $headers); | |
| 1369 | + } | |
| 1370 | + } | |
| 1371 | + | |
| 1372 | + $summary_html = '<div class="ai-form-summary-card" style="background: #f4f6f9; border-left: 4px solid #0073aa; padding: 12px 14px; margin: 10px 0; border-radius: 4px; font-size: 13px; line-height: 1.5; color: #333;">'; | |
| 1373 | + if (!empty($data['form_title'])) { | |
| 1374 | + $summary_html .= '<div style="font-weight: 600; color: #0073aa; margin-bottom: 8px; text-transform: capitalize; font-size: 14px;">' . esc_html($data['form_title']) . '</div>'; | |
| 1375 | + } | |
| 1376 | + if (!empty($data['data']) && is_array($data['data'])) { | |
| 1377 | + $summary_html .= '<table style="width: 100%; border-collapse: collapse; margin-top: 4px;">'; | |
| 1378 | + foreach ($data['data'] as $k => $v) { | |
| 1379 | + $clean_k = ucwords(str_replace(array('-', '_'), ' ', sanitize_text_field($k))); | |
| 1380 | + $summary_html .= '<tr><td style="padding: 3px 6px 3px 0; color: #555; font-weight: 600; width: 42%; vertical-align: top;">' . esc_html($clean_k) . ':</td><td style="padding: 3px 0; color: #222; vertical-align: top;">' . esc_html($v) . '</td></tr>'; | |
| 1381 | + } | |
| 1382 | + $summary_html .= '</table>'; | |
| 1383 | + } | |
| 1384 | + $summary_html .= '</div>'; | |
| 1385 | + | |
| 1386 | + $msg = preg_replace($pattern, $summary_html, $msg); | |
| 1387 | + } | |
| 1388 | + } | |
| 1389 | + } | |
| 1390 | + | |
| 1391 | + return $msg; | |
| 1205 | 1392 | } |
| 1206 | 1393 | } |
| 1207 | 1394 | |
| 1208 | 1395 | /** |