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 +1348 -1288 8.6.8 → 8.8.0 View file →
@@ -1,1288 +1,1348 @@
1 -<?php
2 -/**
3 - * WPBot Sessions & Analytics — Built-in Chat Session Module
4 - *
5 - * Provides full chat session recording, analytics, AI Insight, and
6 - * "Questions Not Answered" reporting natively in the free chatbot plugin.
7 - *
8 - * This module is a port of the Pro plugin's chat-session-addon.
9 - * It uses the SAME database table names (wpbot_user, wpbot_conversation,
10 - * wpbot_failed_response) so data is shared if the Pro addon is later activated.
11 - *
12 - * Everything is guarded with function_exists() checks so that when the Pro
13 - * addon IS active it takes full precedence and this code is skipped entirely.
14 - */
15 -
16 -if ( ! defined( 'ABSPATH' ) ) {
17 - exit;
18 -}
19 -
20 -// ─── Guard: Do not run if the Pro addon is active ────────────────────────────
21 -// The Pro addon defines qcwp_chat_session_menu_fnc(); if that function already
22 -// exists we skip everything here to avoid duplicate menus / conflicts.
23 -if ( function_exists( 'qcwp_chat_session_menu_fnc' ) ) {
24 - return;
25 -}
26 -
27 -// ─── Constants ────────────────────────────────────────────────────────────────
28 -// Define our own URL / path constants for assets.
29 -// Also define the legacy constant names that all copied report partials reference,
30 -// so those files work without modification.
31 -
32 -if ( ! defined( 'QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL' ) ) {
33 - define( 'QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
34 -}
35 -if ( ! defined( 'QCLD_CHATBOT_FREE_SESSION_DIR_PATH' ) ) {
36 - define( 'QCLD_CHATBOT_FREE_SESSION_DIR_PATH', plugin_dir_path( __FILE__ ) );
37 -}
38 -
39 -// Legacy alias constants — used by all copied partials / report files.
40 -if ( ! defined( 'QCLD_wpCHATBOT_HISTORY_PLUGIN_URL' ) ) {
41 - define( 'QCLD_wpCHATBOT_HISTORY_PLUGIN_URL', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL );
42 -}
43 -if ( ! defined( 'QCLD_WPCHATBOT_HISTORY_DIR_PATH' ) ) {
44 - define( 'QCLD_WPCHATBOT_HISTORY_DIR_PATH', QCLD_CHATBOT_FREE_SESSION_DIR_PATH );
45 -}
46 -
47 -// ─── Database Structure ───────────────────────────────────────────────────────
48 -require_once QCLD_CHATBOT_FREE_SESSION_DIR_PATH . 'inc/chatsession-db-structure.php';
49 -
50 -// ─── Admin Menu ───────────────────────────────────────────────────────────────
51 -add_action( 'admin_menu', 'qcwp_chat_session_menu_fnc_free' );
52 -
53 -function qcwp_chat_session_menu_fnc_free() {
54 -
55 - $capability = function_exists( 'qcld_wpbot_get_menu_capability' ) ? qcld_wpbot_get_menu_capability( 'sessions' ) : 'manage_options';
56 -
57 - if ( current_user_can( $capability ) ) {
58 -
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 - );
68 -
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 - );
77 -
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 - );
86 - }
87 -}
88 -
89 -// ─── Admin Scripts & Styles ───────────────────────────────────────────────────
90 -add_action( 'admin_enqueue_scripts', 'qcld_wb_chatbot_session_admin_scripts_free' );
91 -
92 -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;
108 - }
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 -
115 - if ( ! $is_session_page ) {
116 - return;
117 - }
118 -
119 - wp_register_style( 'qlcd-wp-bootstrap-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/qlcd-wp-bootstrap.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
120 - wp_enqueue_style( 'qlcd-wp-bootstrap-cs' );
121 -
122 - 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' );
123 - wp_enqueue_style( 'qlcd-wp-bootstrap-icons-cs' );
124 -
125 - wp_register_style( 'qlcd-wp-dataTables-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/qlcd-wp-dataTables.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
126 - wp_enqueue_style( 'qlcd-wp-dataTables-cs' );
127 -
128 - 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 - wp_enqueue_style( 'qlcd-wp-session-style-cs' );
130 -
131 - // SweetAlert2 — used by admin.js for Swal.fire() and Swal.showLoading()
132 - wp_register_script( 'qcld-wp-chatbot-sweetalrt-cs', QCLD_wpCHATBOT_PLUGIN_URL . 'js/sweetalrt.js', array( 'jquery' ), QCLD_wpCHATBOT_VERSION, true );
133 - wp_enqueue_script( 'qcld-wp-chatbot-sweetalrt-cs' );
134 -
135 - // admin.js depends on SweetAlert2 being loaded first
136 - wp_register_script( 'qcld-wp-session-admin-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'js/admin.js', array( 'jquery', 'qcld-wp-chatbot-sweetalrt-cs' ), QCLD_wpCHATBOT_VERSION, true );
137 - wp_enqueue_script( 'qcld-wp-session-admin-cs' );
138 - wp_localize_script(
139 - 'qcld-wp-session-admin-cs',
140 - 'ajax_object',
141 - array(
142 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
143 - 'ajax_nonce' => wp_create_nonce( 'wpbot_session_ajax_nonce' )
144 - )
145 - );
146 -
147 - wp_register_script( 'qcld-wp-dataTables-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'js/qcld-dataTables.min.js', array( 'jquery' ), QCLD_wpCHATBOT_VERSION, true );
148 - wp_enqueue_script( 'qcld-wp-dataTables-cs' );
149 -}
150 -
151 -
152 -// ─── AI Insight Page Callback ─────────────────────────────────────────────────
153 -function qcld_wpbot_schedule_session_reporting() {
154 - wp_register_style( 'qcld-wp-chatbot-history-style', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/history-style.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
155 - wp_enqueue_style( 'qcld-wp-chatbot-history-style' );
156 - ?>
157 - <div class="wrap wpbot-ai-insight-page">
158 - <div class="wpbot-ai-insight-header">
159 - <h2><?php echo esc_html__( 'AI Insight', 'chatbot' ); ?></h2>
160 - <p><?php echo esc_html__( 'Scheduled AI summaries of your chat conversations.', 'chatbot' ); ?></p>
161 - </div>
162 -
163 - <div class="wpbot-ai-insight-locked">
164 - <div class="wpbot-ai-insight-locked__icon" aria-hidden="true">
165 - <span class="dashicons dashicons-lock"></span>
166 - </div>
167 - <h3><?php echo esc_html__( 'Feature Locked', 'chatbot' ); ?></h3>
168 - <p class="wpbot-ai-insight-locked__desc">
169 - <?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' ); ?>
170 - </p>
171 - <p class="wpbot-ai-insight-locked__cta">
172 - <?php echo esc_html__( 'Please upgrade to WPBot Pro to unlock this feature!', 'chatbot' ); ?>
173 - </p>
174 - <a href="https://www.wpbot.pro/" target="_blank" rel="noopener noreferrer" class="wpbot-ai-insight-locked__btn">
175 - <?php echo esc_html__( 'Upgrade to Pro', 'chatbot' ); ?>
176 - </a>
177 - </div>
178 - </div>
179 - <?php
180 -}
181 -
182 -
183 -// ─── Questions Not Answered Page Callback ─────────────────────────────────────
184 -function qcld_wpbot_not_answered_question() {
185 - global $wpdb;
186 - $wpdb->show_errors = true; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
187 - $table = $wpdb->prefix . 'wpbot_failed_response'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
188 -
189 - if ( isset( $_GET['msg'] ) && $_GET['msg'] == 'success' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
190 - echo '<div class="notice notice-success"><p>Record has been Deleted Successfully!</p></div>';
191 - }
192 -
193 - if ( isset( $_GET['action'] ) && $_GET['action'] == 'deleteall' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
194 - $wpdb->query( "TRUNCATE TABLE `$table`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
195 - echo '<div class="notice notice-success"><p>All Records have been deleted successfully!</p></div>';
196 - }
197 -
198 - $sql = "SELECT * FROM $table WHERE 1 ORDER BY `id` DESC"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
199 - $sql1 = "SELECT count(*) FROM $table WHERE 1 ORDER BY `id` DESC"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
200 -
201 - $total = $wpdb->get_var( $sql1 ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
202 - $items_per_page = 30;
203 - $page = isset( $_GET['cpage'] ) ? abs( (int) $_GET['cpage'] ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
204 - $offset = ( $page * $items_per_page ) - $items_per_page;
205 - $sql .= " LIMIT {$offset}, {$items_per_page}";
206 - $result = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
207 - $totalPage = ceil( $total / $items_per_page );
208 - $customPagHTML = '';
209 - if ( $totalPage > 1 ) {
210 - $customPagHTML = '<div><span class="wpbot_pagination">Page ' . esc_html( $page ) . ' of ' . esc_html( $totalPage ) . '</span>' . paginate_links(
211 - array(
212 - 'base' => add_query_arg( 'cpage', '%#%' ),
213 - 'format' => '',
214 - 'prev_text' => __( '« prev' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
215 - 'next_text' => __( 'next »' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
216 - 'total' => esc_html( $totalPage ),
217 - 'current' => esc_html( $page ),
218 - )
219 - ) . '</div>';
220 - }
221 -
222 - wp_register_style( 'qcld-wp-chatbot-history-style', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/history-style.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
223 - wp_enqueue_style( 'qcld-wp-chatbot-history-style' );
224 - ?>
225 -
226 - <div class="sld_menu_title qcld_session_chat_menu_title">
227 - <h2><?php echo esc_html__( 'Questions Not Answered', 'chatbot' ) . ' (' . intval( $total ) . ')'; ?></h2>
228 - </div>
229 -
230 - <?php if ( $customPagHTML != '' ) : ?>
231 - <div class="sld_menu_title sld_menu_title_align"><?php echo wp_kses_post( $customPagHTML ); ?></div>
232 - <?php endif; ?>
233 -
234 - <?php
235 - require_once QCLD_CHATBOT_FREE_SESSION_DIR_PATH . 'reports/view/partials/questions-not-answered.php';
236 -}
237 -
238 -// ─── Main Chat Sessions Page Callback ────────────────────────────────────────
239 -function qc_wpbot_cs_menu_page_callback_func() {
240 -
241 - global $wpdb;
242 - $wpdb->show_errors = true; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
243 -
244 - $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
245 - $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
246 - $mainurl = admin_url( 'admin.php?page=wbcs-botsessions-page' );
247 -
248 - if ( isset( $_GET['min_interaction'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
249 - $mainurl .= '&min_interaction=' . intval( $_GET['min_interaction'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
250 - }
251 - if ( isset( $_GET['wp_user'] ) && $_GET['wp_user'] != '' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
252 - $mainurl .= '&wp_user=' . intval( $_GET['wp_user'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
253 - }
254 -
255 - $msg = '';
256 -
257 - if ( isset( $_GET['action'] ) && $_GET['action'] == 'deleteall' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
258 - $wpdb->query( "TRUNCATE TABLE `$tableuser`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
259 - $wpdb->query( "TRUNCATE TABLE `$tableconversation`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
260 - $msg = esc_html( 'All Sessions have been deleted successfully!' );
261 - }
262 -
263 - if ( isset( $_GET['msg'] ) && $_GET['msg'] == 'success' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
264 - echo '<div class="notice notice-success"><p>Record has been Deleted Successfully!</p></div>';
265 - }
266 -
267 - if ( isset( $_GET['userid'] ) && $_GET['userid'] != '' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
268 - require_once QCLD_CHATBOT_FREE_SESSION_DIR_PATH . 'reports/view/partials/view-single-chat.php';
269 - } else {
270 -
271 - wp_register_style( 'qcld-wp-chatbot-history-style', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/history-style.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
272 - wp_enqueue_style( 'qcld-wp-chatbot-history-style' );
273 - wp_register_style( 'qcld-wp-chatbot-jquery-ui', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/jqueryui.css', array(), '', 'screen' );
274 - wp_enqueue_style( 'qcld-wp-chatbot-jquery-ui' );
275 - wp_register_script( 'qcld-wp-chatsession-admin-js', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'js/chatsession.js', array( 'jquery' ), QCLD_wpCHATBOT_VERSION, true );
276 - wp_enqueue_script( 'qcld-wp-chatsession-admin-js' );
277 - wp_localize_script(
278 - 'qcld-wp-chatsession-admin-js',
279 - 'ajax_object',
280 - array(
281 - 'ajax_url' => admin_url( 'admin-ajax.php' ),
282 - 'ajax_nonce' => wp_create_nonce( 'wpbot_session_ajax_nonce' )
283 - )
284 - );
285 - wp_register_script( 'qcld-wp-jqueryui-js', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'js/jqueryui.js', array( 'jquery' ), QCLD_wpCHATBOT_VERSION, true );
286 - wp_enqueue_script( 'qcld-wp-jqueryui-js' );
287 -
288 - $where = '';
289 - if ( isset( $_GET['min_interaction'] ) && $_GET['min_interaction'] != 'all' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
290 - if ( isset( $_GET['min_interaction'] ) && $_GET['min_interaction'] > 0 ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
291 - $where = ' and `interaction` >= ' . intval( $_GET['min_interaction'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
292 - }
293 - if ( isset( $_GET['min_interaction'] ) && $_GET['min_interaction'] == 0 ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
294 - $where = ' and `interaction` = 0';
295 - }
296 - }
297 -
298 - $wwhere = '';
299 - if ( isset( $_GET['wp_user'] ) && $_GET['wp_user'] != 'all' && $_GET['wp_user'] != 0 && $_GET['wp_user'] != '' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
300 - $wwhere = ' and `user_id` = ' . intval( $_GET['wp_user'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
301 - }
302 -
303 - $sql = "SELECT * FROM $tableuser WHERE 1 $where $wwhere ORDER BY `date` DESC"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
304 - $sql1 = "SELECT count(*) FROM $tableuser WHERE 1 $where $wwhere"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
305 -
306 - $dateFilter = '';
307 - if ( isset( $_GET['FilterDate'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
308 - if ( $_GET['FilterDate'] === 'LastWeek' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
309 - $dateFilter = " WHERE `date` >= CURDATE() - INTERVAL 7 DAY";
310 - }
311 - if ( $_GET['FilterDate'] === 'LastMonth' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
312 - $dateFilter = " WHERE `date` >= CURDATE() - INTERVAL 30 DAY";
313 - }
314 - if ( $_GET['FilterDate'] === 'Last3Months' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
315 - $dateFilter = " WHERE `date` >= CURDATE() - INTERVAL 90 DAY";
316 - }
317 - $sql = "SELECT * FROM $tableuser $dateFilter $wwhere ORDER BY `date` DESC"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
318 - $sql1 = "SELECT count(*) FROM $tableuser $dateFilter"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
319 - }
320 -
321 - $total = $wpdb->get_var( $sql1 ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
322 - $items_per_page = 30;
323 - $page = isset( $_GET['cpage'] ) ? abs( (int) $_GET['cpage'] ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
324 - $offset = ( $page * $items_per_page ) - $items_per_page;
325 - $sql .= " LIMIT {$offset}, {$items_per_page}";
326 - $result = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
327 - $totalPage = ceil( $total / $items_per_page );
328 - $customPagHTML = '';
329 - if ( $totalPage > 1 ) {
330 - $customPagHTML = '<div class="qcld-session-pagination"><span class="wpbot_pagination">Page ' . esc_html( $page ) . ' of ' . esc_html( $totalPage ) . '</span>' . paginate_links(
331 - array(
332 - 'base' => add_query_arg( 'cpage', '%#%' ),
333 - 'format' => '',
334 - 'prev_text' => __( '« prev' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
335 - 'next_text' => __( 'next »' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
336 - 'total' => esc_html( $totalPage ),
337 - 'current' => esc_html( $page ),
338 - )
339 - ) . '</div>';
340 - }
341 -
342 - $deleteurl = admin_url( 'admin.php?page=wbcs-botsessions-page&action=deleteall' );
343 - ?>
344 -
345 - <div class="qchero_sliders_list_wrapper qcld-session-history_menu_box">
346 - <?php if ( $msg != '' ) : ?>
347 - <div class="notice notice-success is-dismissible">
348 - <p><?php echo esc_html( $msg ); ?></p>
349 - </div>
350 - <?php endif; ?>
351 -
352 - <div class="sld_menu_title qcld-session-history_menu_title">
353 - <h2><?php echo esc_html__( 'Chat Sessions', 'chatbot' ) . ' (' . intval( $total ) . ')'; ?></h2>
354 - </div>
355 -
356 - <div>
357 - <?php
358 - if ( isset( $_GET['FilterDate'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
359 - $filterText = '';
360 - if ( $_GET['FilterDate'] === 'LastWeek' ) { $filterText = 'LAST WEEK'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended
361 - if ( $_GET['FilterDate'] === 'LastMonth' ) { $filterText = 'LAST MONTH'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended
362 - if ( $_GET['FilterDate'] === 'Last3Months' ) { $filterText = 'LAST 3 MONTHS'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended
363 - echo '<div class="sld_menu_title"><em>Filtering Records by: <strong>' . esc_html( $filterText ) . '</strong></em></div>';
364 - }
365 - ?>
366 - </div>
367 -
368 -
369 - <form id="wpcs_form_sessions" action="<?php echo esc_url( $mainurl ); ?>" method="POST" style="width:98%">
370 - <?php wp_nonce_field( 'wpcs_bulk_action' ); ?>
371 - <input type="hidden" name="wpbot_session_remove" />
372 -
373 - <?php if ( ! empty( $result ) ) : ?>
374 - <?php require_once QCLD_CHATBOT_FREE_SESSION_DIR_PATH . 'reports/view/partials/chatsession-table.php'; ?>
375 - <?php else : ?>
376 - <div class="sld_menu_title"><h2>No result found.</h2></div>
377 - <?php endif; ?>
378 - </form>
379 - </div>
380 - <?php
381 - }
382 -}
383 -
384 -// ─── Request Handler (delete, export, redirect) ───────────────────────────────
385 -add_action( 'init', 'qc_wp_cs_request_handle_free' );
386 -
387 -function qc_wp_cs_request_handle_free() {
388 - if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
389 - return;
390 - }
391 -
392 - global $wpdb;
393 - $wpdb->show_errors = true; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
394 -
395 - $tableuser1 = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
396 - $tableconversation1 = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
397 - $table = $wpdb->prefix . 'wpbot_failed_response'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
398 -
399 - // Delete single "not answered" record.
400 - if ( isset( $_GET['page'] ) && $_GET['page'] == 'wbcs-botsessions-notansweredpage' && isset( $_GET['act'] ) && $_GET['act'] == 'delete' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
401 - $userid = intval( $_GET['id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
402 - check_admin_referer( 'wpcs_delete_session_' . $userid );
403 - $wpdb->delete( $table, array( 'id' => $userid ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
404 - wp_safe_redirect( admin_url( 'admin.php?page=wbcs-botsessions-notansweredpage&msg=success' ) );
405 - exit;
406 - }
407 -
408 - // Delete single chat session.
409 - if ( isset( $_GET['page'] ) && $_GET['page'] == 'wbcs-botsessions-page' && isset( $_GET['act'] ) && $_GET['act'] == 'delete' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
410 - $userid = intval( $_GET['userid'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
411 - check_admin_referer( 'wpcs_delete_session_' . $userid );
412 - $wpdb->delete( $tableuser1, array( 'id' => $userid ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
413 - $wpdb->delete( $tableconversation1, array( 'user_id' => $userid ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
414 - wp_safe_redirect( admin_url( 'admin.php?page=wbcs-botsessions-page&msg=success' ) );
415 - exit;
416 - }
417 -
418 - // Export all sessions as CSV.
419 - if ( isset( $_POST['wpbot_session_export_all'] ) && isset( $_POST['wpbot_session_remove'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
420 - check_admin_referer( 'wpcs_bulk_action' );
421 - $users = $wpdb->get_results( "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 LIMIT 5000" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
422 - $sessions = array();
423 - if ( ! empty( $users ) ) {
424 - foreach ( $users as $user ) {
425 - $sessions[] = wpbot_conversations_export( $user );
426 - }
427 - }
428 - qcld_wpbot_chatsession_download_send_headers( 'wpbot_chatsession_' . date( 'Y-m-d' ) . '.csv' );
429 - print wpbot_chatsession_array2csv( $sessions ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw CSV download, escaping would corrupt the file.
430 - exit;
431 - }
432 -
433 - // Export selected sessions or delete selected sessions.
434 - if ( isset( $_POST['wpbot_session_remove'] ) && ! empty( $_POST['sessions'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
435 - check_admin_referer( 'wpcs_bulk_action' );
436 - $userids = array_map( 'intval', $_POST['sessions'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
437 -
438 - if ( isset( $_POST['wpbot_session_export'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
439 - $sessions = array();
440 - foreach ( $userids as $userid ) {
441 - $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
442 - $sessions[] = wpbot_conversations_export( $user );
443 - }
444 - qcld_wpbot_chatsession_download_send_headers( 'wpbot_chatsession_' . date( 'Y-m-d' ) . '.csv' );
445 - print wpbot_chatsession_array2csv( $sessions ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw CSV download, escaping would corrupt the file.
446 - exit;
447 - }
448 -
449 - if ( isset( $_POST['wpbot_session_delete'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
450 - foreach ( $userids as $userid ) {
451 - $wpdb->delete( $tableuser1, array( 'id' => $userid ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
452 - $wpdb->delete( $tableconversation1, array( 'user_id' => $userid ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
453 - }
454 - wp_safe_redirect( admin_url( 'admin.php?page=wbcs-botsessions-page&msg=success' ) );
455 - exit;
456 - }
457 - }
458 -}
459 -
460 -// ─── Admin Footer: Email Modal ────────────────────────────────────────────────
461 -add_action( 'admin_footer', 'wpcs_admin_footer_content_free' );
462 -
463 -function wpcs_admin_footer_content_free() {
464 - if ( isset( $_GET['page'] ) && $_GET['page'] == 'wbcs-botsessions-page' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
465 - ?>
466 - <div id="wpcsmyModal" class="wpcsmodal">
467 - <div class="wpcsmodal-content">
468 - <span class="wpcsclose">&times;</span>
469 - <h2><?php echo esc_html( 'Send an Email to' ); ?> <span id="wpcs_show_email"></span></h2>
470 - <div class="wpcs_form_container">
471 - <form id="wpcs_email_form" action="">
472 - <label for="fname"><?php echo esc_html( 'Subject' ); ?></label>
473 - <input type="text" class="wpcs_text_field" id="wpcs_email_subject" name="wpcs_email_subject" placeholder="Subject.." required>
474 - <label for="lname"><?php echo esc_html( 'Your Message' ); ?></label>
475 - <textarea id="wpcs_email_message" class="wpcs_text_field" name="wpcs_email_message" placeholder="" style="height:200px" required></textarea>
476 - <input type="hidden" id="wpcs_to_email_address" value="" />
477 - <input type="submit" class="wpcs_submit_field" id="wpcs_email_submit" value="Submit">
478 - <span id="wpcs_email_loading" style="display:none;"><img style="width:20px;" src="<?php echo esc_url( QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'images/ajax-loader.gif' ); ?>"></span>
479 - <span id="wpcs_email_status"></span>
480 - </form>
481 - </div>
482 - </div>
483 - </div>
484 - <?php
485 - }
486 -}
487 -
488 -// ─── AJAX: Send Email to User ─────────────────────────────────────────────────
489 -add_action( 'wp_ajax_wpcs_send_email', 'wpcs_send_email' );
490 -
491 -function wpcs_send_email() {
492 - if ( ! current_user_can( 'manage_options' ) ) {
493 - wp_die();
494 - }
495 - check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
496 -
497 - $subject = sanitize_text_field( $_POST['data']['subject'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
498 - $message = sanitize_text_field( $_POST['data']['message'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
499 - $to = sanitize_email( $_POST['data']['to'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
500 -
501 - global $wpdb;
502 - $tableuser = $wpdb->prefix . 'wpbot_user';
503 - $user_exists = $wpdb->get_var( $wpdb->prepare( 'SELECT id FROM %i WHERE email = %s LIMIT 1', $tableuser, $to ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
504 - $admin_email = get_option('admin_email');
505 - if ( ! $user_exists && $to !== $admin_email ) {
506 - wp_send_json( array( 'status' => 'fail', 'message' => 'Invalid recipient address. Email must be a stored session email or admin email.' ) );
507 - }
508 -
509 - $url = get_site_url();
510 - $url = wp_parse_url( $url );
511 - $domain = $url['host'];
512 - $fromEmail = 'wordpress@' . $domain;
513 - $headers = array(
514 - 'Content-Type: text/html; charset=UTF-8',
515 - 'From: ' . esc_html( $domain ) . ' <' . esc_html( $fromEmail ) . '>',
516 - );
517 -
518 - $result = wp_mail( $to, $subject, $message, $headers );
519 - if ( $result ) {
520 - $response = array( 'status' => 'success', 'message' => 'Email has been sent successfully!' );
521 - } else {
522 - $response = array( 'status' => 'fail', 'message' => 'Unable to send email. Please contact your server administrator.' );
523 - }
524 - ob_clean();
525 - echo wp_json_encode( $response );
526 - die();
527 -}
528 -
529 -// ─── AJAX: Save Email Notification Preference ─────────────────────────────────
530 -add_action( 'wp_ajax_session_email_notification_update', 'session_email_notification_update_free' );
531 -
532 -function session_email_notification_update_free() {
533 - if ( ! current_user_can( 'manage_options' ) ) {
534 - wp_die();
535 - }
536 - $email_notification = sanitize_text_field( $_POST['email_notification'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
537 - update_option( 'session_email_notification_update', $email_notification );
538 - wp_send_json( array( 'success' => true ) );
539 -}
540 -
541 -// ─── AJAX: Conversation Save (Frontend) ──────────────────────────────────────
542 -// This is the main conversation-save handler. Guarded with function_exists
543 -// so the Pro addon's definition wins if it's active.
544 -if ( ! function_exists( 'qcld_wb_chatbot_conversation_save' ) ) {
545 -
546 - function qcld_wb_chatbot_conversation_save() {
547 -
548 - check_ajax_referer( 'qcsecretbotnonceval123qc', 'security' );
549 - global $wpdb;
550 -
551 - $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
552 - $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
553 -
554 - $allowed_html = array_merge(
555 - wp_kses_allowed_html( 'post' ),
556 - array(
557 - 'div' => array( 'class' => true, 'id' => true, 'style' => true, 'data-*' => true ),
558 - 'span' => array( 'class' => true, 'id' => true, 'style' => true, 'data-*' => true ),
559 - 'ul' => array( 'class' => true ),
560 - 'li' => array( 'class' => true ),
561 - 'img' => array( 'src' => true, 'alt' => true, 'class' => true, 'style' => true ),
562 - )
563 - );
564 - $raw_conversation = isset( $_POST['conversation'] ) ? wp_unslash( $_POST['conversation'] ) : '';
565 - $clean_conversation = wp_kses( $raw_conversation, $allowed_html );
566 - $conversation = qcld_wpbot_input_validation( $clean_conversation ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
567 - $email = isset( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
568 - $phone = isset( $_POST['phone'] ) ? sanitize_text_field( $_POST['phone'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
569 - $name = isset( $_POST['name'] ) ? sanitize_text_field( $_POST['name'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
570 - $session_id = isset( $_POST['session_id'] ) ? sanitize_text_field( $_POST['session_id'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
571 - $wpuser_id = isset( $_POST['user_id'] ) ? sanitize_text_field( $_POST['user_id'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
572 - $source_url = isset( $_POST['source_url'] ) && ! empty( $_POST['source_url'] ) ? sanitize_url( wp_unslash( $_POST['source_url'] ) ) : ( isset( $_SERVER['HTTP_REFERER'] ) ? sanitize_url( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
573 - $user_agent = isset( $_POST['user_agent'] ) && ! empty( $_POST['user_agent'] ) ? sanitize_text_field( wp_unslash( $_POST['user_agent'] ) ) : ( isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
574 - $session_mailed = isset( $_POST['session_mailed'] ) ? sanitize_text_field( $_POST['session_mailed'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
575 -
576 - // Prepend source URL to conversation.
577 - $conversation = '&#x3C;ul&#x3E;&#x3C;li class=&#x22;session_start_url&#x22;&#x3E;&#x3C;span&#x3E;Source URL: &#x3C;/span&#x3E;&#x3C;a href=&#x22;' . $source_url . '&#x22;&#x3E;' . $source_url . '&#x3C;/a&#x3E;&#x3C;/li&#x3E;&#x3C;/ul&#x3E;' . $conversation;
578 -
579 - $response = array();
580 - $response['status'] = 'success';
581 -
582 - $user_exists = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableuser WHERE 1 AND session_id = %s", $session_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
583 - $is_new_insert = false;
584 -
585 - if ( empty( $user_exists ) ) {
586 - $lock_key = 'wpcs_lock_' . md5( $session_id );
587 - if ( add_option( $lock_key, '1', '', 'no' ) ) {
588 - $interaction = (int) substr_count( $conversation, 'wp-chat-user-msg' );
589 - if ( $interaction == 0 ) {
590 - $interaction = (int) substr_count( $conversation, 'woo-chat-user-msg' );
591 - }
592 -
593 - if ( $interaction != 0 ) {
594 - $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
595 - $tableuser,
596 - array(
597 - 'date' => current_time( 'mysql' ),
598 - 'name' => $name,
599 - 'email' => $email,
600 - 'phone' => $phone,
601 - 'session_id' => $session_id,
602 - 'interaction' => $interaction,
603 - 'user_id' => $wpuser_id,
604 - )
605 - );
606 - $user_id = $wpdb->insert_id; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
607 - $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
608 - $tableconversation,
609 - array(
610 - 'user_id' => $user_id,
611 - 'conversation' => $conversation,
612 - 'interaction' => $interaction,
613 - 'environment_info' => $user_agent,
614 - )
615 - );
616 - $is_new_insert = true;
617 - }
618 - delete_option( $lock_key );
619 - } else {
620 - $retries = 3;
621 - while ( $retries > 0 ) {
622 - usleep( 500000 );
623 - $user_exists = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableuser WHERE 1 AND session_id = %s", $session_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
624 - if ( ! empty( $user_exists ) ) {
625 - break;
626 - }
627 - $retries--;
628 - }
629 - }
630 - }
631 -
632 - if ( ! $is_new_insert && ! empty( $user_exists ) ) {
633 - $interaction = (int) substr_count( $conversation, 'wp-chat-user-msg' );
634 - if ( $interaction == 0 ) {
635 - $interaction = (int) substr_count( $conversation, 'woo-chat-user-msg' );
636 - }
637 -
638 - $user_id = isset( $user_exists->id ) ? $user_exists->id : get_current_user_id();
639 - $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
640 - $tableuser,
641 - array(
642 - 'date' => current_time( 'mysql' ),
643 - 'name' => $name,
644 - 'email' => $email,
645 - 'phone' => $phone,
646 - 'interaction' => $interaction,
647 - 'user_id' => $wpuser_id,
648 - ),
649 - array( 'id' => $user_id ),
650 - array( '%s', '%s', '%s', '%s', '%d', '%d' ),
651 - array( '%d' )
652 - );
653 - $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
654 - $tableconversation,
655 - array(
656 - 'conversation' => $conversation,
657 - 'interaction' => $interaction,
658 - ),
659 - array( 'user_id' => $user_id ),
660 - array( '%s', '%d' ),
661 - array( '%d' )
662 - );
663 - }
664 -
665 - // Email notification for new session.
666 - if ( $is_new_insert && ( get_option( 'session_email_notification_update' ) == 'checked' ) ) {
667 - $admin_email = get_option( 'admin_email' );
668 - $subject = esc_html__( 'Someone has started a new chat session with ChatBot.', 'chatbot' );
669 - $bodyContent = '<p>' . esc_html__( 'Hi,', 'chatbot' ) . '</p>';
670 - $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>';
671 -
672 - $bodyContent .= '<ul>';
673 - $bodyContent .= '<li>' . esc_html__( 'Session ID:', 'chatbot' ) . ' <strong>' . esc_html( $session_id ) . '</strong></li>';
674 - if ( ! empty( $email ) ) {
675 - $bodyContent .= '<li>' . esc_html__( 'Email:', 'chatbot' ) . ' <strong>' . esc_html( $email ) . '</strong></li>';
676 - }
677 - if ( ! empty( $phone ) ) {
678 - $bodyContent .= '<li>' . esc_html__( 'Phone:', 'chatbot' ) . ' <strong>' . esc_html( $phone ) . '</strong></li>';
679 - }
680 - if ( ! empty( $source_url ) ) {
681 - $bodyContent .= '<li>' . esc_html__( 'Page Link:', 'chatbot' ) . ' <strong><a href="' . esc_url( $source_url ) . '">' . esc_html( $source_url ) . '</a></strong></li>';
682 - }
683 - if ( ! empty( $user_agent ) ) {
684 - $bodyContent .= '<li>' . esc_html__( 'Browser:', 'chatbot' ) . ' <strong>' . esc_html( $user_agent ) . '</strong></li>';
685 - }
686 - $bodyContent .= '</ul>';
687 -
688 - $bodyContent .= '<p>' . esc_html__( 'Thanks', 'chatbot' ) . '</p>';
689 - $bodyContent .= '<p>' . esc_html__( '(You can disable email notifications from ', 'chatbot' ) . '<a href="' . admin_url() . 'admin.php?page=wbcs-botsessions-page">' . esc_html__( 'Bot - Sessions)', 'chatbot' ) . '</a></p>';
690 -
691 - $to = get_option( 'qlcd_wp_chatbot_admin_email' ) != '' ? get_option( 'qlcd_wp_chatbot_admin_email' ) : $admin_email;
692 - $headers = array( 'Content-Type: text/html; charset=UTF-8' );
693 - wp_mail( $to, $subject, $bodyContent, $headers );
694 - }
695 -
696 - // WPBot Automator Trigger - Debounced by 3 minutes
697 - $cron_args = array( $session_id );
698 - if ( wp_next_scheduled( 'wpbot_automator_delayed_trigger', $cron_args ) ) {
699 - wp_clear_scheduled_hook( 'wpbot_automator_delayed_trigger', $cron_args );
700 - }
701 - wp_schedule_single_event( time() + 60, 'wpbot_automator_delayed_trigger', $cron_args );
702 -
703 - echo wp_json_encode( $response );
704 - die();
705 - }
706 -}
707 -add_action( 'wp_ajax_qcld_wb_chatbot_conversation_save', 'qcld_wb_chatbot_conversation_save' );
708 -add_action( 'wp_ajax_nopriv_qcld_wb_chatbot_conversation_save', 'qcld_wb_chatbot_conversation_save' );
709 -
710 -// ─── AJAX: Date Filter ────────────────────────────────────────────────────────
711 -add_action( 'wp_ajax_qcld_chatbot_session_date_filter', 'qcld_chatbot_session_date_filter_free' );
712 -
713 -function qcld_chatbot_session_date_filter_free() {
714 - if ( ! current_user_can( 'manage_options' ) ) {
715 - wp_die();
716 - }
717 - check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
718 - global $wpdb;
719 - $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
720 - $start_date = sanitize_text_field( $_POST['start_date'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
721 - $end_date = sanitize_text_field( $_POST['end_date'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
722 - $result = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $tableuser WHERE date BETWEEN %s AND %s", $start_date, $end_date ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
723 - echo wp_json_encode( $result );
724 - wp_die();
725 -}
726 -
727 -// ─── AJAX: Email Transcript ───────────────────────────────────────────────────
728 -add_action( 'wp_ajax_wpbot_send_email_transcript', 'wpbot_send_email_transcript_free' );
729 -
730 -function wpbot_send_email_transcript_free() {
731 - if ( ! current_user_can( 'manage_options' ) ) {
732 - wp_send_json( array( 'status' => 'fail', 'message' => 'Unauthorized' ) );
733 - }
734 - check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
735 -
736 - global $wpdb;
737 -
738 - $session = trim( sanitize_text_field( $_POST['session'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
739 -
740 - $url = wp_parse_url( get_site_url() );
741 - $domain = $url['host'];
742 - $admin_email = get_option( 'admin_email' );
743 - $fromEmail = get_option( 'qlcd_wp_chatbot_from_email' ) ? get_option( 'qlcd_wp_chatbot_from_email' ) : 'wordpress@' . $domain;
744 - $subject = 'Chat transcript by ' . get_bloginfo( 'name' );
745 -
746 - $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
747 - $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
748 -
749 - $user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableuser WHERE 1 AND session_id = %s", $session ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
750 -
751 - $response = array( 'status' => 'fail', 'message' => 'Session not found.' );
752 -
753 - if ( ! empty( $user ) ) {
754 - $email = sanitize_email( $user->email ); // Use email from user record
755 - if ( empty( $email ) ) {
756 - wp_send_json( array( 'status' => 'fail', 'message' => 'User has no email.' ) );
757 - }
758 -
759 - $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableconversation WHERE 1 AND user_id = %d", $user->id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
760 - $bodyContent = '';
761 - $bodyContent .= '<p><strong>' . esc_html__( 'User Details', 'chatbot' ) . ':</strong></p><hr>';
762 - $bodyContent .= '<p>' . esc_html__( 'Name', 'chatbot' ) . ' : ' . esc_html( $user->name ) . '</p>';
763 - $bodyContent .= '<p>' . esc_html__( 'Email', 'chatbot' ) . ' : ' . esc_html( $email ) . '</p>';
764 - $bodyContent .= '<p><b>Conversations</b></p><p>-----------------------</p>';
765 - $messages = qcld_wpch_conversation_extract( htmlspecialchars_decode( $result->conversation ) );
766 - foreach ( $messages as $message ) {
767 - if ( isset( $message['bot'] ) && trim( $message['bot'] ) != '' ) {
768 - $bodyContent .= '<p>Chatbot : ' . esc_html( trim( $message['bot'] ) ) . '</p>';
769 - }
770 - if ( isset( $message['user'] ) && trim( $message['user'] ) != '' ) {
771 - $bodyContent .= '<p>' . esc_html( $user->name ) . ' : ' . esc_html( trim( $message['user'] ) ) . '</p>';
772 - }
773 - }
774 - $bodyContent .= '<p>-----------------------</p>';
775 - $bodyContent .= '<p>Mail Generated on: ' . current_time( 'F j, Y, g:i a' ) . '</p>';
776 - $headers = array(
777 - 'Content-Type: text/html; charset=UTF-8',
778 - 'From: ' . esc_html( $user->name ) . ' <' . esc_html( $fromEmail ) . '>',
779 - 'Reply-To: ' . esc_html( $user->name ) . ' <' . esc_html( $email ) . '>',
780 - );
781 - $result_mail = wp_mail( $email, $subject, $bodyContent, $headers );
782 - if ( $result_mail ) {
783 - $response = array( 'status' => 'success', 'message' => 'Email transcript sent successfully.' );
784 - }
785 - }
786 - echo wp_json_encode( $response );
787 - die();
788 -}
789 -
790 -// ─── AJAX: Forward Session to Email ──────────────────────────────────────────
791 -add_action( 'wp_ajax_forward_session_to_email', 'forward_session_to_email_free' );
792 -
793 -function forward_session_to_email_free() {
794 - if ( ! current_user_can( 'manage_options' ) ) {
795 - wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Insufficient permissions', 'chatbot' ) ) );
796 - wp_die();
797 - }
798 - check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
799 - global $wpdb;
800 -
801 - $session_id = sanitize_text_field( $_POST['session_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
802 - $to = sanitize_email( $_POST['email'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
803 - $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
804 - $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
805 -
806 - $userinfo = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableuser WHERE 1 AND session_id = %s", $session_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
807 - $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableconversation WHERE 1 AND user_id = %d", $userinfo->id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
808 -
809 - if ( ! empty( $result ) ) {
810 - $raw_html = isset( $result->conversation ) ? (string) $result->conversation : '';
811 - $decoded_content = html_entity_decode( $raw_html );
812 - $doc = new DOMDocument();
813 - libxml_use_internal_errors( true );
814 - $doc->loadHTML( '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $decoded_content );
815 - libxml_clear_errors();
816 -
817 - $decoded_content = '';
818 - $body_nodes = $doc->getElementsByTagName( 'body' );
819 - if ( $body_nodes->length > 0 ) {
820 - foreach ( $body_nodes->item( 0 )->childNodes as $child_node ) {
821 - $decoded_content .= $doc->saveHTML( $child_node );
822 - }
823 - }
824 -
825 - $email_body = '<!DOCTYPE html><html><head><style>
826 - .wp-chatbot-messages-container { list-style: none; padding: 20px; background: #f4f7f6; font-family: sans-serif; }
827 - .wp-chatbot-msg { margin-bottom: 15px; display: flex; flex-wrap: wrap; }
828 - .wp-chatbot-agent { font-weight: bold; color: #333; display: block; margin-bottom: 4px; }
829 - .wp-chatbot-paragraph { background: #ffffff; padding: 10px; border-radius: 8px; border: 1px solid #ddd; flex: 1; }
830 - .wp-chat-user-msg { display: flex; flex-wrap: wrap; flex-direction: row-reverse; }
831 - .wp-chat-user-msg .wp-chatbot-paragraph { background: #ffffff; padding: 10px; border-radius: 8px; border: 1px solid #ddd; flex: none; }
832 - body ul { width:100%; max-width: 640px; list-style: none; padding: 0; margin: 0 auto; }
833 - </style></head><body>' . $decoded_content . '</body></html>';
834 -
835 - $subject = isset( $_POST['subject'] ) ? sanitize_text_field( wp_unslash( $_POST['subject'] ) ) : 'Chat Session Transcript'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
836 - if ( empty( $subject ) ) { $subject = 'Chat Session Transcript'; }
837 - $headers = array( 'Content-Type: text/html; charset=UTF-8' );
838 - wp_mail( $to, $subject, $email_body, $headers );
839 - wp_send_json( array( 'success' => true, 'msg' => esc_html__( 'Session has been forwarded to email successfully', 'chatbot' ) ) );
840 - wp_die();
841 - } else {
842 - wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'No conversation found for this session', 'chatbot' ) ) );
843 - wp_die();
844 - }
845 -}
846 -
847 -// ─── AJAX: Session Hover Details ─────────────────────────────────────────────
848 -add_action( 'wp_ajax_wpbot_session_hover_details', 'wpbot_session_hover_details_free' );
849 -
850 -function wpbot_session_hover_details_free() {
851 - if ( ! current_user_can( 'manage_options' ) ) {
852 - wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Insufficient permissions', 'chatbot' ) ) );
853 - wp_die();
854 - }
855 - check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
856 - global $wpdb;
857 - $session_id = sanitize_text_field( $_POST['session_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
858 - $tableconversation = $wpdb->prefix . 'wpbot_conversation';
859 - $tableuser = $wpdb->prefix . 'wpbot_user';
860 - $email_from = get_option( 'qlcd_wp_chatbot_from_email' );
861 - $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
862 - if ( ! empty( $result ) ) {
863 - $result->email_from = $email_from;
864 - $result->status = 'success';
865 - }
866 - echo wp_json_encode( $result );
867 - wp_die();
868 -}
869 -
870 -// ─── AJAX: Send Reply Email ───────────────────────────────────────────────────
871 -add_action( 'wp_ajax_wpbot_send_reply_email', 'wpbot_send_reply_email_free' );
872 -
873 -function wpbot_send_reply_email_free() {
874 - if ( ! current_user_can( 'manage_options' ) ) {
875 - wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Insufficient permissions', 'chatbot' ) ) );
876 - wp_die();
877 - }
878 - check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
879 -
880 - $to = isset( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
881 - $from_raw = isset( $_POST['from_email'] ) ? trim( wp_unslash( $_POST['from_email'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
882 - $from = sanitize_email( $from_raw );
883 - $subject = isset( $_POST['subject'] ) ? sanitize_text_field( wp_unslash( $_POST['subject'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
884 - $message = isset( $_POST['message'] ) ? wp_kses_post( wp_unslash( $_POST['message'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
885 -
886 - if ( empty( $to ) || empty( $message ) ) {
887 - wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Email and Message are required', 'chatbot' ) ) );
888 - wp_die();
889 - }
890 -
891 - // if ( ! empty( $from_raw ) && ( $from_raw !== $from || ! is_email( $from ) ) ) {
892 - // wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Please enter a valid From email address', 'chatbot' ) ) );
893 - // wp_die();
894 - // }
895 -
896 - $headers = array( 'Content-Type: text/html; charset=UTF-8' );
897 - if ( ! empty( $from ) && is_email( $from ) ) {
898 - $headers[] = 'From: ' . $from;
899 - }
900 -
901 - // Convert newlines to HTML line breaks
902 - $email_body = nl2br( $message );
903 -
904 - $sent = wp_mail( $to, $subject, $email_body, $headers );
905 -
906 - if ( $sent ) {
907 - wp_send_json( array( 'success' => true, 'msg' => esc_html__( 'Email replied successfully', 'chatbot' ) ) );
908 - } else {
909 - wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Failed to send email', 'chatbot' ) ) );
910 - }
911 - wp_die();
912 -}
913 -
914 -// ─── AJAX: Save Cron Settings ─────────────────────────────────────────────────
915 -add_action( 'wp_ajax_wpbot_seesion_corn_save', 'wpbot_seesion_corn_save_free' );
916 -
917 -function wpbot_seesion_corn_save_free() {
918 - if ( ! current_user_can( 'manage_options' ) ) {
919 - wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Insufficient permissions', 'chatbot' ) ) );
920 - wp_die();
921 - }
922 -
923 - $wbsession_ai_enabled = isset( $_POST['ai_enabled'] ) ? sanitize_text_field( $_POST['ai_enabled'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
924 - $wbsession_corn_schedule_interval = isset( $_POST['corn_schedule_interval'] ) ? sanitize_text_field( $_POST['corn_schedule_interval'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
925 - $qcld_wpsession_corn_promt = isset( $_POST['qcld_wpsession_corn_promt'] ) ? sanitize_text_field( $_POST['qcld_wpsession_corn_promt'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
926 - $qcld_wbsession_corn_starttime = isset( $_POST['qcld_wbsession_corn_starttime'] ) ? sanitize_text_field( $_POST['qcld_wbsession_corn_starttime'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
927 -
928 - $openai_enabled = get_option( 'ai_enabled' );
929 - $apiKey = get_option( 'open_ai_api_key' );
930 -
931 - if ( $openai_enabled != '1' ) {
932 - wp_send_json( array( 'success' => false, 'icon' => 'error', 'response' => esc_html__( 'OpenAI is Not Enabled', 'chatbot' ) ) );
933 - wp_die();
934 - }
935 - if ( $apiKey == '' ) {
936 - wp_send_json( array( 'success' => false, 'icon' => 'error', 'response' => esc_html__( 'OpenAI API key is not set', 'chatbot' ) ) );
937 - wp_die();
938 - }
939 -
940 - update_option( 'qcld_wbsession_ai_enable', $wbsession_ai_enabled );
941 - update_option( 'qcld_wbsession_corn_interval', $wbsession_corn_schedule_interval );
942 - update_option( 'qcld_wbsession_corn_starttime', $qcld_wbsession_corn_starttime );
943 - update_option( 'qcld_wpsession_corn_promt', $qcld_wpsession_corn_promt );
944 -
945 - wp_clear_scheduled_hook( 'qcld_wpsession_mysql_scraper_event' );
946 - wp_send_json( array( 'success' => true, 'icon' => 'success', 'response' => esc_html__( 'Settings Saved Successfully', 'chatbot' ) ) );
947 - wp_die();
948 -}
949 -
950 -// ─── AJAX: Manual AI Scraper ──────────────────────────────────────────────────
951 -add_action( 'wp_ajax_qcld_chatbot_session_mannual_scraper', 'qcld_chatbot_session_mannual_scraper_free' );
952 -
953 -if ( ! function_exists( 'qcld_chatbot_session_mannual_scraper_free' ) ) {
954 - function qcld_chatbot_session_mannual_scraper_free() {
955 - if ( ! current_user_can( 'manage_options' ) ) {
956 - wp_send_json_error( array( 'msg' => 'Insufficient permissions.' ) );
957 - wp_die();
958 - }
959 - if ( get_option( 'qcld_wbsession_ai_enable' ) != '1' ) {
960 - wp_send_json( array( 'success' => false, 'icon' => 'error', 'response' => esc_html__( 'AI Insight is not enabled.', 'chatbot' ) ) );
961 - wp_die();
962 - }
963 -
964 - global $wpdb;
965 - $num = isset( $_POST['wpchatbot_session_mannual_number'] ) ? intval( $_POST['wpchatbot_session_mannual_number'] ) : 20; // phpcs:ignore WordPress.Security.NonceVerification.Missing
966 - if ( ! is_numeric( $num ) || $num <= 0 ) {
967 - wp_send_json( array( 'success' => false, 'icon' => 'error', 'response' => esc_html__( 'Invalid number of sessions.', 'chatbot' ) ) );
968 - wp_die();
969 - }
970 -
971 - $tableuser = $wpdb->prefix . 'wpbot_user';
972 - $tableconversation = $wpdb->prefix . 'wpbot_conversation';
973 - $results = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
974 - "SELECT u.id, c.user_id, u.date, u.session_id, c.id AS conversation_id, c.conversation
975 - FROM $tableuser AS u LEFT JOIN $tableconversation AS c ON u.id = c.user_id
976 - ORDER BY u.date DESC LIMIT %d", $num ) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
977 - );
978 -
979 - $remarkable_session = array();
980 - foreach ( $results as $row ) {
981 - $trimmed = wpsession_message_html_filter_free( htmlspecialchars_decode( $row->conversation ) );
982 - $remarkable_session[] = array( 'id' => $row->session_id, 'conversation' => $trimmed );
983 - }
984 -
985 - $keyword = get_option( 'qcld_wpsession_corn_promt' ) ?: 'Below is Chat conversation session data from our users on our website. Each conversation starts with an ID. Can you analyze each conversation and summarize each of them? Note down the total number of conversations you analyzed. Create a condensed summary at the end of your report for all the conversations. Point out the important questions asked by the users below the summary and include the IDs for the important points.';
986 - $gptkeyword = array(
987 - array( 'role' => 'system', 'content' => array( array( 'type' => 'input_text', 'text' => $keyword ) ) ),
988 - array( 'role' => 'user', 'content' => array( array( 'type' => 'input_text', 'text' => wp_json_encode( $remarkable_session ) ) ) ),
989 - );
990 -
991 - $api_key = get_option( 'open_ai_api_key' );
992 - $engines = get_option( 'openai_engines' );
993 - $post_fields = array( 'model' => $engines, 'input' => $gptkeyword );
994 - $header = array( 'Content-Type: application/json', 'Authorization: Bearer ' . $api_key );
995 -
996 - $ch = curl_init();
997 - curl_setopt( $ch, CURLOPT_URL, 'https://api.openai.com/v1/responses' );
998 - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
999 - curl_setopt( $ch, CURLOPT_POST, 1 );
1000 - curl_setopt( $ch, CURLOPT_POSTFIELDS, wp_json_encode( $post_fields ) );
1001 - curl_setopt( $ch, CURLOPT_HTTPHEADER, $header );
1002 - $result = curl_exec( $ch );
1003 - curl_close( $ch );
1004 -
1005 - $mess = json_decode( $result );
1006 - if ( ! empty( $mess->error ) ) {
1007 - wp_send_json( array( 'status' => 'error', 'icon' => 'error', 'msg' => esc_html( $mess->error->code ), 'response' => esc_html( $mess->error->message ) ) );
1008 - wp_die();
1009 - }
1010 -
1011 - $msg = isset( $mess->output[0]->content ) ? $mess->output[0]->content[0]->text : ( $mess->output[1]->content[0]->text ?? '' );
1012 - $msg = preg_replace( "/\r\n|\r|\n/", '<br/>', $msg );
1013 -
1014 - $to = get_option( 'qlcd_wp_chatbot_admin_email' ) ?: get_option( 'admin_email' );
1015 - $headers = array(
1016 - 'Content-Type: text/html; charset=UTF-8',
1017 - 'From: ' . esc_html( get_bloginfo( 'name' ) ) . ' <wordpress@' . wp_parse_url( get_site_url(), PHP_URL_HOST ) . '>',
1018 - );
1019 - wp_mail( $to, 'ChatBot Sessions Analysis', $msg, $headers );
1020 -
1021 - wp_send_json( array( 'status' => 'success', 'icon' => 'success', 'response' => 'Please Check Email for Report' ) );
1022 - wp_die();
1023 - }
1024 -}
1025 -
1026 -// ─── Helper: HTML filter for AI scraper ──────────────────────────────────────
1027 -if ( ! function_exists( 'wpsession_message_html_filter_free' ) ) {
1028 - function wpsession_message_html_filter_free( $html ) {
1029 - $dom = new DOMDocument();
1030 - libxml_use_internal_errors( true );
1031 - $dom->loadHTML( '<?xml encoding="utf-8" ?>' . $html );
1032 -
1033 - $lis = $dom->getElementsByTagName( 'li' );
1034 - $full_conversation = array();
1035 - foreach ( $lis as $li ) {
1036 - $agentDiv = $li->getElementsByTagName( 'div' )->item( 1 );
1037 - $paragraphDiv = $li->getElementsByTagName( 'div' )->item( 2 );
1038 - if ( $paragraphDiv ) {
1039 - $full_conversation[] = array(
1040 - 'id' => trim( $agentDiv->textContent ),
1041 - 'conversation' => trim( $paragraphDiv->textContent ),
1042 - );
1043 - }
1044 - }
1045 - return wp_json_encode( $full_conversation );
1046 - }
1047 -}
1048 -
1049 -// ─── Helper: Conversation Extract ────────────────────────────────────────────
1050 -if ( ! function_exists( 'qcld_wpch_conversation_extract' ) ) {
1051 - function qcld_wpch_conversation_extract( $html ) {
1052 - $doc = new DOMDocument();
1053 - libxml_use_internal_errors( true );
1054 - $doc->loadHTML( '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $html );
1055 - $lis = iterator_to_array( $doc->getElementsByTagName( 'li' ) );
1056 - $messages = array();
1057 - foreach ( $lis as $li ) {
1058 - if ( strpos( $li->getAttribute( 'class' ), 'wp-chatbot-msg' ) !== false ) {
1059 - $messages[]['bot'] = trim( $li->textContent );
1060 - }
1061 - if ( strpos( $li->getAttribute( 'class' ), 'wp-chat-user-msg' ) !== false ) {
1062 - $messages[]['user'] = trim( $li->textContent );
1063 - }
1064 - }
1065 - $messages = array_filter( $messages, function( $val ) {
1066 - if ( isset( $val['bot'] ) && empty( $val['bot'] ) ) { return false; }
1067 - return true;
1068 - } );
1069 - return $messages;
1070 - }
1071 -}
1072 -
1073 -// ─── CSV Export Helpers ───────────────────────────────────────────────────────
1074 -add_action( 'admin_post_wpbot_conversations.csv', 'wpbot_conversations_csv_export_free' );
1075 -
1076 -function wpbot_conversations_csv_export_free() {
1077 - if ( ! current_user_can( 'manage_options' ) ) {
1078 - wp_die( esc_html__( 'Unauthorized', 'wpbot' ) );
1079 - }
1080 -
1081 - if ( ! isset( $_GET['_wpnonce'] ) || ! wp_verify_nonce( sanitize_key( $_GET['_wpnonce'] ), 'wpbot_conversations_csv' ) ) {
1082 - wp_die( esc_html__( 'Security check failed.', 'wpbot' ) );
1083 - }
1084 -
1085 - global $wpdb;
1086 - $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
1087 - $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
1088 - $userid = sanitize_text_field( $_GET['user_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1089 -
1090 - $userinfo = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableuser WHERE 1 AND id = %d", $userid ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
1091 - $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableconversation WHERE 1 AND user_id = %d", $userid ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
1092 - $data = array();
1093 -
1094 - if ( ! empty( $result ) ) {
1095 - $data[] = array( 'User Name', $userinfo->name );
1096 - $data[] = array( 'User Email', $userinfo->email );
1097 - $data[] = array( 'Session ID', $userinfo->session_id );
1098 - $data[] = array( 'Date', date( 'M,d,Y h:i:s A', strtotime( $userinfo->date ) ) );
1099 - $data[] = array( 'Bot Message', 'User Message' );
1100 - $messages = qcld_wpch_conversation_extract( htmlspecialchars_decode( $result->conversation ) );
1101 - foreach ( $messages as $message ) {
1102 - if ( isset( $message['bot'] ) && trim( $message['bot'] ) != '' ) {
1103 - $data[] = array( str_replace( '&nbsp;', ' ', trim( $message['bot'] ) ), '' );
1104 - }
1105 - if ( isset( $message['user'] ) && trim( $message['user'] ) != '' ) {
1106 - $data[] = array( '', str_replace( '&nbsp;', ' ', trim( $message['user'] ) ) );
1107 - }
1108 - }
1109 - }
1110 - qcld_wpbot_chatsession_download_send_headers( $userinfo->name . '_wpbot_chatsession_' . date( 'Y-m-d' ) . '.csv' );
1111 - print wpbot_chatsession_array2csv( $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw CSV download, escaping would corrupt the file.
1112 -}
1113 -
1114 -if ( ! function_exists( 'wpbot_conversations_export' ) ) {
1115 - function wpbot_conversations_export( $user ) {
1116 - $user_id = isset( $user->id ) ? $user->id : $user;
1117 - $dataArray = array();
1118 - if ( ! empty( $user ) ) {
1119 - $messages = qcld_wpch_conversation_extract( htmlspecialchars_decode( $user->conversation ) );
1120 - $dataArray = array(
1121 - 'Session ID' => $user->session_id,
1122 - 'Date' => date( 'M,d,Y h:i:s A', strtotime( $user->date ) ),
1123 - 'User Name' => $user->name,
1124 - 'User Email' => $user->email,
1125 - );
1126 - $conversations = '';
1127 - foreach ( $messages as $message ) {
1128 - if ( isset( $message['bot'] ) && trim( $message['bot'] ) != '' ) {
1129 - $conversations .= 'Bot Message: ' . str_replace( '&nbsp;', ' ', trim( $message['bot'] ) ) . "\n";
1130 - }
1131 - if ( isset( $message['user'] ) && trim( $message['user'] ) != '' ) {
1132 - $conversations .= 'User Message: ' . str_replace( '&nbsp;', ' ', trim( $message['user'] ) ) . "\n";
1133 - }
1134 - }
1135 - $dataArray['Conversations'] = $conversations;
1136 - }
1137 - $dataArray['Interaction'] = $user->interaction;
1138 - return $dataArray;
1139 - }
1140 -}
1141 -
1142 -if ( ! function_exists( 'qcld_wpbot_chatsession_download_send_headers' ) ) {
1143 - function qcld_wpbot_chatsession_download_send_headers( $filename ) {
1144 - $now = gmdate( 'D, d M Y H:i:s' );
1145 - header( 'Expires: Tue, 03 Jul 2001 06:00:00 GMT' );
1146 - header( 'Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate' );
1147 - header( "Last-Modified: {$now} GMT" );
1148 - header( 'Content-Encoding: UTF-8' );
1149 - header( 'Content-type: text/csv; charset=UTF-8' );
1150 - header( "Content-Disposition: attachment;filename={$filename}" );
1151 - header( 'Content-Transfer-Encoding: binary' );
1152 - }
1153 -}
1154 -
1155 -if ( ! function_exists( 'wpbot_chatsession_array2csv' ) ) {
1156 - function wpbot_chatsession_array2csv( array &$array ) {
1157 - if ( count( $array ) == 0 ) { return null; }
1158 - ob_start();
1159 - $df = fopen( 'php://output', 'w' );
1160 - fputs( $df, chr( 0xEF ) . chr( 0xBB ) . chr( 0xBF ) ); // UTF-8 BOM
1161 - foreach ( $array as $data ) {
1162 - fputcsv( $df, array_keys( $data ), ',', '"', '\\' );
1163 - break;
1164 - }
1165 - foreach ( $array as $row ) {
1166 - fputcsv( $df, $row, ',', '"', '\\' );
1167 - }
1168 - fclose( $df );
1169 - return ob_get_clean();
1170 - }
1171 -}
1172 -
1173 -// ─── Shortcode: User Session History ─────────────────────────────────────────
1174 -if ( ! function_exists( 'qc_current_user_session' ) ) {
1175 - function qc_current_user_session() {
1176 - $user = wp_get_current_user();
1177 - global $wpdb;
1178 - $tableuser = $wpdb->prefix . 'wpbot_user';
1179 - $conversatios_table = $wpdb->prefix . 'wpbot_conversation';
1180 - $result = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $tableuser AS u LEFT JOIN $conversatios_table AS c ON u.user_id = c.user_id WHERE u.user_id = %d", $user->ID ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
1181 - if ( ! $user->exists() ) {
1182 - return '<p>No user logged in.</p>';
1183 - }
1184 - ob_start(); ?>
1185 - <style>
1186 - .cell-content #wp-chatbot-messages-container { height: 200px; overflow: scroll; overflow-x: hidden; }
1187 - .session_start_url { display: none; }
1188 - .table-striped tr { border: 2px solid #888; }
1189 - </style>
1190 - <table class="table table-striped align-middle" id="chatsession-table">
1191 - <thead>
1192 - <tr class="table-primary">
1193 - <th class="text-left"><?php echo esc_html__( 'Date', 'chatbot' ); ?></th>
1194 - <th class="text-left"><?php echo esc_html__( 'Session ID', 'chatbot' ); ?></th>
1195 - <th class="text-left"><?php echo esc_html__( 'Name', 'chatbot' ); ?></th>
1196 - <th class="text-left" data-dt-order="disable"><?php echo esc_html__( 'Conversation', 'chatbot' ); ?></th>
1197 - </tr>
1198 - <?php foreach ( $result as $key => $value ) : ?>
1199 - <tr>
1200 - <td class="text-left"><?php echo esc_html( $value->date ); ?></td>
1201 - <td class="text-left"><?php echo esc_html( $value->session_id ); ?></td>
1202 - <td class="text-left"><?php echo esc_html( $value->name ); ?></td>
1203 - <td class="text-left"><div class="cell-content"><a class="qcld-modal-content" data-value="<?php echo esc_attr( $value->conversation ); ?>">view data</a></div></td>
1204 - </tr>
1205 - <?php endforeach; ?>
1206 - </thead>
1207 - </table>
1208 - <?php
1209 - return ob_get_clean();
1210 - }
1211 -}
1212 -add_shortcode( 'qcpress_user', 'qc_current_user_session' );
1213 -
1214 -// ─── WP-Cron: AI Insight Scheduled Email ─────────────────────────────────────
1215 -add_filter( 'cron_schedules', 'qcld_wpsession_wp_cron_schedule_free' );
1216 -
1217 -if ( ! function_exists( 'qcld_wpsession_wp_cron_schedule_free' ) ) {
1218 - function qcld_wpsession_wp_cron_schedule_free( $schedules ) {
1219 - $schedules['session_schedules'] = array(
1220 - 'interval' => ( get_option( 'qcld_wbsession_corn_interval' ) != null ) ? get_option( 'qcld_wbsession_corn_interval' ) : 86400,
1221 - 'display' => esc_attr( 'Session min', 'wpchatbot' ),
1222 - );
1223 - return $schedules;
1224 - }
1225 -}
1226 -
1227 -$wpsession_corn_start_times = wp_date( 'Y-m-d' ) . ' ' . get_option( 'qcld_wbsession_corn_starttime' );
1228 -$wpsession_corn_start_time = strtotime( $wpsession_corn_start_times );
1229 -if ( ! wp_next_scheduled( 'qcld_wpsession_mysql_scraper_event' ) && ( get_option( 'qcld_wbsession_ai_enable' ) == '1' ) ) {
1230 - wp_schedule_event( $wpsession_corn_start_time, 'session_schedules', 'qcld_wpsession_mysql_scraper_event' );
1231 -}
1232 -
1233 -add_action( 'qcld_wpsession_mysql_scraper_event', 'qcld_wpsession_mysql_scraper_function_free' );
1234 -
1235 -if ( ! function_exists( 'qcld_wpsession_mysql_scraper_function_free' ) ) {
1236 - function qcld_wpsession_mysql_scraper_function_free() {
1237 - if ( get_option( 'qcld_wbsession_ai_enable' ) != '1' ) { return; }
1238 -
1239 - global $wpdb;
1240 - $interval_hours = ( (int) get_option( 'qcld_wbsession_corn_interval' ) ?: 86400 ) / 3600;
1241 - $tableuser = $wpdb->prefix . 'wpbot_user';
1242 - $tableconversation = $wpdb->prefix . 'wpbot_conversation';
1243 - $results = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
1244 - "SELECT u.id, c.user_id, u.date, u.session_id, c.id, c.conversation
1245 - FROM $tableuser AS u LEFT JOIN $tableconversation AS c ON u.id = c.user_id
1246 - WHERE u.date >= (NOW() - INTERVAL %d HOUR) ORDER BY u.date DESC", $interval_hours ) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1247 - );
1248 -
1249 - $remarkable_session = array();
1250 - foreach ( $results as $row ) {
1251 - $remarkable_session[] = array( 'id' => $row->session_id, 'conversation' => wpsession_message_html_filter_free( htmlspecialchars_decode( $row->conversation ) ) );
1252 - }
1253 -
1254 - $keyword = get_option( 'qcld_wpsession_corn_promt' ) ?: 'Below is Chat conversation session data from our users on our website. Each conversation starts with an ID. Can you analyze each conversation and summarize each of them?';
1255 - $gptkeyword = array(
1256 - array( 'role' => 'system', 'content' => array( array( 'type' => 'input_text', 'text' => $keyword ) ) ),
1257 - array( 'role' => 'user', 'content' => array( array( 'type' => 'input_text', 'text' => wp_json_encode( $remarkable_session ) ) ) ),
1258 - );
1259 -
1260 - $api_key = get_option( 'open_ai_api_key' );
1261 - $engines = get_option( 'openai_engines' );
1262 - $post_fields = array( 'model' => $engines, 'input' => $gptkeyword );
1263 - $header = array( 'Content-Type: application/json', 'Authorization: Bearer ' . $api_key );
1264 -
1265 - $ch = curl_init();
1266 - curl_setopt( $ch, CURLOPT_URL, 'https://api.openai.com/v1/responses' );
1267 - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
1268 - curl_setopt( $ch, CURLOPT_POST, 1 );
1269 - curl_setopt( $ch, CURLOPT_POSTFIELDS, wp_json_encode( $post_fields ) );
1270 - curl_setopt( $ch, CURLOPT_HTTPHEADER, $header );
1271 - $result = curl_exec( $ch );
1272 - curl_close( $ch );
1273 -
1274 - $mess = json_decode( $result );
1275 - $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.' );
1276 - $msg = preg_replace( "/\r\n|\r|\n/", '<br/>', $msg );
1277 -
1278 - $to = get_option( 'qlcd_wp_chatbot_admin_email' ) ?: get_option( 'admin_email' );
1279 - $headers = array(
1280 - 'Content-Type: text/html; charset=UTF-8',
1281 - 'From: ' . esc_html( get_bloginfo( 'name' ) ) . ' <wordpress@' . wp_parse_url( get_site_url(), PHP_URL_HOST ) . '>',
1282 - );
1283 - wp_mail( $to, 'ChatBot Sessions Analysis', $msg, $headers );
1284 - }
1285 -}
1286 -
1287 -// ─── Reports (Bot - Reports submenu) ─────────────────────────────────────────
1288 -require_once QCLD_CHATBOT_FREE_SESSION_DIR_PATH . 'reports/chatbot-history-reporting.php';
1 +<?php
2 +/**
3 + * WPBot Sessions & Analytics — Built-in Chat Session Module
4 + *
5 + * Provides full chat session recording, analytics, AI Insight, and
6 + * "Questions Not Answered" reporting natively in the free chatbot plugin.
7 + *
8 + * This module is a port of the Pro plugin's chat-session-addon.
9 + * It uses the SAME database table names (wpbot_user, wpbot_conversation,
10 + * wpbot_failed_response) so data is shared if the Pro addon is later activated.
11 + *
12 + * Everything is guarded with function_exists() checks so that when the Pro
13 + * addon IS active it takes full precedence and this code is skipped entirely.
14 + */
15 +
16 +if ( ! defined( 'ABSPATH' ) ) {
17 + exit;
18 +}
19 +
20 +// ─── Guard: Do not run if the Pro addon is active ────────────────────────────
21 +// The Pro addon defines qcwp_chat_session_menu_fnc(); if that function already
22 +// exists we skip everything here to avoid duplicate menus / conflicts.
23 +if ( function_exists( 'qcwp_chat_session_menu_fnc' ) ) {
24 + return;
25 +}
26 +
27 +// ─── Constants ────────────────────────────────────────────────────────────────
28 +// Define our own URL / path constants for assets.
29 +// Also define the legacy constant names that all copied report partials reference,
30 +// so those files work without modification.
31 +
32 +if ( ! defined( 'QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL' ) ) {
33 + define( 'QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
34 +}
35 +if ( ! defined( 'QCLD_CHATBOT_FREE_SESSION_DIR_PATH' ) ) {
36 + define( 'QCLD_CHATBOT_FREE_SESSION_DIR_PATH', plugin_dir_path( __FILE__ ) );
37 +}
38 +
39 +// Legacy alias constants — used by all copied partials / report files.
40 +if ( ! defined( 'QCLD_wpCHATBOT_HISTORY_PLUGIN_URL' ) ) {
41 + define( 'QCLD_wpCHATBOT_HISTORY_PLUGIN_URL', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL );
42 +}
43 +if ( ! defined( 'QCLD_WPCHATBOT_HISTORY_DIR_PATH' ) ) {
44 + define( 'QCLD_WPCHATBOT_HISTORY_DIR_PATH', QCLD_CHATBOT_FREE_SESSION_DIR_PATH );
45 +}
46 +
47 +// ─── Database Structure ───────────────────────────────────────────────────────
48 +require_once QCLD_CHATBOT_FREE_SESSION_DIR_PATH . 'inc/chatsession-db-structure.php';
49 +
50 +// ─── Admin Menu ───────────────────────────────────────────────────────────────
51 +add_action( 'admin_menu', 'qcwp_chat_session_menu_fnc_free' );
52 +
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 + }
57 +
58 + function qc_wpbot_cs_tabbed_wrapper() {
59 + $active_tab = isset( $_GET['tab'] ) ? sanitize_text_field( wp_unslash( $_GET['tab'] ) ) : 'sessions';
60 +
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 + );
75 +
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 + }
82 +
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>
86 +
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
144 + }
145 +
146 +// ─── Admin Scripts & Styles ───────────────────────────────────────────────────
147 +add_action( 'admin_enqueue_scripts', 'qcld_wb_chatbot_session_admin_scripts_free' );
148 +
149 +function qcld_wb_chatbot_session_admin_scripts_free( $hook ) {
150 + // Only enqueue on our specific sessions page
151 + if ( ! isset( $_GET['page'] ) || 'wbcs-botsessions-page' !== $_GET['page'] ) {
152 + return;
153 + }
154 +
155 + wp_register_style( 'qlcd-wp-bootstrap-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/qlcd-wp-bootstrap.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
156 + wp_enqueue_style( 'qlcd-wp-bootstrap-cs' );
157 +
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' );
159 + wp_enqueue_style( 'qlcd-wp-bootstrap-icons-cs' );
160 +
161 + wp_register_style( 'qlcd-wp-dataTables-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/qlcd-wp-dataTables.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
162 + wp_enqueue_style( 'qlcd-wp-dataTables-cs' );
163 +
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' );
165 + wp_enqueue_style( 'qlcd-wp-session-style-cs' );
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 +
170 + // SweetAlert2 — used by admin.js for Swal.fire() and Swal.showLoading()
171 + wp_register_script( 'qcld-wp-chatbot-sweetalrt-cs', QCLD_wpCHATBOT_PLUGIN_URL . 'js/sweetalrt.js', array( 'jquery' ), QCLD_wpCHATBOT_VERSION, true );
172 + wp_enqueue_script( 'qcld-wp-chatbot-sweetalrt-cs' );
173 +
174 + // admin.js depends on SweetAlert2 being loaded first
175 + wp_register_script( 'qcld-wp-session-admin-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'js/admin.js', array( 'jquery', 'qcld-wp-chatbot-sweetalrt-cs' ), QCLD_wpCHATBOT_VERSION, true );
176 + wp_enqueue_script( 'qcld-wp-session-admin-cs' );
177 + wp_localize_script(
178 + 'qcld-wp-session-admin-cs',
179 + 'ajax_object',
180 + array(
181 + 'ajax_url' => admin_url( 'admin-ajax.php' ),
182 + 'ajax_nonce' => wp_create_nonce( 'wpbot_session_ajax_nonce' )
183 + )
184 + );
185 +
186 + wp_register_script( 'qcld-wp-dataTables-cs', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'js/qcld-dataTables.min.js', array( 'jquery' ), QCLD_wpCHATBOT_VERSION, true );
187 + wp_enqueue_script( 'qcld-wp-dataTables-cs' );
188 +}
189 +
190 +
191 +// ─── AI Insight Page Callback ─────────────────────────────────────────────────
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' );
195 + ?>
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' ); ?>
209 + </p>
210 + <p class="wpbot-ai-insight-locked__cta">
211 + <?php echo esc_html__( 'Please upgrade to WPBot Pro to unlock this feature!', 'chatbot' ); ?>
212 + </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>
216 + </div>
217 + </div>
218 + <?php
219 +}
220 +
221 +
222 +// ─── Questions Not Answered Page Callback ─────────────────────────────────────
223 +function qcld_wpbot_not_answered_question() {
224 + global $wpdb;
225 + $wpdb->show_errors = true; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
226 + $table = $wpdb->prefix . 'wpbot_failed_response'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
227 +
228 + if ( isset( $_GET['msg'] ) && $_GET['msg'] == 'success' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
229 + echo '<div class="notice notice-success"><p>Record has been Deleted Successfully!</p></div>';
230 + }
231 +
232 + if ( isset( $_GET['action'] ) && $_GET['action'] == 'deleteall' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
233 + $wpdb->query( "TRUNCATE TABLE `$table`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
234 + echo '<div class="notice notice-success"><p>All Records have been deleted successfully!</p></div>';
235 + }
236 +
237 + $sql = "SELECT * FROM $table WHERE 1 ORDER BY `id` DESC"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
238 + $sql1 = "SELECT count(*) FROM $table WHERE 1 ORDER BY `id` DESC"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
239 +
240 + $total = $wpdb->get_var( $sql1 ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
241 + $items_per_page = 30;
242 + $page = isset( $_GET['cpage'] ) ? abs( (int) $_GET['cpage'] ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
243 + $offset = ( $page * $items_per_page ) - $items_per_page;
244 + $sql .= " LIMIT {$offset}, {$items_per_page}";
245 + $result = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
246 + $totalPage = ceil( $total / $items_per_page );
247 + $customPagHTML = '';
248 + if ( $totalPage > 1 ) {
249 + $customPagHTML = '<div><span class="wpbot_pagination">Page ' . esc_html( $page ) . ' of ' . esc_html( $totalPage ) . '</span>' . paginate_links(
250 + array(
251 + 'base' => add_query_arg( 'cpage', '%#%' ),
252 + 'format' => '',
253 + 'prev_text' => __( '« prev' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
254 + 'next_text' => __( 'next »' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
255 + 'total' => esc_html( $totalPage ),
256 + 'current' => esc_html( $page ),
257 + )
258 + ) . '</div>';
259 + }
260 +
261 + wp_register_style( 'qcld-wp-chatbot-history-style', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/history-style.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
262 + wp_enqueue_style( 'qcld-wp-chatbot-history-style' );
263 + ?>
264 +
265 + <div class="sld_menu_title qcld_session_chat_menu_title">
266 + <h2><?php echo esc_html__( 'Questions Not Answered', 'chatbot' ) . ' (' . intval( $total ) . ')'; ?></h2>
267 + </div>
268 +
269 + <?php if ( $customPagHTML != '' ) : ?>
270 + <div class="sld_menu_title sld_menu_title_align"><?php echo wp_kses_post( $customPagHTML ); ?></div>
271 + <?php endif; ?>
272 +
273 + <?php
274 + require_once QCLD_CHATBOT_FREE_SESSION_DIR_PATH . 'reports/view/partials/questions-not-answered.php';
275 +}
276 +
277 +// ─── Main Chat Sessions Page Callback ────────────────────────────────────────
278 +function qc_wpbot_cs_menu_page_callback_func() {
279 +
280 + global $wpdb;
281 + $wpdb->show_errors = true; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
282 +
283 + $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
284 + $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
285 + $mainurl = admin_url( 'admin.php?page=wbcs-botsessions-page' );
286 +
287 + if ( isset( $_GET['min_interaction'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
288 + $mainurl .= '&min_interaction=' . intval( $_GET['min_interaction'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
289 + }
290 + if ( isset( $_GET['wp_user'] ) && $_GET['wp_user'] != '' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
291 + $mainurl .= '&wp_user=' . intval( $_GET['wp_user'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
292 + }
293 +
294 + $msg = '';
295 +
296 + if ( isset( $_GET['action'] ) && $_GET['action'] == 'deleteall' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
297 + $wpdb->query( "TRUNCATE TABLE `$tableuser`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
298 + $wpdb->query( "TRUNCATE TABLE `$tableconversation`" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
299 + $msg = esc_html( 'All Sessions have been deleted successfully!' );
300 + }
301 +
302 + if ( isset( $_GET['msg'] ) && $_GET['msg'] == 'success' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
303 + echo '<div class="notice notice-success"><p>Record has been Deleted Successfully!</p></div>';
304 + }
305 +
306 + if ( isset( $_GET['userid'] ) && $_GET['userid'] != '' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
307 + require_once QCLD_CHATBOT_FREE_SESSION_DIR_PATH . 'reports/view/partials/view-single-chat.php';
308 + } else {
309 +
310 + wp_register_style( 'qcld-wp-chatbot-history-style', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/history-style.css', array(), QCLD_wpCHATBOT_VERSION, 'screen' );
311 + wp_enqueue_style( 'qcld-wp-chatbot-history-style' );
312 + wp_register_style( 'qcld-wp-chatbot-jquery-ui', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'css/jqueryui.css', array(), '', 'screen' );
313 + wp_enqueue_style( 'qcld-wp-chatbot-jquery-ui' );
314 + wp_register_script( 'qcld-wp-chatsession-admin-js', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'js/chatsession.js', array( 'jquery' ), QCLD_wpCHATBOT_VERSION, true );
315 + wp_enqueue_script( 'qcld-wp-chatsession-admin-js' );
316 + wp_localize_script(
317 + 'qcld-wp-chatsession-admin-js',
318 + 'ajax_object',
319 + array(
320 + 'ajax_url' => admin_url( 'admin-ajax.php' ),
321 + 'ajax_nonce' => wp_create_nonce( 'wpbot_session_ajax_nonce' )
322 + )
323 + );
324 + wp_register_script( 'qcld-wp-jqueryui-js', QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'js/jqueryui.js', array( 'jquery' ), QCLD_wpCHATBOT_VERSION, true );
325 + wp_enqueue_script( 'qcld-wp-jqueryui-js' );
326 +
327 + $where = '';
328 + if ( isset( $_GET['min_interaction'] ) && $_GET['min_interaction'] != 'all' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
329 + if ( isset( $_GET['min_interaction'] ) && $_GET['min_interaction'] > 0 ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
330 + $where = ' and `interaction` >= ' . intval( $_GET['min_interaction'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
331 + }
332 + if ( isset( $_GET['min_interaction'] ) && $_GET['min_interaction'] == 0 ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
333 + $where = ' and `interaction` = 0';
334 + }
335 + }
336 +
337 + $wwhere = '';
338 + if ( isset( $_GET['wp_user'] ) && $_GET['wp_user'] != 'all' && $_GET['wp_user'] != 0 && $_GET['wp_user'] != '' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
339 + $wwhere = ' and `user_id` = ' . intval( $_GET['wp_user'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
340 + }
341 +
342 + $sql = "SELECT * FROM $tableuser WHERE 1 $where $wwhere ORDER BY `date` DESC"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
343 + $sql1 = "SELECT count(*) FROM $tableuser WHERE 1 $where $wwhere"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
344 +
345 + $dateFilter = '';
346 + if ( isset( $_GET['FilterDate'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
347 + if ( $_GET['FilterDate'] === 'LastWeek' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
348 + $dateFilter = " WHERE `date` >= CURDATE() - INTERVAL 7 DAY";
349 + }
350 + if ( $_GET['FilterDate'] === 'LastMonth' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
351 + $dateFilter = " WHERE `date` >= CURDATE() - INTERVAL 30 DAY";
352 + }
353 + if ( $_GET['FilterDate'] === 'Last3Months' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
354 + $dateFilter = " WHERE `date` >= CURDATE() - INTERVAL 90 DAY";
355 + }
356 + $sql = "SELECT * FROM $tableuser $dateFilter $wwhere ORDER BY `date` DESC"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
357 + $sql1 = "SELECT count(*) FROM $tableuser $dateFilter"; // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
358 + }
359 +
360 + $total = $wpdb->get_var( $sql1 ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
361 + $items_per_page = 30;
362 + $page = isset( $_GET['cpage'] ) ? abs( (int) $_GET['cpage'] ) : 1; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
363 + $offset = ( $page * $items_per_page ) - $items_per_page;
364 + $sql .= " LIMIT {$offset}, {$items_per_page}";
365 + $result = $wpdb->get_results( $sql ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
366 + $totalPage = ceil( $total / $items_per_page );
367 + $customPagHTML = '';
368 + if ( $totalPage > 1 ) {
369 + $customPagHTML = '<div class="qcld-session-pagination"><span class="wpbot_pagination">Page ' . esc_html( $page ) . ' of ' . esc_html( $totalPage ) . '</span>' . paginate_links(
370 + array(
371 + 'base' => add_query_arg( 'cpage', '%#%' ),
372 + 'format' => '',
373 + 'prev_text' => __( '« prev' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
374 + 'next_text' => __( 'next »' ), // phpcs:ignore WordPress.WP.I18n.MissingArgDomain
375 + 'total' => esc_html( $totalPage ),
376 + 'current' => esc_html( $page ),
377 + )
378 + ) . '</div>';
379 + }
380 +
381 + $deleteurl = admin_url( 'admin.php?page=wbcs-botsessions-page&action=deleteall' );
382 + ?>
383 +
384 + <div class="qchero_sliders_list_wrapper qcld-session-history_menu_box">
385 + <?php if ( $msg != '' ) : ?>
386 + <div class="notice notice-success is-dismissible">
387 + <p><?php echo esc_html( $msg ); ?></p>
388 + </div>
389 + <?php endif; ?>
390 +
391 + <div class="sld_menu_title qcld-session-history_menu_title">
392 + <h2><?php echo esc_html__( 'Chat Sessions', 'chatbot' ) . ' (' . intval( $total ) . ')'; ?></h2>
393 + </div>
394 +
395 + <div>
396 + <?php
397 + if ( isset( $_GET['FilterDate'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
398 + $filterText = '';
399 + if ( $_GET['FilterDate'] === 'LastWeek' ) { $filterText = 'LAST WEEK'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended
400 + if ( $_GET['FilterDate'] === 'LastMonth' ) { $filterText = 'LAST MONTH'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended
401 + if ( $_GET['FilterDate'] === 'Last3Months' ) { $filterText = 'LAST 3 MONTHS'; } // phpcs:ignore WordPress.Security.NonceVerification.Recommended
402 + echo '<div class="sld_menu_title"><em>Filtering Records by: <strong>' . esc_html( $filterText ) . '</strong></em></div>';
403 + }
404 + ?>
405 + </div>
406 +
407 +
408 + <form id="wpcs_form_sessions" action="<?php echo esc_url( $mainurl ); ?>" method="POST">
409 + <?php wp_nonce_field( 'wpcs_bulk_action' ); ?>
410 + <input type="hidden" name="wpbot_session_remove" />
411 +
412 + <?php if ( ! empty( $result ) ) : ?>
413 + <?php require_once QCLD_CHATBOT_FREE_SESSION_DIR_PATH . 'reports/view/partials/chatsession-table.php'; ?>
414 + <?php else : ?>
415 + <div class="sld_menu_title"><h2>No result found.</h2></div>
416 + <?php endif; ?>
417 + </form>
418 + </div>
419 + <?php
420 + }
421 +}
422 +
423 +// ─── Request Handler (delete, export, redirect) ───────────────────────────────
424 +add_action( 'init', 'qc_wp_cs_request_handle_free' );
425 +
426 +function qc_wp_cs_request_handle_free() {
427 + if ( ! is_admin() || ! current_user_can( 'manage_options' ) ) {
428 + return;
429 + }
430 +
431 + global $wpdb;
432 + $wpdb->show_errors = true; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
433 +
434 + $tableuser1 = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
435 + $tableconversation1 = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
436 + $table = $wpdb->prefix . 'wpbot_failed_response'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
437 +
438 + // Delete single "not answered" record.
439 + if ( isset( $_GET['page'] ) && $_GET['page'] == 'wbcs-botsessions-notansweredpage' && isset( $_GET['act'] ) && $_GET['act'] == 'delete' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
440 + $userid = intval( $_GET['id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
441 + check_admin_referer( 'wpcs_delete_session_' . $userid );
442 + $wpdb->delete( $table, array( 'id' => $userid ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
443 + wp_safe_redirect( admin_url( 'admin.php?page=wbcs-botsessions-notansweredpage&msg=success' ) );
444 + exit;
445 + }
446 +
447 + // Delete single chat session.
448 + if ( isset( $_GET['page'] ) && $_GET['page'] == 'wbcs-botsessions-page' && isset( $_GET['act'] ) && $_GET['act'] == 'delete' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
449 + $userid = intval( $_GET['userid'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
450 + check_admin_referer( 'wpcs_delete_session_' . $userid );
451 + $wpdb->delete( $tableuser1, array( 'id' => $userid ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
452 + $wpdb->delete( $tableconversation1, array( 'user_id' => $userid ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
453 + wp_safe_redirect( admin_url( 'admin.php?page=wbcs-botsessions-page&msg=success' ) );
454 + exit;
455 + }
456 +
457 + // Export all sessions as CSV.
458 + if ( isset( $_POST['wpbot_session_export_all'] ) && isset( $_POST['wpbot_session_remove'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
459 + check_admin_referer( 'wpcs_bulk_action' );
460 + $users = $wpdb->get_results( "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 LIMIT 5000" ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
461 + $sessions = array();
462 + if ( ! empty( $users ) ) {
463 + foreach ( $users as $user ) {
464 + $sessions[] = wpbot_conversations_export( $user );
465 + }
466 + }
467 + qcld_wpbot_chatsession_download_send_headers( 'wpbot_chatsession_' . gmdate( 'Y-m-d' ) . '.csv' );
468 + print wpbot_chatsession_array2csv( $sessions ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw CSV download, escaping would corrupt the file.
469 + exit;
470 + }
471 +
472 + // Export selected sessions or delete selected sessions.
473 + if ( isset( $_POST['wpbot_session_remove'] ) && ! empty( $_POST['sessions'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
474 + check_admin_referer( 'wpcs_bulk_action' );
475 + $userids = array_map( 'intval', $_POST['sessions'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
476 +
477 + if ( isset( $_POST['wpbot_session_export'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
478 + $sessions = array();
479 + foreach ( $userids as $userid ) {
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
481 + $sessions[] = wpbot_conversations_export( $user );
482 + }
483 + qcld_wpbot_chatsession_download_send_headers( 'wpbot_chatsession_' . gmdate( 'Y-m-d' ) . '.csv' );
484 + print wpbot_chatsession_array2csv( $sessions ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw CSV download, escaping would corrupt the file.
485 + exit;
486 + }
487 +
488 + if ( isset( $_POST['wpbot_session_delete'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
489 + foreach ( $userids as $userid ) {
490 + $wpdb->delete( $tableuser1, array( 'id' => $userid ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
491 + $wpdb->delete( $tableconversation1, array( 'user_id' => $userid ), array( '%d' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
492 + }
493 + wp_safe_redirect( admin_url( 'admin.php?page=wbcs-botsessions-page&msg=success' ) );
494 + exit;
495 + }
496 + }
497 +}
498 +
499 +// ─── Admin Footer: Email Modal ────────────────────────────────────────────────
500 +add_action( 'admin_footer', 'wpcs_admin_footer_content_free' );
501 +
502 +function wpcs_admin_footer_content_free() {
503 + if ( isset( $_GET['page'] ) && $_GET['page'] == 'wbcs-botsessions-page' ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
504 + ?>
505 + <div id="wpcsmyModal" class="wpcsmodal">
506 + <div class="wpcsmodal-content">
507 + <span class="wpcsclose">&times;</span>
508 + <h2><?php echo esc_html( 'Send an Email to' ); ?> <span id="wpcs_show_email"></span></h2>
509 + <div class="wpcs_form_container">
510 + <form id="wpcs_email_form" action="">
511 + <label for="fname"><?php echo esc_html( 'Subject' ); ?></label>
512 + <input type="text" class="wpcs_text_field" id="wpcs_email_subject" name="wpcs_email_subject" placeholder="Subject.." required>
513 + <label for="lname"><?php echo esc_html( 'Your Message' ); ?></label>
514 + <textarea id="wpcs_email_message" class="wpcs_text_field" name="wpcs_email_message" placeholder="" style="height:200px" required></textarea>
515 + <input type="hidden" id="wpcs_to_email_address" value="" />
516 + <input type="submit" class="wpcs_submit_field" id="wpcs_email_submit" value="Submit">
517 + <span id="wpcs_email_loading" style="display:none;"><img style="width:20px;" src="<?php echo esc_url( QCLD_CHATBOT_FREE_SESSION_PLUGIN_URL . 'images/ajax-loader.gif' ); ?>"></span>
518 + <span id="wpcs_email_status"></span>
519 + </form>
520 + </div>
521 + </div>
522 + </div>
523 + <?php
524 + }
525 +}
526 +
527 +// ─── AJAX: Send Email to User ─────────────────────────────────────────────────
528 +add_action( 'wp_ajax_wpcs_send_email', 'wpcs_send_email' );
529 +
530 +function wpcs_send_email() {
531 + if ( ! current_user_can( 'manage_options' ) ) {
532 + wp_die();
533 + }
534 + check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
535 +
536 + $subject = sanitize_text_field( $_POST['data']['subject'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
537 + $message = sanitize_text_field( $_POST['data']['message'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
538 + $to = sanitize_email( $_POST['data']['to'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
539 +
540 + global $wpdb;
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 +
555 + $admin_email = get_option('admin_email');
556 + if ( ! $user_exists && $to !== $admin_email ) {
557 + wp_send_json( array( 'status' => 'fail', 'message' => 'Invalid recipient address. Email must be a stored session email or admin email.' ) );
558 + }
559 +
560 + $url = get_site_url();
561 + $url = wp_parse_url( $url );
562 + $domain = $url['host'];
563 + $fromEmail = 'wordpress@' . $domain;
564 + $headers = array(
565 + 'Content-Type: text/html; charset=UTF-8',
566 + 'From: ' . esc_html( $domain ) . ' <' . esc_html( $fromEmail ) . '>',
567 + );
568 +
569 + $result = wp_mail( $to, $subject, $message, $headers );
570 + if ( $result ) {
571 + $response = array( 'status' => 'success', 'message' => 'Email has been sent successfully!' );
572 + } else {
573 + $response = array( 'status' => 'fail', 'message' => 'Unable to send email. Please contact your server administrator.' );
574 + }
575 + ob_clean();
576 + echo wp_json_encode( $response );
577 + die();
578 +}
579 +
580 +// ─── AJAX: Save Email Notification Preference ─────────────────────────────────
581 +add_action( 'wp_ajax_session_email_notification_update', 'session_email_notification_update_free' );
582 +
583 +function session_email_notification_update_free() {
584 + if ( ! current_user_can( 'manage_options' ) ) {
585 + wp_die();
586 + }
587 + $email_notification = sanitize_text_field( $_POST['email_notification'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
588 + update_option( 'session_email_notification_update', $email_notification );
589 + wp_send_json( array( 'success' => true ) );
590 +}
591 +
592 +// ─── AJAX: Conversation Save (Frontend) ──────────────────────────────────────
593 +// This is the main conversation-save handler. Guarded with function_exists
594 +// so the Pro addon's definition wins if it's active.
595 +if ( ! function_exists( 'qcld_wb_chatbot_conversation_save' ) ) {
596 +
597 + function qcld_wb_chatbot_conversation_save() {
598 +
599 + check_ajax_referer( 'qcsecretbotnonceval123qc', 'security' );
600 + global $wpdb;
601 +
602 + $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
603 + $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
604 +
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
611 + $email = isset( $_POST['email'] ) ? sanitize_email( $_POST['email'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
612 + $phone = isset( $_POST['phone'] ) ? sanitize_text_field( $_POST['phone'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
613 + $name = isset( $_POST['name'] ) ? sanitize_text_field( $_POST['name'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
614 + $session_id = isset( $_POST['session_id'] ) ? sanitize_text_field( $_POST['session_id'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
615 + $wpuser_id = isset( $_POST['user_id'] ) ? sanitize_text_field( $_POST['user_id'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
616 + $source_url = isset( $_POST['source_url'] ) && ! empty( $_POST['source_url'] ) ? sanitize_url( wp_unslash( $_POST['source_url'] ) ) : ( isset( $_SERVER['HTTP_REFERER'] ) ? sanitize_url( wp_unslash( $_SERVER['HTTP_REFERER'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
617 + $user_agent = isset( $_POST['user_agent'] ) && ! empty( $_POST['user_agent'] ) ? sanitize_text_field( wp_unslash( $_POST['user_agent'] ) ) : ( isset( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
618 + $session_mailed = isset( $_POST['session_mailed'] ) ? sanitize_text_field( $_POST['session_mailed'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
619 +
620 + // Prepend source URL to conversation.
621 + $conversation = '&#x3C;ul&#x3E;&#x3C;li class=&#x22;session_start_url&#x22;&#x3E;&#x3C;span&#x3E;Source URL: &#x3C;/span&#x3E;&#x3C;a href=&#x22;' . $source_url . '&#x22;&#x3E;' . $source_url . '&#x3C;/a&#x3E;&#x3C;/li&#x3E;&#x3C;/ul&#x3E;' . $conversation;
622 +
623 + $response = array();
624 + $response['status'] = 'success';
625 +
626 + $user_exists = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableuser WHERE 1 AND session_id = %s", $session_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
627 + $is_new_insert = false;
628 +
629 + if ( empty( $user_exists ) ) {
630 + $lock_key = 'wpcs_lock_' . md5( $session_id );
631 + if ( add_option( $lock_key, '1', '', 'no' ) ) {
632 + $interaction = (int) substr_count( $conversation, 'wp-chat-user-msg' );
633 + if ( $interaction == 0 ) {
634 + $interaction = (int) substr_count( $conversation, 'woo-chat-user-msg' );
635 + }
636 +
637 + if ( $interaction != 0 ) {
638 + $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
639 + $tableuser,
640 + array(
641 + 'date' => current_time( 'mysql' ),
642 + 'name' => $name,
643 + 'email' => $email,
644 + 'phone' => $phone,
645 + 'session_id' => $session_id,
646 + 'interaction' => $interaction,
647 + 'user_id' => $wpuser_id,
648 + )
649 + );
650 + $user_id = $wpdb->insert_id; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
651 + $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
652 + $tableconversation,
653 + array(
654 + 'user_id' => $user_id,
655 + 'conversation' => $conversation,
656 + 'interaction' => $interaction,
657 + 'environment_info' => $user_agent,
658 + )
659 + );
660 + $is_new_insert = true;
661 + }
662 + delete_option( $lock_key );
663 + } else {
664 + $retries = 3;
665 + while ( $retries > 0 ) {
666 + usleep( 500000 );
667 + $user_exists = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableuser WHERE 1 AND session_id = %s", $session_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
668 + if ( ! empty( $user_exists ) ) {
669 + break;
670 + }
671 + $retries--;
672 + }
673 + }
674 + }
675 +
676 + if ( ! $is_new_insert && ! empty( $user_exists ) ) {
677 + $interaction = (int) substr_count( $conversation, 'wp-chat-user-msg' );
678 + if ( $interaction == 0 ) {
679 + $interaction = (int) substr_count( $conversation, 'woo-chat-user-msg' );
680 + }
681 +
682 + $user_id = isset( $user_exists->id ) ? $user_exists->id : get_current_user_id();
683 + $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
684 + $tableuser,
685 + array(
686 + 'date' => current_time( 'mysql' ),
687 + 'name' => $name,
688 + 'email' => $email,
689 + 'phone' => $phone,
690 + 'interaction' => $interaction,
691 + 'user_id' => $wpuser_id,
692 + ),
693 + array( 'id' => $user_id ),
694 + array( '%s', '%s', '%s', '%s', '%d', '%d' ),
695 + array( '%d' )
696 + );
697 + $wpdb->update( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
698 + $tableconversation,
699 + array(
700 + 'conversation' => $conversation,
701 + 'interaction' => $interaction,
702 + ),
703 + array( 'user_id' => $user_id ),
704 + array( '%s', '%d' ),
705 + array( '%d' )
706 + );
707 + }
708 +
709 + // Email notification for new session.
710 + if ( $is_new_insert && ( get_option( 'session_email_notification_update' ) == 'checked' ) ) {
711 + $admin_email = get_option( 'admin_email' );
712 + $subject = esc_html__( 'Someone has started a new chat session with ChatBot.', 'chatbot' );
713 + $bodyContent = '<p>' . esc_html__( 'Hi,', '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>';
715 +
716 + $bodyContent .= '<ul>';
717 + $bodyContent .= '<li>' . esc_html__( 'Session ID:', 'chatbot' ) . ' <strong>' . esc_html( $session_id ) . '</strong></li>';
718 + if ( ! empty( $email ) ) {
719 + $bodyContent .= '<li>' . esc_html__( 'Email:', 'chatbot' ) . ' <strong>' . esc_html( $email ) . '</strong></li>';
720 + }
721 + if ( ! empty( $phone ) ) {
722 + $bodyContent .= '<li>' . esc_html__( 'Phone:', 'chatbot' ) . ' <strong>' . esc_html( $phone ) . '</strong></li>';
723 + }
724 + if ( ! empty( $source_url ) ) {
725 + $bodyContent .= '<li>' . esc_html__( 'Page Link:', 'chatbot' ) . ' <strong><a href="' . esc_url( $source_url ) . '">' . esc_html( $source_url ) . '</a></strong></li>';
726 + }
727 + if ( ! empty( $user_agent ) ) {
728 + $bodyContent .= '<li>' . esc_html__( 'Browser:', 'chatbot' ) . ' <strong>' . esc_html( $user_agent ) . '</strong></li>';
729 + }
730 + $bodyContent .= '</ul>';
731 +
732 + $bodyContent .= '<p>' . esc_html__( 'Thanks', 'chatbot' ) . '</p>';
733 + $bodyContent .= '<p>' . esc_html__( '(You can disable email notifications from ', 'chatbot' ) . '<a href="' . admin_url() . 'admin.php?page=wbcs-botsessions-page">' . esc_html__( 'Bot - Sessions)', 'chatbot' ) . '</a></p>';
734 +
735 + $to = get_option( 'qlcd_wp_chatbot_admin_email' ) != '' ? get_option( 'qlcd_wp_chatbot_admin_email' ) : $admin_email;
736 + $headers = array( 'Content-Type: text/html; charset=UTF-8' );
737 + wp_mail( $to, $subject, $bodyContent, $headers );
738 + }
739 +
740 + // WPBot Automator Trigger - Debounced by 3 minutes
741 + $cron_args = array( $session_id );
742 + if ( wp_next_scheduled( 'wpbot_automator_delayed_trigger', $cron_args ) ) {
743 + wp_clear_scheduled_hook( 'wpbot_automator_delayed_trigger', $cron_args );
744 + }
745 + wp_schedule_single_event( time() + 60, 'wpbot_automator_delayed_trigger', $cron_args );
746 +
747 + echo wp_json_encode( $response );
748 + die();
749 + }
750 +}
751 +add_action( 'wp_ajax_qcld_wb_chatbot_conversation_save', 'qcld_wb_chatbot_conversation_save' );
752 +add_action( 'wp_ajax_nopriv_qcld_wb_chatbot_conversation_save', 'qcld_wb_chatbot_conversation_save' );
753 +
754 +// ─── AJAX: Date Filter ────────────────────────────────────────────────────────
755 +add_action( 'wp_ajax_qcld_chatbot_session_date_filter', 'qcld_chatbot_session_date_filter_free' );
756 +
757 +function qcld_chatbot_session_date_filter_free() {
758 + if ( ! current_user_can( 'manage_options' ) ) {
759 + wp_die();
760 + }
761 + check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
762 + global $wpdb;
763 + $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
764 + $start_date = sanitize_text_field( $_POST['start_date'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
765 + $end_date = sanitize_text_field( $_POST['end_date'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
766 + $result = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $tableuser WHERE date BETWEEN %s AND %s", $start_date, $end_date ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
767 + echo wp_json_encode( $result );
768 + wp_die();
769 +}
770 +
771 +// ─── AJAX: Email Transcript ───────────────────────────────────────────────────
772 +add_action( 'wp_ajax_wpbot_send_email_transcript', 'wpbot_send_email_transcript_free' );
773 +
774 +function wpbot_send_email_transcript_free() {
775 + if ( ! current_user_can( 'manage_options' ) ) {
776 + wp_send_json( array( 'status' => 'fail', 'message' => 'Unauthorized' ) );
777 + }
778 + check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
779 +
780 + global $wpdb;
781 +
782 + $session = trim( sanitize_text_field( $_POST['session'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
783 +
784 + $url = wp_parse_url( get_site_url() );
785 + $domain = $url['host'];
786 + $admin_email = get_option( 'admin_email' );
787 + $fromEmail = get_option( 'qlcd_wp_chatbot_from_email' ) ? get_option( 'qlcd_wp_chatbot_from_email' ) : 'wordpress@' . $domain;
788 + $subject = 'Chat transcript by ' . get_bloginfo( 'name' );
789 +
790 + $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
791 + $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
792 +
793 + $user = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableuser WHERE 1 AND session_id = %s", $session ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
794 +
795 + $response = array( 'status' => 'fail', 'message' => 'Session not found.' );
796 +
797 + if ( ! empty( $user ) ) {
798 + $email = sanitize_email( $user->email ); // Use email from user record
799 + if ( empty( $email ) ) {
800 + wp_send_json( array( 'status' => 'fail', 'message' => 'User has no email.' ) );
801 + }
802 +
803 + $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableconversation WHERE 1 AND user_id = %d", $user->id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
804 + $bodyContent = '';
805 + $bodyContent .= '<p><strong>' . esc_html__( 'User Details', 'chatbot' ) . ':</strong></p><hr>';
806 + $bodyContent .= '<p>' . esc_html__( 'Name', 'chatbot' ) . ' : ' . esc_html( $user->name ) . '</p>';
807 + $bodyContent .= '<p>' . esc_html__( 'Email', 'chatbot' ) . ' : ' . esc_html( $email ) . '</p>';
808 + $bodyContent .= '<p><b>Conversations</b></p><p>-----------------------</p>';
809 + $messages = qcld_wpch_conversation_extract( htmlspecialchars_decode( $result->conversation ) );
810 + foreach ( $messages as $message ) {
811 + if ( isset( $message['bot'] ) && trim( $message['bot'] ) != '' ) {
812 + $bodyContent .= '<p>Chatbot : ' . esc_html( trim( $message['bot'] ) ) . '</p>';
813 + }
814 + if ( isset( $message['user'] ) && trim( $message['user'] ) != '' ) {
815 + $bodyContent .= '<p>' . esc_html( $user->name ) . ' : ' . esc_html( trim( $message['user'] ) ) . '</p>';
816 + }
817 + }
818 + $bodyContent .= '<p>-----------------------</p>';
819 + $bodyContent .= '<p>Mail Generated on: ' . current_time( 'F j, Y, g:i a' ) . '</p>';
820 + $headers = array(
821 + 'Content-Type: text/html; charset=UTF-8',
822 + 'From: ' . esc_html( $user->name ) . ' <' . esc_html( $fromEmail ) . '>',
823 + 'Reply-To: ' . esc_html( $user->name ) . ' <' . esc_html( $email ) . '>',
824 + );
825 + $result_mail = wp_mail( $email, $subject, $bodyContent, $headers );
826 + if ( $result_mail ) {
827 + $response = array( 'status' => 'success', 'message' => 'Email transcript sent successfully.' );
828 + }
829 + }
830 + echo wp_json_encode( $response );
831 + die();
832 +}
833 +
834 +// ─── AJAX: Forward Session to Email ──────────────────────────────────────────
835 +add_action( 'wp_ajax_forward_session_to_email', 'forward_session_to_email_free' );
836 +
837 +function forward_session_to_email_free() {
838 + if ( ! current_user_can( 'manage_options' ) ) {
839 + wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Insufficient permissions', 'chatbot' ) ) );
840 + wp_die();
841 + }
842 + check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
843 + global $wpdb;
844 +
845 + $session_id = sanitize_text_field( $_POST['session_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
846 + $to = sanitize_email( $_POST['email'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
847 + $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
848 + $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
849 +
850 + $userinfo = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableuser WHERE 1 AND session_id = %s", $session_id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
851 + $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableconversation WHERE 1 AND user_id = %d", $userinfo->id ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
852 +
853 + if ( ! empty( $result ) ) {
854 + $raw_html = isset( $result->conversation ) ? (string) $result->conversation : '';
855 + $decoded_content = html_entity_decode( $raw_html );
856 + $doc = new DOMDocument();
857 + libxml_use_internal_errors( true );
858 + $doc->loadHTML( '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $decoded_content );
859 + libxml_clear_errors();
860 +
861 + $decoded_content = '';
862 + $body_nodes = $doc->getElementsByTagName( 'body' );
863 + if ( $body_nodes->length > 0 ) {
864 + foreach ( $body_nodes->item( 0 )->childNodes as $child_node ) {
865 + $decoded_content .= $doc->saveHTML( $child_node );
866 + }
867 + }
868 +
869 + $email_body = '<!DOCTYPE html><html><head><style>
870 + .wp-chatbot-messages-container { list-style: none; padding: 20px; background: #f4f7f6; font-family: sans-serif; }
871 + .wp-chatbot-msg { margin-bottom: 15px; display: flex; flex-wrap: wrap; }
872 + .wp-chatbot-agent { font-weight: bold; color: #333; display: block; margin-bottom: 4px; }
873 + .wp-chatbot-paragraph { background: #ffffff; padding: 10px; border-radius: 8px; border: 1px solid #ddd; flex: 1; }
874 + .wp-chat-user-msg { display: flex; flex-wrap: wrap; flex-direction: row-reverse; }
875 + .wp-chat-user-msg .wp-chatbot-paragraph { background: #ffffff; padding: 10px; border-radius: 8px; border: 1px solid #ddd; flex: none; }
876 + body ul { width:100%; max-width: 640px; list-style: none; padding: 0; margin: 0 auto; }
877 + </style></head><body>' . $decoded_content . '</body></html>';
878 +
879 + $subject = isset( $_POST['subject'] ) ? sanitize_text_field( wp_unslash( $_POST['subject'] ) ) : 'Chat Session Transcript'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
880 + if ( empty( $subject ) ) { $subject = 'Chat Session Transcript'; }
881 + $headers = array( 'Content-Type: text/html; charset=UTF-8' );
882 + wp_mail( $to, $subject, $email_body, $headers );
883 + wp_send_json( array( 'success' => true, 'msg' => esc_html__( 'Session has been forwarded to email successfully', 'chatbot' ) ) );
884 + wp_die();
885 + } else {
886 + wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'No conversation found for this session', 'chatbot' ) ) );
887 + wp_die();
888 + }
889 +}
890 +
891 +// ─── AJAX: Session Hover Details ─────────────────────────────────────────────
892 +add_action( 'wp_ajax_wpbot_session_hover_details', 'wpbot_session_hover_details_free' );
893 +
894 +function wpbot_session_hover_details_free() {
895 + if ( ! current_user_can( 'manage_options' ) ) {
896 + wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Insufficient permissions', 'chatbot' ) ) );
897 + wp_die();
898 + }
899 + check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
900 + global $wpdb;
901 + $session_id = sanitize_text_field( $_POST['session_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
902 + $tableconversation = $wpdb->prefix . 'wpbot_conversation';
903 + $tableuser = $wpdb->prefix . 'wpbot_user';
904 + $email_from = get_option( 'qlcd_wp_chatbot_from_email' );
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
906 + if ( ! empty( $result ) ) {
907 + $result->email_from = $email_from;
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 + }
917 + }
918 + echo wp_json_encode( $result );
919 + wp_die();
920 +}
921 +
922 +// ─── AJAX: Send Reply Email ───────────────────────────────────────────────────
923 +add_action( 'wp_ajax_wpbot_send_reply_email', 'wpbot_send_reply_email_free' );
924 +
925 +function wpbot_send_reply_email_free() {
926 + if ( ! current_user_can( 'manage_options' ) ) {
927 + wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Insufficient permissions', 'chatbot' ) ) );
928 + wp_die();
929 + }
930 + check_ajax_referer( 'wpbot_session_ajax_nonce', 'security' );
931 +
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 );
935 + $subject = isset( $_POST['subject'] ) ? sanitize_text_field( wp_unslash( $_POST['subject'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
936 + $message = isset( $_POST['message'] ) ? wp_kses_post( wp_unslash( $_POST['message'] ) ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
937 +
938 + if ( empty( $to ) || empty( $message ) ) {
939 + wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Email and Message are required', 'chatbot' ) ) );
940 + wp_die();
941 + }
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 +
948 + $headers = array( 'Content-Type: text/html; charset=UTF-8' );
949 + if ( ! empty( $from ) && is_email( $from ) ) {
950 + $headers[] = 'From: ' . $from;
951 + }
952 +
953 + // Convert newlines to HTML line breaks
954 + $email_body = nl2br( $message );
955 +
956 + $sent = wp_mail( $to, $subject, $email_body, $headers );
957 +
958 + if ( $sent ) {
959 + wp_send_json( array( 'success' => true, 'msg' => esc_html__( 'Email replied successfully', 'chatbot' ) ) );
960 + } else {
961 + wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Failed to send email', 'chatbot' ) ) );
962 + }
963 + wp_die();
964 +}
965 +
966 +// ─── AJAX: Save Cron Settings ─────────────────────────────────────────────────
967 +add_action( 'wp_ajax_wpbot_seesion_corn_save', 'wpbot_seesion_corn_save_free' );
968 +
969 +function wpbot_seesion_corn_save_free() {
970 + if ( ! current_user_can( 'manage_options' ) ) {
971 + wp_send_json( array( 'success' => false, 'msg' => esc_html__( 'Insufficient permissions', 'chatbot' ) ) );
972 + wp_die();
973 + }
974 +
975 + $wbsession_ai_enabled = isset( $_POST['ai_enabled'] ) ? sanitize_text_field( $_POST['ai_enabled'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
976 + $wbsession_corn_schedule_interval = isset( $_POST['corn_schedule_interval'] ) ? sanitize_text_field( $_POST['corn_schedule_interval'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
977 + $qcld_wpsession_corn_promt = isset( $_POST['qcld_wpsession_corn_promt'] ) ? sanitize_text_field( $_POST['qcld_wpsession_corn_promt'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
978 + $qcld_wbsession_corn_starttime = isset( $_POST['qcld_wbsession_corn_starttime'] ) ? sanitize_text_field( $_POST['qcld_wbsession_corn_starttime'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
979 +
980 + $openai_enabled = get_option( 'ai_enabled' );
981 + $apiKey = get_option( 'open_ai_api_key' );
982 +
983 + if ( $openai_enabled != '1' ) {
984 + wp_send_json( array( 'success' => false, 'icon' => 'error', 'response' => esc_html__( 'OpenAI is Not Enabled', 'chatbot' ) ) );
985 + wp_die();
986 + }
987 + if ( $apiKey == '' ) {
988 + wp_send_json( array( 'success' => false, 'icon' => 'error', 'response' => esc_html__( 'OpenAI API key is not set', 'chatbot' ) ) );
989 + wp_die();
990 + }
991 +
992 + update_option( 'qcld_wbsession_ai_enable', $wbsession_ai_enabled );
993 + update_option( 'qcld_wbsession_corn_interval', $wbsession_corn_schedule_interval );
994 + update_option( 'qcld_wbsession_corn_starttime', $qcld_wbsession_corn_starttime );
995 + update_option( 'qcld_wpsession_corn_promt', $qcld_wpsession_corn_promt );
996 +
997 + wp_clear_scheduled_hook( 'qcld_wpsession_mysql_scraper_event' );
998 + wp_send_json( array( 'success' => true, 'icon' => 'success', 'response' => esc_html__( 'Settings Saved Successfully', 'chatbot' ) ) );
999 + wp_die();
1000 +}
1001 +
1002 +// ─── AJAX: Manual AI Scraper ──────────────────────────────────────────────────
1003 +add_action( 'wp_ajax_qcld_chatbot_session_mannual_scraper', 'qcld_chatbot_session_mannual_scraper_free' );
1004 +
1005 +if ( ! function_exists( 'qcld_chatbot_session_mannual_scraper_free' ) ) {
1006 + function qcld_chatbot_session_mannual_scraper_free() {
1007 + if ( ! current_user_can( 'manage_options' ) ) {
1008 + wp_send_json_error( array( 'msg' => 'Insufficient permissions.' ) );
1009 + wp_die();
1010 + }
1011 + if ( get_option( 'qcld_wbsession_ai_enable' ) != '1' ) {
1012 + wp_send_json( array( 'success' => false, 'icon' => 'error', 'response' => esc_html__( 'AI Insight is not enabled.', 'chatbot' ) ) );
1013 + wp_die();
1014 + }
1015 +
1016 + global $wpdb;
1017 + $num = isset( $_POST['wpchatbot_session_mannual_number'] ) ? intval( $_POST['wpchatbot_session_mannual_number'] ) : 20; // phpcs:ignore WordPress.Security.NonceVerification.Missing
1018 + if ( ! is_numeric( $num ) || $num <= 0 ) {
1019 + wp_send_json( array( 'success' => false, 'icon' => 'error', 'response' => esc_html__( 'Invalid number of sessions.', 'chatbot' ) ) );
1020 + wp_die();
1021 + }
1022 +
1023 + $tableuser = $wpdb->prefix . 'wpbot_user';
1024 + $tableconversation = $wpdb->prefix . 'wpbot_conversation';
1025 + $results = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
1026 + "SELECT u.id, c.user_id, u.date, u.session_id, c.id AS conversation_id, c.conversation
1027 + FROM $tableuser AS u LEFT JOIN $tableconversation AS c ON u.id = c.user_id
1028 + ORDER BY u.date DESC LIMIT %d", $num ) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1029 + );
1030 +
1031 + $remarkable_session = array();
1032 + foreach ( $results as $row ) {
1033 + $trimmed = wpsession_message_html_filter_free( htmlspecialchars_decode( $row->conversation ) );
1034 + $remarkable_session[] = array( 'id' => $row->session_id, 'conversation' => $trimmed );
1035 + }
1036 +
1037 + $keyword = get_option( 'qcld_wpsession_corn_promt' ) ?: 'Below is Chat conversation session data from our users on our website. Each conversation starts with an ID. Can you analyze each conversation and summarize each of them? Note down the total number of conversations you analyzed. Create a condensed summary at the end of your report for all the conversations. Point out the important questions asked by the users below the summary and include the IDs for the important points.';
1038 + $gptkeyword = array(
1039 + array( 'role' => 'system', 'content' => array( array( 'type' => 'input_text', 'text' => $keyword ) ) ),
1040 + array( 'role' => 'user', 'content' => array( array( 'type' => 'input_text', 'text' => wp_json_encode( $remarkable_session ) ) ) ),
1041 + );
1042 +
1043 + $api_key = get_option( 'open_ai_api_key' );
1044 + $engines = get_option( 'openai_engines' );
1045 + $post_fields = array( 'model' => $engines, 'input' => $gptkeyword );
1046 +
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 );
1059 +
1060 + $mess = json_decode( $result );
1061 + if ( ! empty( $mess->error ) ) {
1062 + wp_send_json( array( 'status' => 'error', 'icon' => 'error', 'msg' => esc_html( $mess->error->code ), 'response' => esc_html( $mess->error->message ) ) );
1063 + wp_die();
1064 + }
1065 +
1066 + $msg = isset( $mess->output[0]->content ) ? $mess->output[0]->content[0]->text : ( $mess->output[1]->content[0]->text ?? '' );
1067 + $msg = preg_replace( "/\r\n|\r|\n/", '<br/>', $msg );
1068 +
1069 + $to = get_option( 'qlcd_wp_chatbot_admin_email' ) ?: get_option( 'admin_email' );
1070 + $headers = array(
1071 + 'Content-Type: text/html; charset=UTF-8',
1072 + 'From: ' . esc_html( get_bloginfo( 'name' ) ) . ' <wordpress@' . wp_parse_url( get_site_url(), PHP_URL_HOST ) . '>',
1073 + );
1074 + wp_mail( $to, 'ChatBot Sessions Analysis', $msg, $headers );
1075 +
1076 + wp_send_json( array( 'status' => 'success', 'icon' => 'success', 'response' => 'Please Check Email for Report' ) );
1077 + wp_die();
1078 + }
1079 +}
1080 +
1081 +// ─── Helper: HTML filter for AI scraper ──────────────────────────────────────
1082 +if ( ! function_exists( 'wpsession_message_html_filter_free' ) ) {
1083 + function wpsession_message_html_filter_free( $html ) {
1084 + $dom = new DOMDocument();
1085 + libxml_use_internal_errors( true );
1086 + $dom->loadHTML( '<?xml encoding="utf-8" ?>' . $html );
1087 +
1088 + $lis = $dom->getElementsByTagName( 'li' );
1089 + $full_conversation = array();
1090 + foreach ( $lis as $li ) {
1091 + $agentDiv = $li->getElementsByTagName( 'div' )->item( 1 );
1092 + $paragraphDiv = $li->getElementsByTagName( 'div' )->item( 2 );
1093 + if ( $paragraphDiv ) {
1094 + $full_conversation[] = array(
1095 + 'id' => trim( $agentDiv->textContent ),
1096 + 'conversation' => trim( $paragraphDiv->textContent ),
1097 + );
1098 + }
1099 + }
1100 + return wp_json_encode( $full_conversation );
1101 + }
1102 +}
1103 +
1104 +// ─── Helper: Conversation Extract ────────────────────────────────────────────
1105 +if ( ! function_exists( 'qcld_wpch_conversation_extract' ) ) {
1106 + function qcld_wpch_conversation_extract( $html ) {
1107 + $doc = new DOMDocument();
1108 + libxml_use_internal_errors( true );
1109 + $doc->loadHTML( '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">' . $html );
1110 + $lis = iterator_to_array( $doc->getElementsByTagName( 'li' ) );
1111 + $messages = array();
1112 + foreach ( $lis as $li ) {
1113 + if ( strpos( $li->getAttribute( 'class' ), 'wp-chatbot-msg' ) !== false ) {
1114 + $messages[]['bot'] = trim( $li->textContent );
1115 + }
1116 + if ( strpos( $li->getAttribute( 'class' ), 'wp-chat-user-msg' ) !== false ) {
1117 + $messages[]['user'] = trim( $li->textContent );
1118 + }
1119 + }
1120 + $messages = array_filter( $messages, function( $val ) {
1121 + if ( isset( $val['bot'] ) && empty( $val['bot'] ) ) { return false; }
1122 + return true;
1123 + } );
1124 + return $messages;
1125 + }
1126 +}
1127 +
1128 +// ─── CSV Export Helpers ───────────────────────────────────────────────────────
1129 +add_action( 'admin_post_wpbot_conversations.csv', 'wpbot_conversations_csv_export_free' );
1130 +
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 +
1140 + global $wpdb;
1141 + $tableuser = $wpdb->prefix . 'wpbot_user'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
1142 + $tableconversation = $wpdb->prefix . 'wpbot_conversation'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.DirectDatabaseQuery.SchemaChange, PluginCheck.Security.DirectDB.UnescapedDBParameter
1143 + $userid = sanitize_text_field( $_GET['user_id'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
1144 +
1145 + $userinfo = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableuser WHERE 1 AND id = %d", $userid ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
1146 + $result = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $tableconversation WHERE 1 AND user_id = %d", $userid ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
1147 + $data = array();
1148 +
1149 + if ( ! empty( $result ) ) {
1150 + $data[] = array( 'User Name', $userinfo->name );
1151 + $data[] = array( 'User Email', $userinfo->email );
1152 + $data[] = array( 'Session ID', $userinfo->session_id );
1153 + $data[] = array( 'Date', gmdate( 'M,d,Y h:i:s A', strtotime( $userinfo->date ) ) );
1154 + $data[] = array( 'Bot Message', 'User Message' );
1155 + $messages = qcld_wpch_conversation_extract( htmlspecialchars_decode( $result->conversation ) );
1156 + foreach ( $messages as $message ) {
1157 + if ( isset( $message['bot'] ) && trim( $message['bot'] ) != '' ) {
1158 + $data[] = array( str_replace( '&nbsp;', ' ', trim( $message['bot'] ) ), '' );
1159 + }
1160 + if ( isset( $message['user'] ) && trim( $message['user'] ) != '' ) {
1161 + $data[] = array( '', str_replace( '&nbsp;', ' ', trim( $message['user'] ) ) );
1162 + }
1163 + }
1164 + }
1165 + qcld_wpbot_chatsession_download_send_headers( $userinfo->name . '_wpbot_chatsession_' . gmdate( 'Y-m-d' ) . '.csv' );
1166 + print wpbot_chatsession_array2csv( $data ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- raw CSV download, escaping would corrupt the file.
1167 +}
1168 +
1169 +if ( ! function_exists( 'wpbot_conversations_export' ) ) {
1170 + function wpbot_conversations_export( $user ) {
1171 + $user_id = isset( $user->id ) ? $user->id : $user;
1172 + $dataArray = array();
1173 + if ( ! empty( $user ) ) {
1174 + $messages = qcld_wpch_conversation_extract( htmlspecialchars_decode( $user->conversation ) );
1175 + $dataArray = array(
1176 + 'Session ID' => $user->session_id,
1177 + 'Date' => gmdate( 'M,d,Y h:i:s A', strtotime( $user->date ) ),
1178 + 'User Name' => $user->name,
1179 + 'User Email' => $user->email,
1180 + );
1181 + $conversations = '';
1182 + foreach ( $messages as $message ) {
1183 + if ( isset( $message['bot'] ) && trim( $message['bot'] ) != '' ) {
1184 + $conversations .= 'Bot Message: ' . str_replace( '&nbsp;', ' ', trim( $message['bot'] ) ) . "\n";
1185 + }
1186 + if ( isset( $message['user'] ) && trim( $message['user'] ) != '' ) {
1187 + $conversations .= 'User Message: ' . str_replace( '&nbsp;', ' ', trim( $message['user'] ) ) . "\n";
1188 + }
1189 + }
1190 + $dataArray['Conversations'] = $conversations;
1191 + }
1192 + $dataArray['Interaction'] = $user->interaction;
1193 + return $dataArray;
1194 + }
1195 +}
1196 +
1197 +if ( ! function_exists( 'qcld_wpbot_chatsession_download_send_headers' ) ) {
1198 + function qcld_wpbot_chatsession_download_send_headers( $filename ) {
1199 + $now = gmdate( 'D, d M Y H:i:s' );
1200 + header( 'Expires: Tue, 03 Jul 2001 06:00:00 GMT' );
1201 + header( 'Cache-Control: max-age=0, no-cache, must-revalidate, proxy-revalidate' );
1202 + header( "Last-Modified: {$now} GMT" );
1203 + header( 'Content-Encoding: UTF-8' );
1204 + header( 'Content-type: text/csv; charset=UTF-8' );
1205 + header( "Content-Disposition: attachment;filename={$filename}" );
1206 + header( 'Content-Transfer-Encoding: binary' );
1207 + }
1208 +}
1209 +
1210 +if ( ! function_exists( 'wpbot_chatsession_array2csv' ) ) {
1211 + function wpbot_chatsession_array2csv( array &$array ) {
1212 + if ( count( $array ) == 0 ) { return null; }
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.
1215 + $df = fopen( 'php://output', 'w' );
1216 + fputs( $df, chr( 0xEF ) . chr( 0xBB ) . chr( 0xBF ) ); // UTF-8 BOM
1217 + foreach ( $array as $data ) {
1218 + fputcsv( $df, array_keys( $data ), ',', '"', '\\' );
1219 + break;
1220 + }
1221 + foreach ( $array as $row ) {
1222 + fputcsv( $df, $row, ',', '"', '\\' );
1223 + }
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
1226 + return ob_get_clean();
1227 + }
1228 +}
1229 +
1230 +// ─── Shortcode: User Session History ─────────────────────────────────────────
1231 +if ( ! function_exists( 'qc_current_user_session' ) ) {
1232 + function qc_current_user_session() {
1233 + $user = wp_get_current_user();
1234 + global $wpdb;
1235 + $tableuser = $wpdb->prefix . 'wpbot_user';
1236 + $conversatios_table = $wpdb->prefix . 'wpbot_conversation';
1237 + $result = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM $tableuser AS u LEFT JOIN $conversatios_table AS c ON u.user_id = c.user_id WHERE u.user_id = %d", $user->ID ) ); // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared, WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
1238 + if ( ! $user->exists() ) {
1239 + return '<p>No user logged in.</p>';
1240 + }
1241 + ob_start(); ?>
1242 + <style>
1243 + .cell-content #wp-chatbot-messages-container { height: 200px; overflow: scroll; overflow-x: hidden; }
1244 + .session_start_url { display: none; }
1245 + .table-striped tr { border: 2px solid #888; }
1246 + </style>
1247 + <table class="table table-striped align-middle" id="chatsession-table">
1248 + <thead>
1249 + <tr class="table-primary">
1250 + <th class="text-left"><?php echo esc_html__( 'Date', 'chatbot' ); ?></th>
1251 + <th class="text-left"><?php echo esc_html__( 'Session ID', 'chatbot' ); ?></th>
1252 + <th class="text-left"><?php echo esc_html__( 'Name', 'chatbot' ); ?></th>
1253 + <th class="text-left" data-dt-order="disable"><?php echo esc_html__( 'Conversation', 'chatbot' ); ?></th>
1254 + </tr>
1255 + <?php foreach ( $result as $key => $value ) : ?>
1256 + <tr>
1257 + <td class="text-left"><?php echo esc_html( $value->date ); ?></td>
1258 + <td class="text-left"><?php echo esc_html( $value->session_id ); ?></td>
1259 + <td class="text-left"><?php echo esc_html( $value->name ); ?></td>
1260 + <td class="text-left"><div class="cell-content"><a class="qcld-modal-content" data-value="<?php echo esc_attr( $value->conversation ); ?>">view data</a></div></td>
1261 + </tr>
1262 + <?php endforeach; ?>
1263 + </thead>
1264 + </table>
1265 + <?php
1266 + return ob_get_clean();
1267 + }
1268 +}
1269 +add_shortcode( 'qcpress_user', 'qc_current_user_session' );
1270 +
1271 +// ─── WP-Cron: AI Insight Scheduled Email ─────────────────────────────────────
1272 +add_filter( 'cron_schedules', 'qcld_wpsession_wp_cron_schedule_free' );
1273 +
1274 +if ( ! function_exists( 'qcld_wpsession_wp_cron_schedule_free' ) ) {
1275 + function qcld_wpsession_wp_cron_schedule_free( $schedules ) {
1276 + $schedules['session_schedules'] = array(
1277 + 'interval' => ( get_option( 'qcld_wbsession_corn_interval' ) != null ) ? get_option( 'qcld_wbsession_corn_interval' ) : 86400,
1278 + 'display' => esc_attr__( 'Session min', 'chatbot' ),
1279 + );
1280 + return $schedules;
1281 + }
1282 +}
1283 +
1284 +$wpsession_corn_start_times = wp_date( 'Y-m-d' ) . ' ' . get_option( 'qcld_wbsession_corn_starttime' );
1285 +$wpsession_corn_start_time = strtotime( $wpsession_corn_start_times );
1286 +if ( ! wp_next_scheduled( 'qcld_wpsession_mysql_scraper_event' ) && ( get_option( 'qcld_wbsession_ai_enable' ) == '1' ) ) {
1287 + wp_schedule_event( $wpsession_corn_start_time, 'session_schedules', 'qcld_wpsession_mysql_scraper_event' );
1288 +}
1289 +
1290 +add_action( 'qcld_wpsession_mysql_scraper_event', 'qcld_wpsession_mysql_scraper_function_free' );
1291 +
1292 +if ( ! function_exists( 'qcld_wpsession_mysql_scraper_function_free' ) ) {
1293 + function qcld_wpsession_mysql_scraper_function_free() {
1294 + if ( get_option( 'qcld_wbsession_ai_enable' ) != '1' ) { return; }
1295 +
1296 + global $wpdb;
1297 + $interval_hours = ( (int) get_option( 'qcld_wbsession_corn_interval' ) ?: 86400 ) / 3600;
1298 + $tableuser = $wpdb->prefix . 'wpbot_user';
1299 + $tableconversation = $wpdb->prefix . 'wpbot_conversation';
1300 + $results = $wpdb->get_results( $wpdb->prepare( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, PluginCheck.Security.DirectDB.UnescapedDBParameter
1301 + "SELECT u.id, c.user_id, u.date, u.session_id, c.id, c.conversation
1302 + FROM $tableuser AS u LEFT JOIN $tableconversation AS c ON u.id = c.user_id
1303 + WHERE u.date >= (NOW() - INTERVAL %d HOUR) ORDER BY u.date DESC", $interval_hours ) // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared
1304 + );
1305 +
1306 + $remarkable_session = array();
1307 + foreach ( $results as $row ) {
1308 + $remarkable_session[] = array( 'id' => $row->session_id, 'conversation' => wpsession_message_html_filter_free( htmlspecialchars_decode( $row->conversation ) ) );
1309 + }
1310 +
1311 + $keyword = get_option( 'qcld_wpsession_corn_promt' ) ?: 'Below is Chat conversation session data from our users on our website. Each conversation starts with an ID. Can you analyze each conversation and summarize each of them?';
1312 + $gptkeyword = array(
1313 + array( 'role' => 'system', 'content' => array( array( 'type' => 'input_text', 'text' => $keyword ) ) ),
1314 + array( 'role' => 'user', 'content' => array( array( 'type' => 'input_text', 'text' => wp_json_encode( $remarkable_session ) ) ) ),
1315 + );
1316 +
1317 + $api_key = get_option( 'open_ai_api_key' );
1318 + $engines = get_option( 'openai_engines' );
1319 + $post_fields = array( 'model' => $engines, 'input' => $gptkeyword );
1320 +
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 );
1333 +
1334 + $mess = json_decode( $result );
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.' );
1336 + $msg = preg_replace( "/\r\n|\r|\n/", '<br/>', $msg );
1337 +
1338 + $to = get_option( 'qlcd_wp_chatbot_admin_email' ) ?: get_option( 'admin_email' );
1339 + $headers = array(
1340 + 'Content-Type: text/html; charset=UTF-8',
1341 + 'From: ' . esc_html( get_bloginfo( 'name' ) ) . ' <wordpress@' . wp_parse_url( get_site_url(), PHP_URL_HOST ) . '>',
1342 + );
1343 + wp_mail( $to, 'ChatBot Sessions Analysis', $msg, $headers );
1344 + }
1345 +}
1346 +
1347 +// ─── Reports (Bot - Reports submenu) ─────────────────────────────────────────
1348 +require_once QCLD_CHATBOT_FREE_SESSION_DIR_PATH . 'reports/chatbot-history-reporting.php';