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/chat-sessions/wpbot-chat-sessions.php +195 -111 8.6.0 → 8.8.0 View file →
@@ -49,74 +49,110 @@
49 49
50 50 // ─── Admin Menu ───────────────────────────────────────────────────────────────
51 51 add_action( 'admin_menu', 'qcwp_chat_session_menu_fnc_free' );
52 52
53 -function qcwp_chat_session_menu_fnc_free() {
53 + function qcwp_chat_session_menu_fnc_free() {
54 + // All menu registration is now handled by chatbot (qcld-wpwbot.php)
55 + // We no longer register submenus here to keep the sidebar clean.
56 + }
54 57
55 - $capability = function_exists( 'qcld_wpbot_get_menu_capability' ) ? qcld_wpbot_get_menu_capability( 'sessions' ) : 'manage_options';
58 + function qc_wpbot_cs_tabbed_wrapper() {
59 + $active_tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'sessions';
56 60
57 - if ( current_user_can( $capability ) ) {
61 + $tabs = array(
62 + 'sessions' => array(
63 + 'label' => __( 'Chat Sessions', 'wpbot-chat-history' ),
64 + 'icon' => 'dashicons-format-chat',
65 + ),
66 + 'not-answered' => array(
67 + 'label' => __( 'Questions Not Answered', 'wpbot-chat-history' ),
68 + 'icon' => 'dashicons-editor-help',
69 + ),
70 + 'ai-insight' => array(
71 + 'label' => __( 'AI Insight', 'wpbot-chat-history' ),
72 + 'icon' => 'dashicons-lightbulb',
73 + ),
74 + );
58 75
59 - add_menu_page(
60 - 'WPBot - Sessions & Analytics',
61 - 'WPBot - Sessions & Analytics',
62 - $capability,
63 - 'wbcs-botsessions-page',
64 - 'qc_wpbot_cs_menu_page_callback_func',
65 - 'dashicons-chart-bar',
66 - '9'
67 - );
76 + if ( function_exists( 'qcpdcs_is_woowbot_active' ) && qcpdcs_is_woowbot_active() ) {
77 + $tabs['woowbot-sessions'] = array(
78 + 'label' => __( 'ChatBot Sessions', 'wpbot-chat-history' ),
79 + 'icon' => 'dashicons-cart',
80 + );
81 + }
68 82
69 - add_submenu_page(
70 - 'wbcs-botsessions-page',
71 - 'Questions Not Answered',
72 - 'Questions Not Answered',
73 - $capability,
74 - 'wbcs-botsessions-notansweredpage',
75 - 'qcld_wpbot_not_answered_question'
76 - );
83 + ?>
84 + <div class="wrap qcld-main-wrapper qcld-chat-sessions-wrap">
85 + <h1 style="display:none"><?php esc_html_e( 'Chat Sessions', 'wpbot-chat-history' ); ?></h1>
77 86
78 - add_submenu_page(
79 - 'wbcs-botsessions-page',
80 - 'AI Insight',
81 - 'AI Insight',
82 - $capability,
83 - 'wbcs-schedule-session-reporting',
84 - 'qcld_wpbot_schedule_session_reporting'
85 - );
87 + <div class="qcld-wp-chatbot-wrap-header">
88 + <div class="qcld-wp-chatbot-wrap-header-logo">
89 + <a href="#" class="qcld-wp-chatbot-wrap-site__logo">
90 + <img src="<?php echo esc_url( QCLD_wpCHATBOT_IMG_URL . '/chatbot.png' ); ?>" alt="WPBot"> WPBot Control Panel
91 + </a>
92 + <p><strong>Core Version:</strong> v<?php echo esc_html( QCLD_wpCHATBOT_VERSION ); ?></p>
93 + </div>
94 + <ul class="qcld-wp-chatbot-wrap-version-wrapper">
95 + <li>
96 + <a class="wpchatbot-Upgrade" href="https://www.wpbot.pro/" target="_blank"><?php esc_html_e( 'Upgrade To Pro', 'chatbot' ); ?></a>
97 + </li>
98 + </ul>
99 + </div>
100 +
101 + <div class="qcld-wp-chatbot-wrap-header_inn qcld-chat-sessions-header">
102 + <div class="qcld-wp-chatbot-wrap-header_inn_heading">
103 + <h1 class="wp-heading-inline"><?php esc_html_e( 'Sessions & Analytics', 'wpbot-chat-history' ); ?></h1>
104 + </div>
105 + <nav class="nav-tab-wrapper qcld-chat-sessions-tabs">
106 + <?php foreach ( $tabs as $tab_id => $tab ) : ?>
107 + <a href="<?php echo esc_url( admin_url( 'admin.php?page=wbcs-botsessions-page&tab=' . rawurlencode( $tab_id ) ) ); ?>" class="nav-tab <?php echo $active_tab === $tab_id ? 'nav-tab-active' : ''; ?>">
108 + <span class="dashicons <?php echo esc_attr( $tab['icon'] ); ?>"></span>
109 + <span><?php echo esc_html( $tab['label'] ); ?></span>
110 + </a>
111 + <?php endforeach; ?>
112 + </nav>
113 + </div>
114 +
115 + <div class="qcld-chat-sessions-tab-content">
116 + <?php
117 + switch ( $active_tab ) {
118 + case 'not-answered':
119 + if ( function_exists( 'qcld_wpbot_not_answered_question' ) ) {
120 + qcld_wpbot_not_answered_question();
121 + }
122 + break;
123 + case 'ai-insight':
124 + if ( function_exists( 'qcld_wpbot_schedule_session_reporting' ) ) {
125 + qcld_wpbot_schedule_session_reporting();
126 + }
127 + break;
128 + case 'woowbot-sessions':
129 + if ( function_exists( 'woowbot_cs_menu_page_callback_func' ) ) {
130 + woowbot_cs_menu_page_callback_func();
131 + }
132 + break;
133 + case 'sessions':
134 + default:
135 + if ( function_exists( 'qc_wpbot_cs_menu_page_callback_func' ) ) {
136 + qc_wpbot_cs_menu_page_callback_func();
137 + }
138 + break;
139 + }
140 + ?>
141 + </div>
142 + </div>
143 + <?php
86 144 }
87 -}
88 145
89 146 // ─── Admin Scripts & Styles ───────────────────────────────────────────────────
90 147 add_action( 'admin_enqueue_scripts', 'qcld_wb_chatbot_session_admin_scripts_free' );
91 148
92 149 function qcld_wb_chatbot_session_admin_scripts_free( $hook ) {
93 - // WordPress generates hook suffixes as follows:
94 - // top-level page → toplevel_page_{slug}
95 - // sub-pages → {parent-menu-title}_page_{slug} (title, lowercased, spaces→hyphens)
96 - // Our parent title is "WPBot Sessions & Analytics" → "wpbot-sessions-analytics"
97 - $session_hooks = array(
98 - 'toplevel_page_wbcs-botsessions-page',
99 - 'wpbot-sessions-analytics_page_wbcs-botsessions-notansweredpage',
100 - 'wpbot-sessions-analytics_page_wbcs-botsessions-reports',
101 - 'wpbot-sessions-analytics_page_wbcs-schedule-session-reporting',
102 - );
103 - $is_session_page = false;
104 - foreach ( $session_hooks as $session_hook ) {
105 - if ( strpos( $hook, $session_hook ) !== false || ( isset( $_GET['page'] ) && $_GET['page'] === str_replace( 'toplevel_page_', '', $session_hook ) ) ) {
106 - $is_session_page = true;
107 - break;
150 + // Only enqueue on our specific sessions page
151 + if ( ! isset( $_GET['page'] ) || 'wbcs-botsessions-page' !== $_GET['page'] ) {
152 + return;
108 153 }
109 - }
110 -
111 - if ( isset( $_GET['page'] ) && in_array( $_GET['page'], array( 'wbcs-botsessions-page', 'wbcs-botsessions-notansweredpage', 'wbcs-botsessions-reports', 'wbcs-schedule-session-reporting' ) ) ) {
112 - $is_session_page = true;
113 - }
114 154
115 - if ( ! $is_session_page ) {
116 - return;
117 - }
118 -
119 155 wp_register_style( 'qlcd-wp-bootstrap-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/qlcd-wp-bootstrap.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
120 156 wp_enqueue_style( 'qlcd-wp-bootstrap-cs' );
121 157
122 158 wp_register_style( 'qlcd-wp-bootstrap-icons-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/qlcd-wp-bootstrap-icons.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
@@ -127,8 +163,11 @@
127 163
128 164 wp_register_style( 'qlcd-wp-session-style-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'reports/view/assets/style.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
129 165 wp_enqueue_style( 'qlcd-wp-session-style-cs' );
130 166
167 + wp_register_style( 'qcld-wp-chatbot-history-style', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/history-style.css', array( 'qlcd-wp-chatbot-admin-style' ), QCLD_wpCHATBOT_VERSION, 'screen' );
168 + wp_enqueue_style( 'qcld-wp-chatbot-history-style' );
169 +
131 170 // SweetAlert2 — used by admin.js for Swal.fire() and Swal.showLoading()
132 171 wp_register_script( 'qcld-wp-chatbot-sweetalrt-cs', QCLD_wpCHATBOT_PLUGIN_URL . 'js/sweetalrt.js', array( 'jquery' ), QCLD_wpCHATBOT_VERSION, true );
133 172 wp_enqueue_script( 'qcld-wp-chatbot-sweetalrt-cs' );
134 173
@@ -150,22 +189,31 @@
150 189
151 190
152 191 // ─── AI Insight Page Callback ─────────────────────────────────────────────────
153 192 function qcld_wpbot_schedule_session_reporting() {
193 + wp_register_style( 'qcld-wp-chatbot-history-style', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/history-style.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
194 + wp_enqueue_style( 'qcld-wp-chatbot-history-style' );
154 195 ?>
155 - <div class="wrap">
156 - <h2><?php echo esc_html( 'AI Insight' ); ?></h2>
157 - <div class="notice notice-warning inline" style="margin-top: 20px; padding: 20px;">
158 - <h3><span class="dashicons dashicons-lock"></span> <?php echo esc_html( 'Feature Locked' ); ?></h3>
159 - <p>
160 - <?php echo esc_html( 'The AI Insight feature allows you to receive an AI-based summary of all chat conversations emailed directly to you on a schedule.' ); ?>
196 + <div class="wrap wpbot-ai-insight-page">
197 + <div class="wpbot-ai-insight-header">
198 + <h2><?php echo esc_html__( 'AI Insight', 'chatbot' ); ?></h2>
199 + <p><?php echo esc_html__( 'Scheduled AI summaries of your chat conversations.', 'chatbot' ); ?></p>
200 + </div>
201 +
202 + <div class="wpbot-ai-insight-locked">
203 + <div class="wpbot-ai-insight-locked__icon" aria-hidden="true">
204 + <span class="dashicons dashicons-lock"></span>
205 + </div>
206 + <h3><?php echo esc_html__( 'Feature Locked', 'chatbot' ); ?></h3>
207 + <p class="wpbot-ai-insight-locked__desc">
208 + <?php echo esc_html__( 'The AI Insight feature allows you to receive an AI-based summary of all chat conversations emailed directly to you on a schedule.', 'chatbot' ); ?>
161 209 </p>
162 - <p>
163 - <strong><?php echo esc_html( 'Please upgrade to WPBot Pro to unlock this feature!' ); ?></strong>
210 + <p class="wpbot-ai-insight-locked__cta">
211 + <?php echo esc_html__( 'Please upgrade to WPBot Pro to unlock this feature!', 'chatbot' ); ?>
164 212 </p>
165 - <p>
166 - <a href="https://www.wpbot.pro/" target="_blank" class="button button-primary button-large"><?php echo esc_html( 'Upgrade to Pro' ); ?></a>
167 - </p>
213 + <a href="https://www.wpbot.pro/" target="_blank" rel="noopener noreferrer" class="wpbot-ai-insight-locked__btn">
214 + <?php echo esc_html__( 'Upgrade to Pro', 'chatbot' ); ?>
215 + </a>
168 216 </div>
169 217 </div>
170 218 <?php
171 219 }
@@ -317,9 +365,9 @@
317 365 $result = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
318 366 $totalPage = ceil( $total / $items_per_page );
319 367 $customPagHTML = '';
320 368 if ( $totalPage > 1 ) {
321 - $customPagHTML = '<div><span class="wpbot_pagination">Page ' . esc_html( $page ) . ' of ' . esc_html( $totalPage ) . '</span>' . paginate_links(
369 + $customPagHTML = '<div class="qcld-session-pagination"><span class="wpbot_pagination">Page ' . esc_html( $page ) . ' of ' . esc_html( $totalPage ) . '</span>' . paginate_links(
322 370 array(
323 371 'base' => add_query_arg( 'cpage', '%#%' ),
324 372 'format' => '',
325 373 'prev_text' => __( '« prev' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
@@ -355,13 +403,10 @@
355 403 }
356 404 ?>
357 405 </div>
358 406
359 - <?php if ( $customPagHTML != '' ) : ?>
360 - <div class="sld_menu_title sld_menu_title_align"><?php echo wp_kses_post( $customPagHTML ); ?></div>
361 - <?php endif; ?>
362 407
363 - <form id="wpcs_form_sessions" action="<?php echo esc_url( $mainurl ); ?>" method="POST" style="width:100%">
408 + <form id="wpcs_form_sessions" action="<?php echo esc_url( $mainurl ); ?>" method="POST">
364 409 <?php wp_nonce_field( 'wpcs_bulk_action' ); ?>
365 410 <input type="hidden" name="wpbot_session_remove" />
366 411
367 412 <?php if ( ! empty( $result ) ) : ?>
@@ -418,9 +463,9 @@
418 463 foreach ( $users as $user ) {
419 464 $sessions[] = wpbot_conversations_export( $user );
420 465 }
421 466 }
422 - qcld_wpbot_chatsession_download_send_headers( 'wpbot_chatsession_' . date( 'Y-m-d' ) . '.csv' );
467 + qcld_wpbot_chatsession_download_send_headers( 'wpbot_chatsession_' . gmdate( 'Y-m-d' ) . '.csv' );
423 468 print wpbot_chatsession_array2csv( $sessions ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw CSV download, escaping would corrupt the file.
424 469 exit;
425 470 }
426 471
@@ -434,9 +479,9 @@
434 479 foreach ( $userids as $userid ) {
435 480 $user = $wpdb->get_row( $wpdb->prepare( "SELECT wu.`id`, wu.`session_id`, wu.`name`, wu.`email`, wu.`date`, wu.`phone`, wu.`interaction`, wc.`conversation` FROM $tableuser1 as wu, $tableconversation1 as wc WHERE 1 AND wu.id = wc.user_id AND wu.id = %d", $userid ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
436 481 $sessions[] = wpbot_conversations_export( $user );
437 482 }
438 - qcld_wpbot_chatsession_download_send_headers( 'wpbot_chatsession_' . date( 'Y-m-d' ) . '.csv' );
483 + qcld_wpbot_chatsession_download_send_headers( 'wpbot_chatsession_' . gmdate( 'Y-m-d' ) . '.csv' );
439 484 print wpbot_chatsession_array2csv( $sessions ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw CSV download, escaping would corrupt the file.
440 485 exit;
441 486 }
442 487
@@ -492,10 +537,22 @@
492 537 $message = sanitize_text_field( $_POST['data']['message'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
493 538 $to = sanitize_email( $_POST['data']['to'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
494 539
495 540 global $wpdb;
496 - $tableuser = $wpdb->prefix . 'wpbot_user';
497 - $user_exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $tableuser WHERE email = %s LIMIT 1", $to ) );
541 + $tableuser = $wpdb->prefix . 'wpbot_user';
542 + $table_sql = esc_sql( $tableuser );
543 + $cache_key = 'wpbot_user_email_' . md5( $to );
544 + $user_exists = wp_cache_get( $cache_key, 'wpbot' );
545 + if ( false === $user_exists ) {
546 + $user_exists = $wpdb->get_var( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
547 + $wpdb->prepare(
548 + 'SELECT id FROM `' . $table_sql . '` WHERE email = %s LIMIT 1', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
549 + $to
550 + )
551 + );
552 + wp_cache_set( $cache_key, $user_exists, 'wpbot', 60 );
553 + }
554 +
498 555 $admin_email = get_option('admin_email');
499 556 if ( ! $user_exists && $to !== $admin_email ) {
500 557 wp_send_json( array( 'status' => 'fail', 'message' => 'Invalid recipient address. Email must be a stored session email or admin email.' ) );
501 558 }
@@ -544,21 +601,14 @@
544 601
545 602 $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
546 603 $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
547 604
548 - $allowed_html = array_merge(
549 - wp_kses_allowed_html( 'post' ),
550 - array(
551 - 'div' => array( 'class' => true, 'id' => true, 'style' => true, 'data-*' => true ),
552 - 'span' => array( 'class' => true, 'id' => true, 'style' => true, 'data-*' => true ),
553 - 'ul' => array( 'class' => true ),
554 - 'li' => array( 'class' => true ),
555 - 'img' => array( 'src' => true, 'alt' => true, 'class' => true, 'style' => true ),
556 - )
557 - );
558 - $raw_conversation = isset( $_POST['conversation'] ) ? wp_unslash( $_POST['conversation'] ) : '';
559 - $clean_conversation = wp_kses( $raw_conversation, $allowed_html );
560 - $conversation = qcld_wpbot_input_validation( $clean_conversation ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
605 + // SECURITY FIX: Pass raw (decoded) input to qcld_wpbot_input_validation(), which now
606 + // correctly runs html_entity_decode() BEFORE wp_kses(). Previously, wp_kses() ran first
607 + // on entity-encoded input (&lt;img onerror=...&gt;), saw inert text, and passed it through.
608 + // html_entity_decode() then revived the executable markup after sanitization had already run.
609 + $raw_conversation = isset( $_POST['conversation'] ) ? wp_unslash( $_POST['conversation'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
610 + $conversation = qcld_wpbot_input_validation( $raw_conversation ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
561 611 $email = isset( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
562 612 $phone = isset( $_POST['phone'] ) ? sanitize_text_field( $_POST['phone'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
563 613 $name = isset( $_POST['name'] ) ? sanitize_text_field( $_POST['name'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
564 614 $session_id = isset( $_POST['session_id'] ) ? sanitize_text_field( $_POST['session_id'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
@@ -660,9 +710,9 @@
660 710 if ( $is_new_insert && ( get_option( 'session_email_notification_update' ) == 'checked' ) ) {
661 711 $admin_email = get_option( 'admin_email' );
662 712 $subject = esc_html__( 'Someone has started a new chat session with ChatBot.', 'chatbot' );
663 713 $bodyContent = '<p>' . esc_html__( 'Hi,', 'chatbot' ) . '</p>';
664 - $bodyContent .= '<p>' . esc_html__( 'Someone has started a new chat session with ChatBot. Please go to ', 'chatbot' ) . '<a href="' . admin_url() . 'admin.php?page=wbcs-botsessions-page">' . esc_html__( 'Bot Sessions Dashboard', 'chatbot' ) . '</a>' . esc_html__( ' and find him/her.', 'chatbot' ) . '</p>';
714 + $bodyContent .= '<p>' . esc_html__( 'Someone has started a new chat session with ChatBot. Please go to ', 'chatbot' ) . '<a href="' . admin_url() . 'admin.php?page=wbcs-botsessions-page&userid='. $user_id .'">' . esc_html__( 'Bot Sessions Dashboard', 'chatbot' ) . '</a>' . esc_html__( ' and find him/her.', 'chatbot' ) . '</p>';
665 715
666 716 $bodyContent .= '<ul>';
667 717 $bodyContent .= '<li>' . esc_html__( 'Session ID:', 'chatbot' ) . ' <strong>' . esc_html( $session_id ) . '</strong></li>';
668 718 if ( ! empty( $email ) ) {
@@ -854,9 +904,17 @@
854 904 $email_from = get_option( 'qlcd_wp_chatbot_from_email' );
855 905 $result = $wpdb->get_row( $wpdb->prepare( "SELECT c.*, u.email, u.name, u.session_id as user_session_id FROM $tableconversation AS c LEFT JOIN $tableuser AS u ON c.user_id = u.id WHERE c.user_id = %d", $session_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
856 906 if ( ! empty( $result ) ) {
857 907 $result->email_from = $email_from;
858 - $result->status = 'success';
908 + $result->status = 'success';
909 +
910 + // SECURITY FIX: The stored conversation is entity-encoded (htmlspecialchars output).
911 + // Decode it and re-sanitize with wp_kses before returning to the admin UI.
912 + // This guarantees admin.js always receives clean, safe HTML — no onerror/onclick can survive.
913 + if ( isset( $result->conversation ) ) {
914 + $decoded = html_entity_decode( (string) $result->conversation, ENT_QUOTES | ENT_HTML5, 'UTF-8' );
915 + $result->conversation = wp_kses( $decoded, wpbot_get_safe_conversation_tags() );
916 + }
859 917 }
860 918 echo wp_json_encode( $result );
861 919 wp_die();
862 920 }
@@ -870,9 +928,11 @@
870 928 wp_die();
871 929 }
872 930 check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
873 931
874 - $to = isset( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
932 + $to = isset( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
933 + $from_raw = isset( $_POST['from_email'] ) ? trim( wp_unslash( $_POST['from_email'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
934 + $from = sanitize_email( $from_raw );
875 935 $subject = isset( $_POST['subject'] ) ? sanitize_text_field( wp_unslash( $_POST['subject'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
876 936 $message = isset( $_POST['message'] ) ? wp_kses_post( wp_unslash( $_POST['message'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
877 937
878 938 if ( empty( $to ) || empty( $message ) ) {
@@ -879,9 +939,17 @@
879 939 wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Email and Message are required', 'chatbot' ) ) );
880 940 wp_die();
881 941 }
882 942
943 + // if ( ! empty( $from_raw ) && ( $from_raw !== $from || ! is_email( $from ) ) ) {
944 + // wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Please enter a valid From email address', 'chatbot' ) ) );
945 + // wp_die();
946 + // }
947 +
883 948 $headers = array( 'Content-Type: text/html; charset=UTF-8' );
949 + if ( ! empty( $from ) && is_email( $from ) ) {
950 + $headers[] = 'From: ' . $from;
951 + }
884 952
885 953 // Convert newlines to HTML line breaks
886 954 $email_body = nl2br( $message );
887 955
@@ -974,18 +1042,21 @@
974 1042
975 1043 $api_key = get_option( 'open_ai_api_key' );
976 1044 $engines = get_option( 'openai_engines' );
977 1045 $post_fields = array( 'model' => $engines, 'input' => $gptkeyword );
978 - $header = array( 'Content-Type: application/json', 'Authorization: Bearer ' . $api_key );
979 1046
980 - $ch = curl_init();
981 - curl_setopt( $ch, CURLOPT_URL, 'https://api.openai.com/v1/responses' );
982 - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
983 - curl_setopt( $ch, CURLOPT_POST, 1 );
984 - curl_setopt( $ch, CURLOPT_POSTFIELDS, wp_json_encode( $post_fields ) );
985 - curl_setopt( $ch, CURLOPT_HTTPHEADER, $header );
986 - $result = curl_exec( $ch );
987 - curl_close( $ch );
1047 + $api_response = wp_remote_post(
1048 + 'https://api.openai.com/v1/responses',
1049 + array(
1050 + 'headers' => array(
1051 + 'Content-Type' => 'application/json',
1052 + 'Authorization' => 'Bearer ' . $api_key,
1053 + ),
1054 + 'body' => wp_json_encode( $post_fields ),
1055 + 'timeout' => 60,
1056 + )
1057 + );
1058 + $result = is_wp_error( $api_response ) ? '' : wp_remote_retrieve_body( $api_response );
988 1059
989 1060 $mess = json_decode( $result );
990 1061 if ( ! empty( $mess->error ) ) {
991 1062 wp_send_json( array( 'status' => 'error', 'icon' => 'error', 'msg' => esc_html( $mess->error->code ), 'response' => esc_html( $mess->error->message ) ) );
@@ -1057,8 +1128,16 @@
1057 1128 // ─── CSV Export Helpers ───────────────────────────────────────────────────────
1058 1129 add_action( 'admin_post_wpbot_conversations.csv', 'wpbot_conversations_csv_export_free' );
1059 1130
1060 1131 function wpbot_conversations_csv_export_free() {
1132 + if ( ! current_user_can( 'manage_options' ) ) {
1133 + wp_die( esc_html__( 'Unauthorized', 'chatbot' ) );
1134 + }
1135 +
1136 + if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wpbot_conversations_csv' ) ) {
1137 + wp_die( esc_html__( 'Security check failed.', 'chatbot' ) );
1138 + }
1139 +
1061 1140 global $wpdb;
1062 1141 $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
1063 1142 $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
1064 1143 $userid = sanitize_text_field( $_GET['user_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
@@ -1070,9 +1149,9 @@
1070 1149 if ( ! empty( $result ) ) {
1071 1150 $data[] = array( 'User Name', $userinfo->name );
1072 1151 $data[] = array( 'User Email', $userinfo->email );
1073 1152 $data[] = array( 'Session ID', $userinfo->session_id );
1074 - $data[] = array( 'Date', date( 'M,d,Y h:i:s A', strtotime( $userinfo->date ) ) );
1153 + $data[] = array( 'Date', gmdate( 'M,d,Y h:i:s A', strtotime( $userinfo->date ) ) );
1075 1154 $data[] = array( 'Bot Message', 'User Message' );
1076 1155 $messages = qcld_wpch_conversation_extract( htmlspecialchars_decode( $result->conversation ) );
1077 1156 foreach ( $messages as $message ) {
1078 1157 if ( isset( $message['bot'] ) && trim( $message['bot'] ) != '' ) {
@@ -1082,9 +1161,9 @@
1082 1161 $data[] = array( '', str_replace( '&nbsp;', ' ', trim( $message['user'] ) ) );
1083 1162 }
1084 1163 }
1085 1164 }
1086 - qcld_wpbot_chatsession_download_send_headers( $userinfo->name . '_wpbot_chatsession_' . date( 'Y-m-d' ) . '.csv' );
1165 + qcld_wpbot_chatsession_download_send_headers( $userinfo->name . '_wpbot_chatsession_' . gmdate( 'Y-m-d' ) . '.csv' );
1087 1166 print wpbot_chatsession_array2csv( $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw CSV download, escaping would corrupt the file.
1088 1167 }
1089 1168
1090 1169 if ( ! function_exists( 'wpbot_conversations_export' ) ) {
@@ -1094,9 +1173,9 @@
1094 1173 if ( ! empty( $user ) ) {
1095 1174 $messages = qcld_wpch_conversation_extract( htmlspecialchars_decode( $user->conversation ) );
1096 1175 $dataArray = array(
1097 1176 'Session ID' => $user->session_id,
1098 - 'Date' => date( 'M,d,Y h:i:s A', strtotime( $user->date ) ),
1177 + 'Date' => gmdate( 'M,d,Y h:i:s A', strtotime( $user->date ) ),
1099 1178 'User Name' => $user->name,
1100 1179 'User Email' => $user->email,
1101 1180 );
1102 1181 $conversations = '';
@@ -1131,8 +1210,9 @@
1131 1210 if ( ! function_exists( 'wpbot_chatsession_array2csv' ) ) {
1132 1211 function wpbot_chatsession_array2csv( array &$array ) {
1133 1212 if ( count( $array ) == 0 ) { return null; }
1134 1213 ob_start();
1214 + // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fputs, WordPress.WP.AlternativeFunctions.file_system_operations_fclose -- php://output memory stream for CSV export.
1135 1215 $df = fopen( 'php://output', 'w' );
1136 1216 fputs( $df, chr( 0xEF ) . chr( 0xBB ) . chr( 0xBF ) ); // UTF-8 BOM
1137 1217 foreach ( $array as $data ) {
1138 1218 fputcsv( $df, array_keys( $data ), ',', '"', '\\' );
@@ -1141,8 +1221,9 @@
1141 1221 foreach ( $array as $row ) {
1142 1222 fputcsv( $df, $row, ',', '"', '\\' );
1143 1223 }
1144 1224 fclose( $df );
1225 + // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_fopen, WordPress.WP.AlternativeFunctions.file_system_operations_fputs, WordPress.WP.AlternativeFunctions.file_system_operations_fclose
1145 1226 return ob_get_clean();
1146 1227 }
1147 1228 }
1148 1229
@@ -1193,9 +1274,9 @@
1193 1274 if ( ! function_exists( 'qcld_wpsession_wp_cron_schedule_free' ) ) {
1194 1275 function qcld_wpsession_wp_cron_schedule_free( $schedules ) {
1195 1276 $schedules['session_schedules'] = array(
1196 1277 'interval' => ( get_option( 'qcld_wbsession_corn_interval' ) != null ) ? get_option( 'qcld_wbsession_corn_interval' ) : 86400,
1197 - 'display' => esc_attr( 'Session min', 'wpchatbot' ),
1278 + 'display' => esc_attr__( 'Session min', 'chatbot' ),
1198 1279 );
1199 1280 return $schedules;
1200 1281 }
1201 1282 }
@@ -1235,18 +1316,21 @@
1235 1316
1236 1317 $api_key = get_option( 'open_ai_api_key' );
1237 1318 $engines = get_option( 'openai_engines' );
1238 1319 $post_fields = array( 'model' => $engines, 'input' => $gptkeyword );
1239 - $header = array( 'Content-Type: application/json', 'Authorization: Bearer ' . $api_key );
1240 1320
1241 - $ch = curl_init();
1242 - curl_setopt( $ch, CURLOPT_URL, 'https://api.openai.com/v1/responses' );
1243 - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
1244 - curl_setopt( $ch, CURLOPT_POST, 1 );
1245 - curl_setopt( $ch, CURLOPT_POSTFIELDS, wp_json_encode( $post_fields ) );
1246 - curl_setopt( $ch, CURLOPT_HTTPHEADER, $header );
1247 - $result = curl_exec( $ch );
1248 - curl_close( $ch );
1321 + $api_response = wp_remote_post(
1322 + 'https://api.openai.com/v1/responses',
1323 + array(
1324 + 'headers' => array(
1325 + 'Content-Type' => 'application/json',
1326 + 'Authorization' => 'Bearer ' . $api_key,
1327 + ),
1328 + 'body' => wp_json_encode( $post_fields ),
1329 + 'timeout' => 60,
1330 + )
1331 + );
1332 + $result = is_wp_error( $api_response ) ? '' : wp_remote_retrieve_body( $api_response );
1249 1333
1250 1334 $mess = json_decode( $result );
1251 1335 $msg = isset( $mess->output[0]->content[0]->text ) ? $mess->output[0]->content[0]->text : ( isset( $mess->output[1]->content[0]->text ) ? $mess->output[1]->content[0]->text : 'No response from OpenAI.' );
1252 1336 $msg = preg_replace( "/\r\n|\r|\n/", '<br/>', $msg );