PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.7.7
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.7.7
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 8.5.4 8.5.3 8.5.2 All 533 releases
← All changes | includes/class-common-function.php +538 -538 8.7.58.7.7 View file →
@@ -1,538 +1,538 @@
1 -<?php
2 -if (!defined('ABSPATH')) exit; // Exit if accessed directly
3 -/**
4 - * Common functions class
5 - */
6 -class Qcld_WPBot_Common_Functions {
7 -
8 -
9 - private static $instance = null;
10 -
11 - public static function instance() {
12 - if ( is_null( self::$instance ) ) {
13 - self::$instance = new self();
14 - }
15 - return self::$instance;
16 - }
17 -
18 - private function __construct() {
19 - add_action('wp_ajax_wpbot_save_feedback', array( $this, 'wpbot_save_feedback') );
20 - add_action('wp_ajax_nopriv_wpbot_save_feedback', array( $this,'wpbot_save_feedback') );
21 - add_action( 'wp_ajax_wpbot_save_report', array( $this, 'wpbot_save_report') );
22 - add_action( 'wp_ajax_nopriv_wpbot_save_report', array( $this, 'wpbot_save_report') );
23 - add_action('wp_ajax_nopriv_get_relevant_pages', array($this, 'qcld_get_relevant_pages_ajax'));
24 - add_action('wp_ajax_rate_limit_settings_option', array($this, 'rate_limit_settings_option_callback'));
25 - register_activation_hook(__FILE__, array('qcld_wpopenai_addons', 'schedule_rate_limit_reset_events'));
26 - add_filter('cron_schedules', array($this, 'qcld_user_role_rate_cron_schedule'));
27 - add_action('rate_limit_checker', array($this, 'rate_limit_checker'));
28 - add_action('qcld_openai_user_rate_cal', array($this, 'qcld_openai_user_rate_cal'));
29 - add_action('reset_rate_limit_used_counts', [$this, 'reset_rate_limit_used_counts'], 10, 1);
30 -
31 - }
32 - /**
33 - * Remove stopwords from search query
34 - *
35 - * @param string $query The search query
36 - * @param array $stopwords Array of stopwords to remove
37 - * @return string Query with stopwords removed
38 - */
39 - public static function qcpd_remove_wa_stopwords($query, $stopwords) {
40 - return preg_replace('/\b('.implode('|',$stopwords).')\b/','',$query);
41 - }
42 -
43 - /**
44 - * Get relevant page links based on search query
45 - *
46 - * @param string $search_query The search query
47 - * @return array Array of relevant page links
48 - */
49 - public static function qcpd_relevant_pagelink($search_query) {
50 - $stopwords = explode(',', get_option('qlcd_wp_chatbot_stop_words'));
51 -
52 - $finalQueryWordsWithoutStopWords = self::qcpd_remove_wa_stopwords(strtolower($search_query), $stopwords);
53 -
54 - $cleanWordsWithoutPunctuationMarks = preg_replace('/[\p{P}]/u', '', $finalQueryWordsWithoutStopWords);
55 -
56 - $q = trim($cleanWordsWithoutPunctuationMarks);
57 -
58 - $links = [];
59 -
60 - $post_type_array = get_option('qcld_openai_relevant_post');
61 -
62 - $the_query = new WP_Query(array(
63 - 'post_status' => 'publish',
64 - 'posts_per_page' => 5,
65 - 's' => esc_attr($q),
66 - 'post_type' => $post_type_array
67 - ));
68 -
69 - if($the_query->have_posts()) {
70 - while($the_query->have_posts()) {
71 - $the_query->the_post();
72 -
73 - $url = esc_url(get_permalink());
74 - $link = '<a href=' . $url . ' target="_blank">' . get_the_title() . '</a>';
75 - array_push($links, $link);
76 - }
77 - wp_reset_postdata();
78 - }
79 -
80 - $links = array_unique($links);
81 - return $links;
82 - }
83 -
84 -
85 - public function wpbot_save_feedback() {
86 - check_ajax_referer('wp_chatbot', 'nonce');
87 - global $wpdb;
88 -
89 - $table = $wpdb->prefix . 'wpbot_chat_report';
90 -
91 - $user_id = intval(wp_unslash($_POST['user_id']));
92 - $conversation_id= intval(wp_unslash($_POST['conversation_id']));
93 - $message = sanitize_text_field(wp_unslash($_POST['message']));
94 - $feedback = sanitize_text_field(wp_unslash($_POST['feedback'])); // "like" or "dislike"
95 - $meta_info = sanitize_textarea_field(wp_unslash($_POST['meta_info']));
96 - $date = current_time('mysql');
97 -
98 - $inserted = $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
99 - $table, array(
100 - 'user_id' => $user_id,
101 - 'conversation_id'=> $conversation_id,
102 - 'message' => $message,
103 - 'feedback' => $feedback,
104 - 'meta_info' => $meta_info,
105 - 'created_at' => $date
106 - ));
107 -
108 - if ($inserted !== false) {
109 - wp_send_json_success(array('message' => 'Feedback saved.'));
110 - } else {
111 - wp_send_json_error(array('message' => 'Failed to save feedback.'));
112 - }
113 - }
114 -
115 - /**
116 - * Save report func
117 - *
118 - */
119 -
120 -
121 - public function wpbot_save_report() {
122 - check_ajax_referer('wp_chatbot', 'nonce');
123 - global $wpdb;
124 - $table_report = $wpdb->prefix . 'wpbot_chat_report';
125 -
126 - $email = sanitize_email(wp_unslash($_POST['email']));
127 - $message = sanitize_textarea_field(wp_unslash($_POST['message']));
128 - $report_text = sanitize_textarea_field(wp_unslash($_POST['report_text']));
129 -
130 - $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
131 - $table_report,
132 - [
133 - 'user_id' => get_current_user_id(), // or match from wpbot_user.
134 - 'message' => $message,
135 - 'meta_info' => maybe_serialize( [ 'email' => $email, 'report_text' => $report_text ] ),
136 - ],
137 - [ '%d', '%s', '%s' ]
138 - );
139 -
140 - // Send report email to admin
141 - $admin_email = get_option( 'admin_email' );
142 - $subject = "New Chat Report";
143 - $body = "Reported Message:\n{$message}\n\nReport Text:\n{$report_text}\n\nUser Email: {$email}";
144 - wp_mail( $admin_email, $subject, $body );
145 -
146 - wp_send_json_success();
147 - }
148 - public function rate_limit_settings_option_callback()
149 - {
150 -
151 - // Check is admin and verify nonce
152 - if (! current_user_can('manage_options')) {
153 - wp_send_json_error('Unauthorized');
154 - wp_die();
155 - }
156 - check_ajax_referer( 'wp_chatbot', 'nonce' );
157 - // Save the rate limiting enabled/disabled setting
158 - if (isset($_POST['is_rate_limiting_enabled'])) {
159 - $is_rate_limiting_enabled = intval( wp_unslash( $_POST['is_rate_limiting_enabled'] ) );
160 - update_option('is_rate_limiting_enabled', $is_rate_limiting_enabled);
161 - }
162 - // Save the rate limits for each role
163 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
164 - if (isset($_POST['rate_limits']) && is_array( $_POST['rate_limits'] )) {
165 - $rate_limits = array_map( 'sanitize_text_field', wp_unslash( $_POST['rate_limits'] ) );
166 - foreach ($rate_limits as $role => $limit) {
167 - $option_name = 'rate_limit_' . sanitize_key( $role );
168 - $limit_value = intval($limit);
169 - update_option($option_name, $limit_value);
170 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
171 - $timeframe_raw = isset( $_POST['rate_limit_timeframes'][ $role ] ) ? sanitize_text_field( wp_unslash( $_POST['rate_limit_timeframes'][ $role ] ) ) : '24';
172 - $timeframe = intval($timeframe_raw) * 3600; // Convert hours to seconds
173 - update_option('rate_limit_timeframe_' . $role, $timeframe);
174 - }
175 - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
176 - $guest_timeframe_raw = isset( $_POST['rate_limit_timeframes']['guest'] ) ? sanitize_text_field( wp_unslash( $_POST['rate_limit_timeframes']['guest'] ) ) : '24';
177 - $timeframe = intval($guest_timeframe_raw) * 3600; // Convert hours to seconds
178 - update_option('rate_limit_timeframe_guest', $timeframe);
179 - }
180 - // In your rate_limit_settings_option_callback(), after updating options:
181 - $this->schedule_rate_limit_reset_events();
182 - wp_send_json_success('Settings saved');
183 - wp_die();
184 - }
185 - public function qcld_user_role_rate_cron_schedule($schedules)
186 - {
187 - $roles = wp_roles()->roles;
188 - foreach ($roles as $role => $details) {
189 - $timeframe = intval(get_option('rate_limit_timeframe_' . $role, true));
190 - // Minimum 3600 seconds
191 - $schedules['session_schedules_rate_limit_' . $role] = array(
192 - 'interval' => $timeframe,
193 - 'display' => esc_html( 'session_schedules_rate_limit_' . $role ),
194 - );
195 - // Schedule the cron job for each role if not already scheduled);
196 - }
197 - return $schedules;
198 - }
199 - // wpshedule event to reset the rate limit used counts
200 - public function schedule_rate_limit_reset_events()
201 - {
202 - // Ensure WordPress cron functions are available
203 - if (! function_exists('wp_schedule_event')) {
204 - require_once ABSPATH . 'wp-includes/wp-cron.php';
205 - }
206 - $roles = wp_roles()->roles;
207 - foreach ($roles as $role => $d) {
208 - // Unschedule all existing events for this role
209 - $timestamp = wp_next_scheduled('reset_rate_limit_used_counts', array($role));
210 - while ($timestamp) {
211 - wp_unschedule_event($timestamp, 'reset_rate_limit_used_counts', array($role));
212 - $timestamp = wp_next_scheduled('reset_rate_limit_used_counts', array($role));
213 - }
214 - // Schedule new event with updated interval
215 - wp_schedule_event(time(), 'session_schedules_rate_limit_' . $role, 'reset_rate_limit_used_counts', array($role));
216 - }
217 - }
218 - public function qcld_wpsession_cron_deactivation()
219 - {
220 - $timestamp = wp_next_scheduled('reset_rate_limit_used_counts');
221 - wp_unschedule_event($timestamp, 'reset_rate_limit_used_counts');
222 - }
223 - public function rate_limit_checker()
224 - {
225 - $qlcd_wp_chatbot_ai_rate_limiting_message = qcld_wb_chatbot_func_str_replace(
226 - maybe_unserialize(get_option('qlcd_wp_chatbot_ai_rate_limiting_message', true))
227 - );
228 - if (get_option('is_stream_enabled') == 1) {
229 - $response = array(
230 - 'status' => 'error',
231 - 'message' => esc_html(
232 - ( is_array( $qlcd_wp_chatbot_ai_rate_limiting_message ) && ! empty( $qlcd_wp_chatbot_ai_rate_limiting_message[0] ) )
233 - ? $qlcd_wp_chatbot_ai_rate_limiting_message[0]
234 - : __( 'Rate limit exceeded. Please try again later.', 'chatbot' )
235 - ),
236 - );
237 - } else {
238 - $response = array(
239 - 'status' => 'success',
240 - 'message' => esc_html(
241 - ( is_array( $qlcd_wp_chatbot_ai_rate_limiting_message ) && ! empty( $qlcd_wp_chatbot_ai_rate_limiting_message[0] ) )
242 - ? $qlcd_wp_chatbot_ai_rate_limiting_message[0]
243 - : __( 'Rate limit exceeded. Please try again later.', 'chatbot' )
244 - ),
245 - );
246 - }
247 - if (is_user_logged_in()) {
248 - $user = wp_get_current_user();
249 - if (! empty($user->roles) && is_array($user->roles)) {
250 - $role = $user->roles[0];
251 - $option_name = 'rate_limit_' . $role;
252 - $limit = get_option($option_name);
253 - $rate_limit = get_user_meta(get_current_user_id(), 'qcld_openai_user_rate_limit', true);
254 - if (($limit >= $rate_limit) || $limit == 0) {
255 - return; // allowed
256 - } else {
257 - if (get_option('is_stream_enabled') == 1) {
258 - wp_send_json($response); //Proper JSON with header.
259 - } else {
260 - echo json_encode($response);
261 - wp_die();
262 - }
263 - }
264 - }
265 - } else {
266 - $option_name = 'rate_limit_guest';
267 - $limit = get_option($option_name);
268 -
269 - if (session_status() == PHP_SESSION_NONE) {
270 - session_start();
271 - }
272 -
273 - $guest_rate_limit = isset($_SESSION['guest_rate_limit']) ? intval($_SESSION['guest_rate_limit']) : 0;
274 -
275 - if ($limit >= $guest_rate_limit || $limit == 0) {
276 - return; // allowed
277 - } else {
278 - if (get_option('is_stream_enabled') == 1) {
279 - wp_send_json($response); //Proper JSON with header.
280 - } else {
281 - echo json_encode($response);
282 - // Return role if limit exceeded
283 - wp_die();
284 - }
285 - }
286 - }
287 - }
288 -
289 - public function reset_rate_limit_used_counts($role)
290 - {
291 - if (empty($role)) {
292 - return;
293 - }
294 - // Resetting rate limit used counts for role.
295 - // loop through each role
296 - // for each role get all users with that role
297 - $roles = wp_roles()->roles;
298 - foreach ($roles as $list_roll => $details) {
299 - if ($list_roll != $role) {
300 - continue;
301 - } else {
302 - $option_name = 'rate_limit_' . $role;
303 - $limit = get_option($option_name); // Default to if not set
304 - $users = get_users(array('role' => $role));
305 - foreach ($users as $user) {
306 - update_user_meta($user->ID, 'qcld_openai_user_rate_limit', 0);
307 - }
308 - }
309 - }
310 - }
311 - public function qcld_openai_user_rate_cal($update)
312 - {
313 - if (is_user_logged_in()) {
314 - $rate_limit = get_user_meta(get_current_user_id(), 'qcld_openai_user_rate_limit', true);
315 - if ($update != '') {
316 - $rate_limit = intval($rate_limit) + intval($update);
317 - }
318 - update_user_meta(get_current_user_id(), 'qcld_openai_user_rate_limit', $rate_limit);
319 - return;
320 - } else {
321 - // for guest users we will use session to store the rate limit
322 - // if session is not started then start it
323 - if (session_status() == PHP_SESSION_NONE) {
324 - session_start();
325 - }
326 - // we will use guest_id to identify the guest user
327 - if (! isset($_SESSION['guest_id'])) {
328 - $user_ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
329 - $session_id = md5($user_ip . time());
330 - $_SESSION['guest_id'] = $session_id;
331 - $_SESSION['guest_id_time'][$session_id] = time();
332 - get_option('rate_', 10);
333 - // if guest_id_time is set and it is older than 24 hours then unset the guest_id
334 - // we will store the time when the guest_id was created in session
335 - $timeframe = get_option('rate_limit_timeframe_guest', true);
336 - $timeframe = intval($timeframe) * 3600; // convert hours to seconds
337 - if (isset($_SESSION['guest_id_time'][$session_id]) && (time() - $_SESSION['guest_id_time'][$session_id]) > $timeframe) {
338 - unset($_SESSION['guest_id']);
339 - unset($_SESSION['guest_id_time']);
340 - }
341 - // we will store the time when the guest_id was created in session
342 - $_SESSION['guest_id_time'][$session_id] = time();
343 - $_SESSION['guest_rate_limit'] = 0;
344 - }
345 - // update the rate limit
346 - // if update is not empty then add it to the existing rate limit else set it
347 - if ($update != '') {
348 - $guest_rate_limit = isset($_SESSION['guest_rate_limit']) ? intval($_SESSION['guest_rate_limit']) : 0;
349 - // add the update to the existing rate limit
350 - // set the rate limit in session
351 - $_SESSION['guest_rate_limit'] = $guest_rate_limit + $update;
352 - return;
353 - }
354 - }
355 - }
356 -
357 - public static function format_and_save_ai_form_response($msg) {
358 - if (empty($msg) || (strpos($msg, 'AI_FORM_DATA') === false)) {
359 - return $msg;
360 - }
361 -
362 - $pattern = '/(?:<[^>]+>)*\s*(?:__|<strong>|<b>)?AI_FORM_DATA(?:__|<\/strong>|<\/b>)?[\s\S]*?(?:__|<strong>|<b>)?AI_FORM_DATA_END(?:__|<\/strong>|<\/b>)?\s*(?:<\/[^>]+>)*/i';
363 -
364 - if (preg_match($pattern, $msg, $matches)) {
365 - $block = $matches[0];
366 - if (preg_match('/\{[\s\S]*\}/', $block, $json_matches)) {
367 - $json_str = trim($json_matches[0]);
368 - $data = json_decode($json_str, true);
369 -
370 - if ($data && isset($data['form_title'])) {
371 - $post_title = sanitize_text_field($data['form_title']) . ' - ' . current_time('mysql');
372 - $post_id = wp_insert_post(array(
373 - 'post_title' => $post_title,
374 - 'post_type' => 'wpbot_form_entry',
375 - 'post_status' => 'publish'
376 - ));
377 -
378 - if ($post_id && isset($data['data']) && is_array($data['data'])) {
379 - $email_body = "<h2>" . esc_html__('New AI Chat Submission', 'chatbot') . "</h2>";
380 - $email_body .= "<p><strong>" . esc_html__('Chat', 'chatbot') . ":</strong> " . sanitize_text_field($data['form_title']) . "</p>";
381 - $email_body .= "<table border='1' cellpadding='10' cellspacing='0' style='border-collapse: collapse; width: 100%; max-width: 600px; font-family: sans-serif;'>";
382 -
383 - // Extract user's email for Reply-To (not From, to avoid SMTP rejection)
384 - $reply_to = '';
385 - foreach ($data['data'] as $key => $value) {
386 - update_post_meta($post_id, sanitize_text_field($key), sanitize_text_field($value));
387 - $clean_key = ucwords(str_replace(array('-', '_'), ' ', sanitize_text_field($key)));
388 - $email_body .= "<tr><td style='background: #f4f4f4; width: 40%;'><strong>" . esc_html($clean_key) . "</strong></td><td>" . esc_html(sanitize_text_field($value)) . "</td></tr>";
389 -
390 - // Extract email from value — handles plain, [bracketed], and combined answers
391 - if (preg_match('/\[?([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})\]?/', $value, $email_match)) {
392 - $candidate = sanitize_email(trim($email_match[1]));
393 - if (is_email($candidate)) {
394 - $reply_to = $candidate;
395 - }
396 - }
397 - }
398 - $email_body .= "</table>";
399 -
400 - $email_enabled = true;
401 - $saved_forms = get_option('wpbot_ai_forms', array());
402 - if (is_array($saved_forms)) {
403 - foreach ($saved_forms as $form) {
404 - if ($form['title'] == sanitize_text_field($data['form_title'])) {
405 - if (isset($form['email'])) {
406 - $email_enabled = $form['email'] == 1;
407 - }
408 - $per_action_emails = isset($form['email_addresses']) ? trim($form['email_addresses']) : '';
409 - break;
410 - }
411 - }
412 - }
413 -
414 - if ($email_enabled) {
415 - if (!empty($per_action_emails)) {
416 - $to = array_map('trim', explode(',', $per_action_emails));
417 - } else {
418 - $default = get_option('qlcd_wp_chatbot_admin_email', '');
419 - $to = !empty($default) ? array_map('trim', explode(',', $default)) : get_option('admin_email');
420 - }
421 - $subject = sanitize_text_field($data['form_title']) . " - " . esc_html__('New AI Chat Submission', 'chatbot');
422 - $headers = array('Content-Type: text/html; charset=UTF-8');
423 - if (!empty($reply_to)) {
424 - $headers[] = 'Reply-To: ' . $reply_to;
425 - }
426 - wp_mail($to, $subject, $email_body, $headers);
427 - }
428 - }
429 -
430 - $summary_html = '<div class="ai-form-summary-card" style="background: #f4f6f9; border-left: 4px solid #0073aa; padding: 12px 14px; margin: 10px 0; border-radius: 4px; font-size: 13px; line-height: 1.5; color: #333;">';
431 - if (!empty($data['form_title'])) {
432 - $summary_html .= '<div style="font-weight: 600; color: #0073aa; margin-bottom: 8px; text-transform: capitalize; font-size: 14px;">' . esc_html($data['form_title']) . '</div>';
433 - }
434 - if (!empty($data['data']) && is_array($data['data'])) {
435 - $summary_html .= '<table style="width: 100%; border-collapse: collapse; margin-top: 4px;">';
436 - foreach ($data['data'] as $k => $v) {
437 - $clean_k = ucwords(str_replace(array('-', '_'), ' ', sanitize_text_field($k)));
438 - $summary_html .= '<tr><td style="padding: 3px 6px 3px 0; color: #555; font-weight: 600; width: 42%; vertical-align: top;">' . esc_html($clean_k) . ':</td><td style="padding: 3px 0; color: #222; vertical-align: top;">' . esc_html($v) . '</td></tr>';
439 - }
440 - $summary_html .= '</table>';
441 - }
442 - $summary_html .= '</div>';
443 -
444 - $msg = preg_replace($pattern, $summary_html, $msg);
445 - }
446 - }
447 - }
448 -
449 - return $msg;
450 - }
451 -
452 - public static function is_ai_action_in_progress($history = array(), $keyword = '') {
453 - if (!empty($_POST['action_prompt']) || !empty($_POST['is_ai_actions_playground'])) {
454 - return true;
455 - }
456 - $saved_ai_forms = get_option('wpbot_ai_forms', array());
457 - if (empty($saved_ai_forms) || !is_array($saved_ai_forms)) {
458 - return false;
459 - }
460 -
461 - $form_titles = array();
462 - foreach ($saved_ai_forms as $form) {
463 - if (!empty($form['title'])) {
464 - $form_titles[] = strtolower(trim($form['title']));
465 - }
466 - }
467 -
468 - if (empty($form_titles)) {
469 - return false;
470 - }
471 -
472 - $clean_kw = strtolower(trim($keyword));
473 - if (!empty($clean_kw)) {
474 - foreach ($form_titles as $title) {
475 - if ($clean_kw === $title || strpos($clean_kw, $title) !== false || strpos($title, $clean_kw) !== false) {
476 - return true;
477 - }
478 - }
479 - }
480 -
481 - if (empty($history)) {
482 - if (!empty($_POST['ai_history'])) {
483 - $history = json_decode(wp_unslash($_POST['ai_history']), true);
484 - } elseif (!empty($_POST['wpwHistory'])) {
485 - $history_html = wp_unslash($_POST['wpwHistory']);
486 - if (is_string($history_html)) {
487 - $dom = new \DOMDocument();
488 - libxml_use_internal_errors(true);
489 - $dom->loadHTML('<?xml encoding="utf-8" ?>' . $history_html);
490 - libxml_clear_errors();
491 - $xpath = new \DOMXPath($dom);
492 - $lis = $xpath->query('//li');
493 - $history = array();
494 - if ($lis && $lis->length > 0) {
495 - foreach ($lis as $li) {
496 - $class = $li->getAttribute('class');
497 - $text = trim($li->textContent);
498 - if ($text !== '') {
499 - $role = (strpos($class, 'chatbot-msg') !== false) ? 'assistant' : 'user';
500 - $history[] = array('role' => $role, 'content' => $text);
501 - }
502 - }
503 - }
504 - }
505 - }
506 - }
507 -
508 - if (!empty($history) && is_array($history)) {
509 - $last_form_start_idx = -1;
510 - $last_form_end_idx = -1;
511 - foreach ($history as $idx => $item) {
512 - $content = is_array($item) ? strtolower($item['content'] ?? '') : '';
513 - if (empty($content) && is_string($item)) {
514 - $content = strtolower($item);
515 - }
516 - foreach ($form_titles as $title) {
517 - if (!empty($title) && strpos($content, $title) !== false) {
518 - $last_form_start_idx = $idx;
519 - }
520 - }
521 - if (strpos($content, 'ai_form_data') !== false || strpos($content, 'ai-form-summary-card') !== false) {
522 - $last_form_end_idx = $idx;
523 - }
524 - }
525 -
526 - if ($last_form_start_idx !== -1 && $last_form_start_idx >= $last_form_end_idx) {
527 - return true;
528 - }
529 - }
530 -
531 - return false;
532 - }
533 -}
534 -
535 -/**
536 - * Instantiate the class
537 - */
538 -Qcld_WPBot_Common_Functions::instance();
1 +<?php
2 +if (!defined('ABSPATH')) exit; // Exit if accessed directly
3 +/**
4 + * Common functions class
5 + */
6 +class Qcld_WPBot_Common_Functions {
7 +
8 +
9 + private static $instance = null;
10 +
11 + public static function instance() {
12 + if ( is_null( self::$instance ) ) {
13 + self::$instance = new self();
14 + }
15 + return self::$instance;
16 + }
17 +
18 + private function __construct() {
19 + add_action('wp_ajax_wpbot_save_feedback', array( $this, 'wpbot_save_feedback') );
20 + add_action('wp_ajax_nopriv_wpbot_save_feedback', array( $this,'wpbot_save_feedback') );
21 + add_action( 'wp_ajax_wpbot_save_report', array( $this, 'wpbot_save_report') );
22 + add_action( 'wp_ajax_nopriv_wpbot_save_report', array( $this, 'wpbot_save_report') );
23 + add_action('wp_ajax_nopriv_get_relevant_pages', array($this, 'qcld_get_relevant_pages_ajax'));
24 + add_action('wp_ajax_rate_limit_settings_option', array($this, 'rate_limit_settings_option_callback'));
25 + register_activation_hook(__FILE__, array('qcld_wpopenai_addons', 'schedule_rate_limit_reset_events'));
26 + add_filter('cron_schedules', array($this, 'qcld_user_role_rate_cron_schedule'));
27 + add_action('rate_limit_checker', array($this, 'rate_limit_checker'));
28 + add_action('qcld_openai_user_rate_cal', array($this, 'qcld_openai_user_rate_cal'));
29 + add_action('reset_rate_limit_used_counts', [$this, 'reset_rate_limit_used_counts'], 10, 1);
30 +
31 + }
32 + /**
33 + * Remove stopwords from search query
34 + *
35 + * @param string $query The search query
36 + * @param array $stopwords Array of stopwords to remove
37 + * @return string Query with stopwords removed
38 + */
39 + public static function qcpd_remove_wa_stopwords($query, $stopwords) {
40 + return preg_replace('/\b('.implode('|',$stopwords).')\b/','',$query);
41 + }
42 +
43 + /**
44 + * Get relevant page links based on search query
45 + *
46 + * @param string $search_query The search query
47 + * @return array Array of relevant page links
48 + */
49 + public static function qcpd_relevant_pagelink($search_query) {
50 + $stopwords = explode(',', get_option('qlcd_wp_chatbot_stop_words'));
51 +
52 + $finalQueryWordsWithoutStopWords = self::qcpd_remove_wa_stopwords(strtolower($search_query), $stopwords);
53 +
54 + $cleanWordsWithoutPunctuationMarks = preg_replace('/[\p{P}]/u', '', $finalQueryWordsWithoutStopWords);
55 +
56 + $q = trim($cleanWordsWithoutPunctuationMarks);
57 +
58 + $links = [];
59 +
60 + $post_type_array = get_option('qcld_openai_relevant_post');
61 +
62 + $the_query = new WP_Query(array(
63 + 'post_status' => 'publish',
64 + 'posts_per_page' => 5,
65 + 's' => esc_attr($q),
66 + 'post_type' => $post_type_array
67 + ));
68 +
69 + if($the_query->have_posts()) {
70 + while($the_query->have_posts()) {
71 + $the_query->the_post();
72 +
73 + $url = esc_url(get_permalink());
74 + $link = '<a href=' . $url . ' target="_blank">' . get_the_title() . '</a>';
75 + array_push($links, $link);
76 + }
77 + wp_reset_postdata();
78 + }
79 +
80 + $links = array_unique($links);
81 + return $links;
82 + }
83 +
84 +
85 + public function wpbot_save_feedback() {
86 + check_ajax_referer('wp_chatbot', 'nonce');
87 + global $wpdb;
88 +
89 + $table = $wpdb->prefix . 'wpbot_chat_report';
90 +
91 + $user_id = intval(wp_unslash($_POST['user_id']));
92 + $conversation_id= intval(wp_unslash($_POST['conversation_id']));
93 + $message = sanitize_text_field(wp_unslash($_POST['message']));
94 + $feedback = sanitize_text_field(wp_unslash($_POST['feedback'])); // "like" or "dislike"
95 + $meta_info = sanitize_textarea_field(wp_unslash($_POST['meta_info']));
96 + $date = current_time('mysql');
97 +
98 + $inserted = $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
99 + $table, array(
100 + 'user_id' => $user_id,
101 + 'conversation_id'=> $conversation_id,
102 + 'message' => $message,
103 + 'feedback' => $feedback,
104 + 'meta_info' => $meta_info,
105 + 'created_at' => $date
106 + ));
107 +
108 + if ($inserted !== false) {
109 + wp_send_json_success(array('message' => 'Feedback saved.'));
110 + } else {
111 + wp_send_json_error(array('message' => 'Failed to save feedback.'));
112 + }
113 + }
114 +
115 + /**
116 + * Save report func
117 + *
118 + */
119 +
120 +
121 + public function wpbot_save_report() {
122 + check_ajax_referer('wp_chatbot', 'nonce');
123 + global $wpdb;
124 + $table_report = $wpdb->prefix . 'wpbot_chat_report';
125 +
126 + $email = sanitize_email(wp_unslash($_POST['email']));
127 + $message = sanitize_textarea_field(wp_unslash($_POST['message']));
128 + $report_text = sanitize_textarea_field(wp_unslash($_POST['report_text']));
129 +
130 + $wpdb->insert( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
131 + $table_report,
132 + [
133 + 'user_id' => get_current_user_id(), // or match from wpbot_user.
134 + 'message' => $message,
135 + 'meta_info' => maybe_serialize( [ 'email' => $email, 'report_text' => $report_text ] ),
136 + ],
137 + [ '%d', '%s', '%s' ]
138 + );
139 +
140 + // Send report email to admin
141 + $admin_email = get_option( 'admin_email' );
142 + $subject = "New Chat Report";
143 + $body = "Reported Message:\n{$message}\n\nReport Text:\n{$report_text}\n\nUser Email: {$email}";
144 + wp_mail( $admin_email, $subject, $body );
145 +
146 + wp_send_json_success();
147 + }
148 + public function rate_limit_settings_option_callback()
149 + {
150 +
151 + // Check is admin and verify nonce
152 + if (! current_user_can('manage_options')) {
153 + wp_send_json_error('Unauthorized');
154 + wp_die();
155 + }
156 + check_ajax_referer( 'wp_chatbot', 'nonce' );
157 + // Save the rate limiting enabled/disabled setting
158 + if (isset($_POST['is_rate_limiting_enabled'])) {
159 + $is_rate_limiting_enabled = intval( wp_unslash( $_POST['is_rate_limiting_enabled'] ) );
160 + update_option('is_rate_limiting_enabled', $is_rate_limiting_enabled);
161 + }
162 + // Save the rate limits for each role
163 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
164 + if (isset($_POST['rate_limits']) && is_array( $_POST['rate_limits'] )) {
165 + $rate_limits = array_map( 'sanitize_text_field', wp_unslash( $_POST['rate_limits'] ) );
166 + foreach ($rate_limits as $role => $limit) {
167 + $option_name = 'rate_limit_' . sanitize_key( $role );
168 + $limit_value = intval($limit);
169 + update_option($option_name, $limit_value);
170 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
171 + $timeframe_raw = isset( $_POST['rate_limit_timeframes'][ $role ] ) ? sanitize_text_field( wp_unslash( $_POST['rate_limit_timeframes'][ $role ] ) ) : '24';
172 + $timeframe = intval($timeframe_raw) * 3600; // Convert hours to seconds
173 + update_option('rate_limit_timeframe_' . $role, $timeframe);
174 + }
175 + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
176 + $guest_timeframe_raw = isset( $_POST['rate_limit_timeframes']['guest'] ) ? sanitize_text_field( wp_unslash( $_POST['rate_limit_timeframes']['guest'] ) ) : '24';
177 + $timeframe = intval($guest_timeframe_raw) * 3600; // Convert hours to seconds
178 + update_option('rate_limit_timeframe_guest', $timeframe);
179 + }
180 + // In your rate_limit_settings_option_callback(), after updating options:
181 + $this->schedule_rate_limit_reset_events();
182 + wp_send_json_success('Settings saved');
183 + wp_die();
184 + }
185 + public function qcld_user_role_rate_cron_schedule($schedules)
186 + {
187 + $roles = wp_roles()->roles;
188 + foreach ($roles as $role => $details) {
189 + $timeframe = intval(get_option('rate_limit_timeframe_' . $role, true));
190 + // Minimum 3600 seconds
191 + $schedules['session_schedules_rate_limit_' . $role] = array(
192 + 'interval' => $timeframe,
193 + 'display' => esc_html( 'session_schedules_rate_limit_' . $role ),
194 + );
195 + // Schedule the cron job for each role if not already scheduled);
196 + }
197 + return $schedules;
198 + }
199 + // wpshedule event to reset the rate limit used counts
200 + public function schedule_rate_limit_reset_events()
201 + {
202 + // Ensure WordPress cron functions are available
203 + if (! function_exists('wp_schedule_event')) {
204 + require_once ABSPATH . 'wp-includes/wp-cron.php';
205 + }
206 + $roles = wp_roles()->roles;
207 + foreach ($roles as $role => $d) {
208 + // Unschedule all existing events for this role
209 + $timestamp = wp_next_scheduled('reset_rate_limit_used_counts', array($role));
210 + while ($timestamp) {
211 + wp_unschedule_event($timestamp, 'reset_rate_limit_used_counts', array($role));
212 + $timestamp = wp_next_scheduled('reset_rate_limit_used_counts', array($role));
213 + }
214 + // Schedule new event with updated interval
215 + wp_schedule_event(time(), 'session_schedules_rate_limit_' . $role, 'reset_rate_limit_used_counts', array($role));
216 + }
217 + }
218 + public function qcld_wpsession_cron_deactivation()
219 + {
220 + $timestamp = wp_next_scheduled('reset_rate_limit_used_counts');
221 + wp_unschedule_event($timestamp, 'reset_rate_limit_used_counts');
222 + }
223 + public function rate_limit_checker()
224 + {
225 + $qlcd_wp_chatbot_ai_rate_limiting_message = qcld_wb_chatbot_func_str_replace(
226 + maybe_unserialize(get_option('qlcd_wp_chatbot_ai_rate_limiting_message', true))
227 + );
228 + if (get_option('is_stream_enabled') == 1) {
229 + $response = array(
230 + 'status' => 'error',
231 + 'message' => esc_html(
232 + ( is_array( $qlcd_wp_chatbot_ai_rate_limiting_message ) && ! empty( $qlcd_wp_chatbot_ai_rate_limiting_message[0] ) )
233 + ? $qlcd_wp_chatbot_ai_rate_limiting_message[0]
234 + : __( 'Rate limit exceeded. Please try again later.', 'chatbot' )
235 + ),
236 + );
237 + } else {
238 + $response = array(
239 + 'status' => 'success',
240 + 'message' => esc_html(
241 + ( is_array( $qlcd_wp_chatbot_ai_rate_limiting_message ) && ! empty( $qlcd_wp_chatbot_ai_rate_limiting_message[0] ) )
242 + ? $qlcd_wp_chatbot_ai_rate_limiting_message[0]
243 + : __( 'Rate limit exceeded. Please try again later.', 'chatbot' )
244 + ),
245 + );
246 + }
247 + if (is_user_logged_in()) {
248 + $user = wp_get_current_user();
249 + if (! empty($user->roles) && is_array($user->roles)) {
250 + $role = $user->roles[0];
251 + $option_name = 'rate_limit_' . $role;
252 + $limit = get_option($option_name);
253 + $rate_limit = get_user_meta(get_current_user_id(), 'qcld_openai_user_rate_limit', true);
254 + if (($limit >= $rate_limit) || $limit == 0) {
255 + return; // allowed
256 + } else {
257 + if (get_option('is_stream_enabled') == 1) {
258 + wp_send_json($response); //Proper JSON with header.
259 + } else {
260 + echo json_encode($response);
261 + wp_die();
262 + }
263 + }
264 + }
265 + } else {
266 + $option_name = 'rate_limit_guest';
267 + $limit = get_option($option_name);
268 +
269 + if (session_status() == PHP_SESSION_NONE) {
270 + session_start();
271 + }
272 +
273 + $guest_rate_limit = isset($_SESSION['guest_rate_limit']) ? intval($_SESSION['guest_rate_limit']) : 0;
274 +
275 + if ($limit >= $guest_rate_limit || $limit == 0) {
276 + return; // allowed
277 + } else {
278 + if (get_option('is_stream_enabled') == 1) {
279 + wp_send_json($response); //Proper JSON with header.
280 + } else {
281 + echo json_encode($response);
282 + // Return role if limit exceeded
283 + wp_die();
284 + }
285 + }
286 + }
287 + }
288 +
289 + public function reset_rate_limit_used_counts($role)
290 + {
291 + if (empty($role)) {
292 + return;
293 + }
294 + // Resetting rate limit used counts for role.
295 + // loop through each role
296 + // for each role get all users with that role
297 + $roles = wp_roles()->roles;
298 + foreach ($roles as $list_roll => $details) {
299 + if ($list_roll != $role) {
300 + continue;
301 + } else {
302 + $option_name = 'rate_limit_' . $role;
303 + $limit = get_option($option_name); // Default to if not set
304 + $users = get_users(array('role' => $role));
305 + foreach ($users as $user) {
306 + update_user_meta($user->ID, 'qcld_openai_user_rate_limit', 0);
307 + }
308 + }
309 + }
310 + }
311 + public function qcld_openai_user_rate_cal($update)
312 + {
313 + if (is_user_logged_in()) {
314 + $rate_limit = get_user_meta(get_current_user_id(), 'qcld_openai_user_rate_limit', true);
315 + if ($update != '') {
316 + $rate_limit = intval($rate_limit) + intval($update);
317 + }
318 + update_user_meta(get_current_user_id(), 'qcld_openai_user_rate_limit', $rate_limit);
319 + return;
320 + } else {
321 + // for guest users we will use session to store the rate limit
322 + // if session is not started then start it
323 + if (session_status() == PHP_SESSION_NONE) {
324 + session_start();
325 + }
326 + // we will use guest_id to identify the guest user
327 + if (! isset($_SESSION['guest_id'])) {
328 + $user_ip = isset( $_SERVER['REMOTE_ADDR'] ) ? sanitize_text_field( wp_unslash( $_SERVER['REMOTE_ADDR'] ) ) : '';
329 + $session_id = md5($user_ip . time());
330 + $_SESSION['guest_id'] = $session_id;
331 + $_SESSION['guest_id_time'][$session_id] = time();
332 + get_option('rate_', 10);
333 + // if guest_id_time is set and it is older than 24 hours then unset the guest_id
334 + // we will store the time when the guest_id was created in session
335 + $timeframe = get_option('rate_limit_timeframe_guest', true);
336 + $timeframe = intval($timeframe) * 3600; // convert hours to seconds
337 + if (isset($_SESSION['guest_id_time'][$session_id]) && (time() - $_SESSION['guest_id_time'][$session_id]) > $timeframe) {
338 + unset($_SESSION['guest_id']);
339 + unset($_SESSION['guest_id_time']);
340 + }
341 + // we will store the time when the guest_id was created in session
342 + $_SESSION['guest_id_time'][$session_id] = time();
343 + $_SESSION['guest_rate_limit'] = 0;
344 + }
345 + // update the rate limit
346 + // if update is not empty then add it to the existing rate limit else set it
347 + if ($update != '') {
348 + $guest_rate_limit = isset($_SESSION['guest_rate_limit']) ? intval($_SESSION['guest_rate_limit']) : 0;
349 + // add the update to the existing rate limit
350 + // set the rate limit in session
351 + $_SESSION['guest_rate_limit'] = $guest_rate_limit + $update;
352 + return;
353 + }
354 + }
355 + }
356 +
357 + public static function format_and_save_ai_form_response($msg) {
358 + if (empty($msg) || (strpos($msg, 'AI_FORM_DATA') === false)) {
359 + return $msg;
360 + }
361 +
362 + $pattern = '/(?:<[^>]+>)*\s*(?:__|<strong>|<b>)?AI_FORM_DATA(?:__|<\/strong>|<\/b>)?[\s\S]*?(?:__|<strong>|<b>)?AI_FORM_DATA_END(?:__|<\/strong>|<\/b>)?\s*(?:<\/[^>]+>)*/i';
363 +
364 + if (preg_match($pattern, $msg, $matches)) {
365 + $block = $matches[0];
366 + if (preg_match('/\{[\s\S]*\}/', $block, $json_matches)) {
367 + $json_str = trim($json_matches[0]);
368 + $data = json_decode($json_str, true);
369 +
370 + if ($data && isset($data['form_title'])) {
371 + $post_title = sanitize_text_field($data['form_title']) . ' - ' . current_time('mysql');
372 + $post_id = wp_insert_post(array(
373 + 'post_title' => $post_title,
374 + 'post_type' => 'wpbot_form_entry',
375 + 'post_status' => 'publish'
376 + ));
377 +
378 + if ($post_id && isset($data['data']) && is_array($data['data'])) {
379 + $email_body = "<h2>" . esc_html__('New AI Chat Submission', 'chatbot') . "</h2>";
380 + $email_body .= "<p><strong>" . esc_html__('Chat', 'chatbot') . ":</strong> " . sanitize_text_field($data['form_title']) . "</p>";
381 + $email_body .= "<table border='1' cellpadding='10' cellspacing='0' style='border-collapse: collapse; width: 100%; max-width: 600px; font-family: sans-serif;'>";
382 +
383 + // Extract user's email for Reply-To (not From, to avoid SMTP rejection)
384 + $reply_to = '';
385 + foreach ($data['data'] as $key => $value) {
386 + update_post_meta($post_id, sanitize_text_field($key), sanitize_text_field($value));
387 + $clean_key = ucwords(str_replace(array('-', '_'), ' ', sanitize_text_field($key)));
388 + $email_body .= "<tr><td style='background: #f4f4f4; width: 40%;'><strong>" . esc_html($clean_key) . "</strong></td><td>" . esc_html(sanitize_text_field($value)) . "</td></tr>";
389 +
390 + // Extract email from value — handles plain, [bracketed], and combined answers
391 + if (preg_match('/\[?([a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,})\]?/', $value, $email_match)) {
392 + $candidate = sanitize_email(trim($email_match[1]));
393 + if (is_email($candidate)) {
394 + $reply_to = $candidate;
395 + }
396 + }
397 + }
398 + $email_body .= "</table>";
399 +
400 + $email_enabled = true;
401 + $saved_forms = get_option('wpbot_ai_forms', array());
402 + if (is_array($saved_forms)) {
403 + foreach ($saved_forms as $form) {
404 + if ($form['title'] == sanitize_text_field($data['form_title'])) {
405 + if (isset($form['email'])) {
406 + $email_enabled = $form['email'] == 1;
407 + }
408 + $per_action_emails = isset($form['email_addresses']) ? trim($form['email_addresses']) : '';
409 + break;
410 + }
411 + }
412 + }
413 +
414 + if ($email_enabled) {
415 + if (!empty($per_action_emails)) {
416 + $to = array_map('trim', explode(',', $per_action_emails));
417 + } else {
418 + $default = get_option('qlcd_wp_chatbot_admin_email', '');
419 + $to = !empty($default) ? array_map('trim', explode(',', $default)) : get_option('admin_email');
420 + }
421 + $subject = sanitize_text_field($data['form_title']) . " - " . esc_html__('New AI Chat Submission', 'chatbot');
422 + $headers = array('Content-Type: text/html; charset=UTF-8');
423 + if (!empty($reply_to)) {
424 + $headers[] = 'Reply-To: ' . $reply_to;
425 + }
426 + wp_mail($to, $subject, $email_body, $headers);
427 + }
428 + }
429 +
430 + $summary_html = '<div class="ai-form-summary-card" style="background: #f4f6f9; border-left: 4px solid #0073aa; padding: 12px 14px; margin: 10px 0; border-radius: 4px; font-size: 13px; line-height: 1.5; color: #333;">';
431 + if (!empty($data['form_title'])) {
432 + $summary_html .= '<div style="font-weight: 600; color: #0073aa; margin-bottom: 8px; text-transform: capitalize; font-size: 14px;">' . esc_html($data['form_title']) . '</div>';
433 + }
434 + if (!empty($data['data']) && is_array($data['data'])) {
435 + $summary_html .= '<table style="width: 100%; border-collapse: collapse; margin-top: 4px;">';
436 + foreach ($data['data'] as $k => $v) {
437 + $clean_k = ucwords(str_replace(array('-', '_'), ' ', sanitize_text_field($k)));
438 + $summary_html .= '<tr><td style="padding: 3px 6px 3px 0; color: #555; font-weight: 600; width: 42%; vertical-align: top;">' . esc_html($clean_k) . ':</td><td style="padding: 3px 0; color: #222; vertical-align: top;">' . esc_html($v) . '</td></tr>';
439 + }
440 + $summary_html .= '</table>';
441 + }
442 + $summary_html .= '</div>';
443 +
444 + $msg = preg_replace($pattern, $summary_html, $msg);
445 + }
446 + }
447 + }
448 +
449 + return $msg;
450 + }
451 +
452 + public static function is_ai_action_in_progress($history = array(), $keyword = '') {
453 + if (!empty($_POST['action_prompt']) || !empty($_POST['is_ai_actions_playground'])) {
454 + return true;
455 + }
456 + $saved_ai_forms = get_option('wpbot_ai_forms', array());
457 + if (empty($saved_ai_forms) || !is_array($saved_ai_forms)) {
458 + return false;
459 + }
460 +
461 + $form_titles = array();
462 + foreach ($saved_ai_forms as $form) {
463 + if (!empty($form['title'])) {
464 + $form_titles[] = strtolower(trim($form['title']));
465 + }
466 + }
467 +
468 + if (empty($form_titles)) {
469 + return false;
470 + }
471 +
472 + $clean_kw = strtolower(trim($keyword));
473 + if (!empty($clean_kw)) {
474 + foreach ($form_titles as $title) {
475 + if ($clean_kw === $title || strpos($clean_kw, $title) !== false || strpos($title, $clean_kw) !== false) {
476 + return true;
477 + }
478 + }
479 + }
480 +
481 + if (empty($history)) {
482 + if (!empty($_POST['ai_history'])) {
483 + $history = json_decode(wp_unslash($_POST['ai_history']), true);
484 + } elseif (!empty($_POST['wpwHistory'])) {
485 + $history_html = wp_unslash($_POST['wpwHistory']);
486 + if (is_string($history_html)) {
487 + $dom = new \DOMDocument();
488 + libxml_use_internal_errors(true);
489 + $dom->loadHTML('<?xml encoding="utf-8" ?>' . $history_html);
490 + libxml_clear_errors();
491 + $xpath = new \DOMXPath($dom);
492 + $lis = $xpath->query('//li');
493 + $history = array();
494 + if ($lis && $lis->length > 0) {
495 + foreach ($lis as $li) {
496 + $class = $li->getAttribute('class');
497 + $text = trim($li->textContent);
498 + if ($text !== '') {
499 + $role = (strpos($class, 'chatbot-msg') !== false) ? 'assistant' : 'user';
500 + $history[] = array('role' => $role, 'content' => $text);
501 + }
502 + }
503 + }
504 + }
505 + }
506 + }
507 +
508 + if (!empty($history) && is_array($history)) {
509 + $last_form_start_idx = -1;
510 + $last_form_end_idx = -1;
511 + foreach ($history as $idx => $item) {
512 + $content = is_array($item) ? strtolower($item['content'] ?? '') : '';
513 + if (empty($content) && is_string($item)) {
514 + $content = strtolower($item);
515 + }
516 + foreach ($form_titles as $title) {
517 + if (!empty($title) && strpos($content, $title) !== false) {
518 + $last_form_start_idx = $idx;
519 + }
520 + }
521 + if (strpos($content, 'ai_form_data') !== false || strpos($content, 'ai-form-summary-card') !== false) {
522 + $last_form_end_idx = $idx;
523 + }
524 + }
525 +
526 + if ($last_form_start_idx !== -1 && $last_form_start_idx >= $last_form_end_idx) {
527 + return true;
528 + }
529 + }
530 +
531 + return false;
532 + }
533 +}
534 +
535 +/**
536 + * Instantiate the class
537 + */
538 +Qcld_WPBot_Common_Functions::instance();