PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.8.0
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.8.0
8.8.0 8.7.9 8.7.8 8.7.7 8.7.6 8.7.5 8.7.4 8.7.3 8.7.2 8.7.1 8.7.0 8.6.9 8.6.8 8.6.7 8.6.6 8.6.5 8.6.4 8.6.2 8.6.1 8.6.0 8.5.9 8.5.8 8.5.7 8.5.6 8.5.5 All 536 releases
← All changes | includes/integration/openai/qcld-bot-openai.php +228 -41 8.5.6 → 8.8.0 View file →
@@ -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 /**