PluginProbe
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services / 8.5.4
WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services v8.5.4
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
chatbot / qcld-wpwbot-search.php

qcld-wpwbot-search.php in WPBot – AI ChatBot for Live Support, Lead Generation, WordPress Automation, AI Services 8.5.4, at qcld-wpwbot-search.php

886 lines 36.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 if (!defined('ABSPATH')) exit; // Exit if accessed directly
3 /**
4 * Product indexing, caching & searching features concept is taken from open source 'Advanced wp Search' Wp plugin by ILLID.
5 */
6 //include_once( 'includes/class-wpwbot-cache.php' );
7
8 include_once( 'includes/class-wpwbot-table.php' );
9 include_once( 'includes/class-wpwbot-search.php' );
10
11 // Helper function to generate variations for each word in a keyword
12 if ( ! function_exists( '_wpbot_generate_word_variations' ) ) {
13 function _wpbot_generate_word_variations($keyword) {
14 $keyword = strtolower(trim($keyword));
15 $words = preg_split('/\s+/', $keyword, -1, PREG_SPLIT_NO_EMPTY);
16 $all_word_variations = [];
17
18 foreach ($words as $word) {
19 $variations = [$word];
20
21 // Simple pluralization/singularization and common suffix handling
22 if (strlen($word) > 1) { // Avoid stemming single letters
23 // Remove 's' (e.g., 'inspections' -> 'inspection')
24 if (substr($word, -1) === 's') {
25 $variations[] = substr($word, 0, -1);
26 } else { // Add 's' (e.g., 'inspection' -> 'inspections')
27 $variations[] = $word . 's';
28 }
29 // Handle 'er' suffix (e.g., 'inspector' -> 'inspect')
30 if (substr($word, -2) === 'er') {
31 $variations[] = substr($word, 0, -2);
32 }
33 // Handle 'ing' suffix (e.g., 'inspecting' -> 'inspect')
34 if (substr($word, -3) === 'ing') {
35 $variations[] = substr($word, 0, -3);
36 }
37 // Add 'er' if the base word is 'inspect' and 'inspector' is not present
38 if (strpos($word, 'inspect') !== false && strpos($word, 'inspector') === false) {
39 $variations[] = str_replace('inspect', 'inspector', $word);
40 }
41 }
42 $all_word_variations[] = array_filter(array_unique($variations));
43 }
44 return $all_word_variations;
45 }
46 }
47
48 // Filter function to modify WP_Query search for flexible matching
49 if ( ! function_exists( 'wpbot_flexible_search_filter' ) ) {
50 function wpbot_flexible_search_filter($search, $wp_query) {
51 global $wpdb, $wpbot_search_word_variations;
52
53 // Only apply if it's the main search query and our variations are set
54 if (empty($wpbot_search_word_variations) || !$wp_query->is_search || !$wp_query->is_main_query()) {
55 return $search;
56 }
57
58 $search_parts_for_and = [];
59
60 foreach ($wpbot_search_word_variations as $word_variations) {
61 $search_parts_for_or = [];
62 foreach ($word_variations as $term) {
63 $term = $wpdb->esc_like($term);
64 // Search in both post_title and post_content
65 $search_parts_for_or[] = "(({$wpdb->posts}.post_title LIKE '%{$term}%') OR ({$wpdb->posts}.post_content LIKE '%{$term}%'))";
66 }
67 if (!empty($search_parts_for_or)) {
68 $search_parts_for_and[] = '(' . implode(' OR ', $search_parts_for_or) . ')';
69 }
70 }
71
72 if (!empty($search_parts_for_and)) {
73 // Completely replace the default search clause generated by WP_Query's 's' parameter
74 // This ensures our flexible matching takes precedence.
75 $search = ' AND ' . implode(' AND ', $search_parts_for_and);
76 }
77
78 return $search;
79 }
80 }
81
82 function wpbo_search_site() {
83 // Verify nonce for security
84 $nonce = isset($_POST['security']) ? sanitize_text_field(wp_unslash($_POST['security'])) : (isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '');
85 if ( ! wp_verify_nonce( $nonce, 'wp_chatbot' ) && ! wp_verify_nonce( $nonce, 'qcsecretbotnonceval123qc' ) ) {
86 wp_send_json_error( array( 'status' => 'fail', 'message' => 'Security check failed.' ) );
87 wp_die();
88 }
89
90 global $wpdb;
91 // Limit results to 5 items.
92 $limit = 5;
93 $response = array('status' => 'fail', 'html' => ''); // Initialize response array
94
95 // Get default language for load more button text and other language-specific checks.
96 $default_language = get_locale();
97
98 if(get_option('enable_wp_chatbot_post_content') == 1){
99 $keyword = isset( $_POST['keyword'] ) ? sanitize_text_field(wp_unslash($_POST['keyword'])) : '';
100 $all_word_variations = _wpbot_generate_word_variations($keyword);
101
102 // Temporarily store the variations for the filter
103 global $wpbot_search_word_variations;
104 $wpbot_search_word_variations = $all_word_variations;
105
106 // Add the custom search filter
107 add_filter('posts_search', 'wpbot_flexible_search_filter', 10, 2);
108
109 // $enable_post_types = array( 'post', 'page'); // This line is commented out, so post_type is not restricted here.
110 $total_items = $limit;
111 $query_arg = array(
112 // 'post_type' => $enable_post_types,
113 'post_status' => 'publish',
114 'posts_per_page'=> $total_items,
115 's' => stripslashes( $keyword ), // Keep original for WP_Query to initiate search, filter will override
116 'paged' => 1,
117 'suppress_filters' => false // Crucial for filters to run
118 );
119 $resultss = new WP_Query( $query_arg );
120 $results = $resultss->posts;
121
122 // Remove the filter after the query to avoid affecting other queries
123 remove_filter('posts_search', 'wpbot_flexible_search_filter', 10);
124 unset($wpbot_search_word_variations); // Clean up global
125 }else{
126 $keyword = isset( $_POST['keyword'] ) ? sanitize_text_field(wp_unslash($_POST['keyword'])) : '';
127 $all_word_variations = _wpbot_generate_word_variations($keyword);
128
129 $sql_parts_for_and = [];
130 $sql_params = [];
131
132 foreach ($all_word_variations as $word_variations) {
133 $sql_parts_for_or = [];
134 foreach ($word_variations as $term) {
135 $sql_parts_for_or[] = "post_title LIKE %s";
136 $sql_params[] = '%' . $wpdb->esc_like($term) . '%';
137 }
138 if (!empty($sql_parts_for_or)) {
139 $sql_parts_for_and[] = '(' . implode(' OR ', $sql_parts_for_or) . ')';
140 }
141 }
142
143 $where_clause = '';
144 if (!empty($sql_parts_for_and)) {
145 $where_clause = ' AND ' . implode(' AND ', $sql_parts_for_and);
146 } else {
147 // Fallback to original behavior if no variations generated (e.g., empty keyword)
148 $where_clause = " AND (post_title LIKE %s)";
149 $sql_params[] = '%' . $wpdb->esc_like($keyword) . '%';
150 }
151
152 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
153 $results = $wpdb->get_results( $wpdb->prepare(
154 "SELECT * FROM " . $wpdb->prefix . "posts WHERE post_status = %s " . $where_clause . " ORDER BY ID DESC LIMIT %d",
155 array_merge(['publish'], $sql_params, [$limit])
156 ) );
157 }
158
159 if(!empty( $results )){
160
161 $response['status'] = 'success';
162 $response['html'] = '<div class="wpb-search-result">';
163 $total_post = 0;
164 $responses = '';
165
166 foreach ( $results as $result ) {
167 $featured_img_url = get_the_post_thumbnail_url( $result->ID, 'full' );
168 $excerpt = '';
169 if ( isset( $result->ID ) ) {
170 $post_obj = get_post( $result->ID );
171 if ( $post_obj ) {
172 if ( has_excerpt( $result->ID ) ) {
173 $excerpt = get_the_excerpt( $result->ID );
174 } else {
175 $content = $post_obj->post_content;
176
177 // Remove ALL WPBakery shortcodes (paired + self-closing)
178 $content = preg_replace( '/\[vc_[^\]]*\](.*?)\[\/vc_[^\]]*\]/s', '$1', $content ); // paired
179 $content = preg_replace( '/\[vc_[^\]]*\]/s', '', $content ); // self-closing
180 $content = preg_replace('/\[\/?[\w\-]+[^\]]*\]/', '', $content);
181 // Extra: remove any leftover [] shortcodes (just in case)
182 $content = strip_shortcodes( $content );
183
184 // Run through normal WP content filters
185 $content_filtered = apply_filters( 'the_content', $content );
186
187 // Strip HTML tags, then trim
188 $excerpt = wp_trim_words( wp_strip_all_tags( $content_filtered ), 20, '...' );
189 }
190
191
192 }
193 }
194
195 $total_post = $total_post + 1;
196 $responses .='<div class="wpbot_card_wraper">';
197 $responses .= '<div class="wpbot_card_image '.($result->post_type=='product'?'wp-chatbot-product':'').' '.( empty($featured_img_url) ?'wpbot_card_image_saas':'').'"><a href="'.esc_url(get_permalink($result->ID)).'" target="_blank" '.($result->post_type=='product'?'wp-chatbot-pid="'.$result->ID.'"':'').'>';
198 if( !empty($featured_img_url) ){
199 $responses .= '<img src="'.esc_url_raw($featured_img_url).'" />';
200 }
201 $responses .= '<div class="wpbot_card_caption '.( empty($featured_img_url) ?'wpbot_card_caption_saas':'').'">';
202 $responses .= '<p><span style="padding: 0 5px;color: #1d73b4;display: inline-block;margin: 0 5px 0 0;width: 18px;height: 18px;border-radius: 50%;font-size: 20px;line-height: 22px;"> ✓ </span> '.esc_html($result->post_title).'</p>';
203 $responses .= '<p>'.esc_html($excerpt).'</p>';
204 if($result->post_type=='product'){
205 if ( class_exists( 'WooCommerce' ) ) {
206 if ( $result->ID ) {
207 $product = wc_get_product( $result->ID );
208 $responses .= '<p class="wpbot_product_price">'.get_woocommerce_currency_symbol().$product->get_price_html().'</p>';
209 }
210 }
211 }
212 $responses .= '</div>';
213 $responses .= '</a></div>';
214 $responses .='</div>';
215
216 }
217 $response['html'] .= $responses;
218 $response['html'] .='</div>';
219 if($total_post >= $limit ){ // Use $limit for consistency
220 $load_more = maybe_unserialize(get_option('qlcd_wp_chatbot_load_more_search'));
221
222 $response['html'] .='<button type="button" class="wp-chatbot-loadmore" data-search-type="default-wp-search" data-keyword="'.esc_attr($keyword).'" data-page="2">'. ( !empty($load_more) && isset($load_more[$default_language]) ? $load_more[$default_language] : 'Load More').' <span id="wp-chatbot-loadmore-loader" class="wp-chatbot-loadmore-loader"></span></button>';
223
224 }
225 }else{
226 // Fuzzy search if initial search yields no results
227 $response['status'] = 'success';
228
229 // Use the same word variation logic for fuzzy search
230 $all_word_variations_for_fuzzy = _wpbot_generate_word_variations($keyword);
231
232 $unique_posts = array(); // Store unique post objects
233 $seen_ids = array(); // Keep track of seen post IDs
234
235 // Iterate through each word's variations to find matching posts
236 foreach ( $all_word_variations_for_fuzzy as $word_variations ) {
237 foreach ($word_variations as $term) {
238 $term = $wpdb->esc_like( $term );
239
240 $term_results = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
241 $wpdb->prepare("SELECT * FROM ". $wpdb->prefix."posts WHERE post_type IN (%s, %s) AND post_status = %s AND (post_title LIKE %s) ORDER BY ID DESC", 'page', 'post', 'publish', '%'. $term .'%')
242 );
243
244 foreach ($term_results as $res) {
245 if (!in_array($res->ID, $seen_ids)) {
246 $unique_posts[] = $res;
247 $seen_ids[] = $res->ID;
248 }
249 }
250 }
251 }
252 $results = $unique_posts; // Now $results contains unique WP_Post objects
253
254 if( !empty( $results) ){
255 $response['html'] = '<div class="wpb-search-result">';
256 $total_post = 0;
257 $responses = '';
258 $selected_lan = get_option('qlcd_wp_chatbot_default_language');
259
260 foreach ($results as $value) { // $value is a single post object here
261 if(!empty($value->guid)){
262 $post_id = $value->ID;
263 $current_featured_img_url = get_the_post_thumbnail_url( $post_id, 'full' );
264
265 // Corrected URL segment parsing for language check
266 $url_path = wp_parse_url(get_permalink($post_id), PHP_URL_PATH);
267 $url_segments = array_filter(explode('/',$url_path));
268
269 // Assuming the language slug is the first non-empty segment of the URL path.
270 $first_segment = !empty($url_segments) ? reset($url_segments) : '';
271
272 // If $selected_lan is empty, the language check is effectively skipped.
273 $language_match = empty($selected_lan) || ($first_segment == $selected_lan);
274
275 if($language_match){
276 $total_post = $total_post + 1;
277 $responses .='<div class="wpbot_card_wraper">';
278 $responses .= '<div class="wpbot_card_image '.(empty($current_featured_img_url)?'wpbot_card_image_saas':'').'"><a href="'.esc_url(get_permalink($post_id)).'" target="_blank">';
279 if(!empty($current_featured_img_url)){
280 $responses .= '<img src="'.esc_url_raw($current_featured_img_url).'" />';
281 }
282 $responses .= '<div class="wpbot_card_caption '.(empty($current_featured_img_url)?'wpbot_card_caption_saas':'').'">';
283 $responses .= '<p><span style="padding: 0 5px;color: #1d73b4;display: inline-block;margin: 0 5px 0 0;width: 18px;height: 18px;border-radius: 50%;font-size: 20px;line-height: 22px;"> ✓ </span>'.esc_html($value->post_title).'</p>';
284 $responses .= '</div>';
285 $responses .= '</a></div>';
286 $responses .='</div>';
287 }
288 }
289 }
290 if($total_post > 2 ){ // This condition is different from the first block ($total_post >= $limit)
291 $load_more = maybe_unserialize(get_option('qlcd_wp_chatbot_load_more_search'));
292 $response['html'] .='<button type="button" class="wp-chatbot-loadmore2" data-search-type="default-wp-search" data-keyword="'.esc_attr($keyword).'" data-page="2">'. ( !empty($load_more) && isset($load_more[$default_language]) ? $load_more[$default_language] : 'Load More').' <span id="wp-chatbot-loadmore-loader" class="wp-chatbot-loadmore-loader"></span></button>';
293 $response['status'] = 'success';
294 }else{
295 $response['status'] = 'fail';
296 }
297
298 $response['html'] .= $responses;
299 $response['html'] .='</div>';
300 } else {
301 $response['status'] = 'fail'; // No results from fuzzy search either
302 }
303 }
304 echo wp_json_encode($response);
305 wp_die();
306 }
307
308
309 add_action( 'wp_ajax_wpbo_search_site', 'wpbo_search_site' );
310 add_action( 'wp_ajax_nopriv_wpbo_search_site', 'wpbo_search_site' );
311
312 if ( ! function_exists( 'qcld_wpbot_modified_keyword' ) ) {
313 function qcld_wpbot_modified_keyword( $keyword ) {
314 $keyword = rtrim( $keyword, '!' );
315 $pattern = '/[?\/]/';
316 $strings = preg_split( $pattern, $keyword );
317 $strings = array_filter( array_map( 'trim', $strings ) );
318 $keyword = rtrim( $strings[0], '!' );
319 return htmlspecialchars_decode( $keyword );
320 }
321 }
322
323 add_action( 'wp_ajax_wpbo_search_responseby_intent', 'qcld_wpbo_search_responseby_intent' );
324 add_action( 'wp_ajax_nopriv_wpbo_search_responseby_intent', 'qcld_wpbo_search_responseby_intent' );
325
326 if( !function_exists( 'wpbo_search_site_pagination' )){
327
328 function wpbo_search_site_pagination() {
329 global $wpdb;
330
331 // Verify nonce for security
332 $p_nonce = isset($_POST['nonce']) ? sanitize_text_field(wp_unslash($_POST['nonce'])) : '';
333 if ( ! wp_verify_nonce( $p_nonce, 'wp_chatbot' ) && ! wp_verify_nonce( $p_nonce, 'qcsecretbotnonceval123qc' ) ) {
334 wp_send_json_error( array( 'message' => 'Security check failed' ) );
335 wp_die();
336 }
337
338 // Sanitize and validate inputs
339 $keyword = isset( $_POST['keyword'] ) ? sanitize_text_field(wp_unslash($_POST['keyword'])) : '';
340 $post_type = isset( $_POST['type'] ) ? sanitize_text_field(wp_unslash($_POST['type'])) : 'post';
341 $page = isset($_POST['page']) ? absint( wp_unslash($_POST['page']) ) : 0;
342
343 // Validate post type against allowed types
344 $allowed_post_types = array( 'post', 'page', 'product' );
345 if ( ! in_array( $post_type, $allowed_post_types, true ) ) {
346 $post_type = 'post';
347 }
348
349 $enable_post_types = get_option( 'wppt_post_types' );
350 $load_more = maybe_unserialize( get_option( 'qlcd_wp_chatbot_load_more' ) );
351
352 if ( is_array( $load_more ) && isset( $load_more[ get_locale() ] ) ) {
353 $load_more = $load_more[ get_locale() ];
354 }
355 if ( is_array( $load_more ) ) {
356 $load_more = $load_more[ array_rand( $load_more ) ];
357 }
358 $searchlimit = ( get_option( 'wppt_number_of_result' ) == '' ? 5 : absint( get_option( 'wppt_number_of_result' ) ) );
359 $orderby = ( get_option( 'wppt_result_orderby' ) == '' ? 'none' : get_option( 'wppt_result_orderby' ) );
360 $order = ( get_option( 'wppt_result_order' ) == '' ? 'ASC' : get_option( 'wppt_result_order' ) );
361 $thumb = ( get_option( 'wpbot_search_image_size' ) ? get_option( 'wpbot_search_image_size' ) : 'thumbnail' );
362 // order by setup
363 $new_window = get_option( 'wpbot_search_result_new_window' );
364
365 $total_items = absint( get_option( 'wppt_number_of_result' ) );
366 if ( $total_items < 1 ) {
367 $total_items = 5;
368 }
369
370 $searchkeyword = qcld_wpbot_modified_keyword( $keyword );
371
372 $response = array();
373 $response['status'] = 'fail';
374 $response['html'] = '';
375
376 // Use prepared statements to prevent SQL injection
377 if ( get_option( 'active_advance_query' ) != '1' ) {
378 // Simple query - search in post_title only
379 $total_results = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
380 $wpdb->prepare(
381 "SELECT * FROM {$wpdb->prefix}posts
382 WHERE post_type = %s
383 AND post_status = 'publish'
384 AND post_title LIKE %s
385 ORDER BY ID DESC",
386 $post_type,
387 '%' . $wpdb->esc_like( $searchkeyword ) . '%'
388 ));
389 } else {
390 // Advanced query - search in both post_title and post_content
391 $total_results = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
392 $wpdb->prepare(
393 "SELECT * FROM " . $wpdb->prefix . "posts
394 WHERE post_type = %s
395 AND post_status = %s
396 AND (post_title REGEXP %s OR post_content REGEXP %s)
397 ORDER BY ID DESC",
398 $post_type,
399 'publish',
400 '[[:<:]]' . $searchkeyword . '[[:>:]]',
401 '[[:<:]]' . $searchkeyword . '[[:>:]]'
402 ));
403 }
404
405 if ( ! empty( $total_results ) ) {
406
407 // Validate and sanitize orderby parameter
408 $valid_orderby = array( 'title', 'date', 'modified', 'none', 'rand' );
409 if ( ! in_array( $orderby, $valid_orderby, true ) ) {
410 $orderby = 'none';
411 }
412
413 if ( $orderby == 'title' ) {
414 $orderby = 'post_title';
415 }
416 if ( $orderby == 'date' ) {
417 $orderby = 'post_date';
418 }
419 if ( $orderby == 'modified' ) {
420 $orderby = 'post_modified';
421 }
422
423 // Validate order parameter
424 $order = strtoupper( $order );
425 if ( ! in_array( $order, array( 'ASC', 'DESC' ), true ) ) {
426 $order = 'ASC';
427 }
428
429 // Build query with pagination
430 $offset = absint( $total_items * $page );
431
432 if ( get_option( 'active_advance_query' ) != '1' ) {
433 if ( $orderby != 'none' && $orderby != 'rand' ) {
434 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
435 $results = $wpdb->get_results( $wpdb->prepare(
436 "SELECT * FROM {$wpdb->prefix}posts
437 WHERE post_type = %s
438 AND post_status = 'publish'
439 AND post_title LIKE %s
440 ORDER BY {$orderby} {$order}
441 LIMIT %d, %d",
442 $post_type,
443 '%' . $wpdb->esc_like( $searchkeyword ) . '%',
444 $offset,
445 $total_items
446 ) );
447 } else {
448 $results = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
449 $wpdb->prepare(
450 "SELECT * FROM {$wpdb->prefix}posts
451 WHERE post_type = %s
452 AND post_status = 'publish'
453 AND post_title LIKE %s
454 ORDER BY ID DESC
455 LIMIT %d, %d",
456 $post_type,
457 '%' . $wpdb->esc_like( $searchkeyword ) . '%',
458 $offset,
459 $total_items
460 ));
461 }
462 } else {
463 if ( $orderby != 'none' && $orderby != 'rand' ) {
464 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
465 $results = $wpdb->get_results( $wpdb->prepare(
466 "SELECT * FROM " . $wpdb->prefix . "posts
467 WHERE post_type = %s
468 AND post_status = %s
469 AND (post_title REGEXP %s OR post_content REGEXP %s)
470 ORDER BY " . $orderby . " " . $order . "
471 LIMIT %d, %d",
472 $post_type,
473 'publish',
474 '[[:<:]]' . $searchkeyword . '[[:>:]]',
475 '[[:<:]]' . $searchkeyword . '[[:>:]]',
476 $offset,
477 $total_items
478 ) );
479 } else {
480 $results = $wpdb->get_results( // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
481 $wpdb->prepare(
482 "SELECT * FROM " . $wpdb->prefix . "posts
483 WHERE post_type = %s
484 AND post_status = %s
485 AND (post_title REGEXP %s OR post_content REGEXP %s)
486 ORDER BY ID DESC
487 LIMIT %d, %d",
488 $post_type,
489 'publish',
490 '[[:<:]]' . $searchkeyword . '[[:>:]]',
491 '[[:<:]]' . $searchkeyword . '[[:>:]]',
492 $offset,
493 $total_items
494 ));
495 }
496 }
497 } else {
498 if ( class_exists( 'SitePress' ) ) {
499 global $sitepress;
500 $selected_lan = isset( $_POST['language'] ) ? sanitize_text_field(wp_unslash($_POST['language'])) : '';
501 $selected_lan = explode( '_', $selected_lan );
502 if ( ! empty( $selected_lan[0] ) ) {
503 $sitepress->switch_lang( $selected_lan[0], true );
504 }
505 }
506
507 $query_arg = array(
508 'post_type' => $post_type,
509 'post_status' => 'publish',
510 'posts_per_page' => $total_items,
511 's' => stripslashes( $keyword ),
512 'paged' => ( $page + 1 ),
513 'orderby' => $orderby,
514 );
515
516 if ( class_exists( 'SitePress' ) ) {
517 global $sitepress;
518 $selected_lan = isset( $_POST['language'] ) ? sanitize_text_field(wp_unslash($_POST['language'])) : '';
519 $selected_lan = explode( '_', $selected_lan );
520 if ( ! empty( $selected_lan[0] ) ) {
521 $sitepress->switch_lang( $selected_lan[0], true );
522 }
523 }
524
525 $query_arg['suppress_filters'] = true;
526 if ( $orderby != 'none' && $orderby != 'rand' ) {
527 $query_arg['order'] = $order;
528 }
529
530 $totalresults = new WP_Query(
531 array(
532 'post_type' => $post_type,
533 'post_status' => 'publish',
534 's' => stripslashes( $keyword ),
535 )
536 );
537 $resultss = new WP_Query( $query_arg );
538 $total_results = $totalresults->posts;
539 $resultss = new WP_Query( $query_arg );
540 $results = $resultss->posts;
541 }
542
543 if ( ! empty( $total_results ) ) {
544
545 $selected_lan = isset( $_POST['language'] ) ? sanitize_text_field(wp_unslash($_POST['language'])) : '';
546 $urlss = get_option( 'wpbotml_url_urls' ) ? get_option( 'wpbotml_url_urls' ) : '';
547 $imagesize = ( get_option( 'wpbot_search_image_size' ) != '' ? get_option( 'wpbot_search_image_size' ) : 'thumbnail' );
548
549 $response['html'] .= '<div class="wpb-search-result">';
550
551 foreach ( $total_results as $result ) {
552
553 if ( $result->post_type == 'product' ) {
554 if ( ! class_exists( 'WooCommerce' ) ) {
555 continue;
556 }
557 }
558
559 $featured_img_url = get_the_post_thumbnail_url( $result->ID, $thumb );
560 $excerpt = '';
561 if ( isset( $result->ID ) ) {
562 $post_obj = get_post( $result->ID );
563 if ( $post_obj ) {
564 if ( has_excerpt( $result->ID ) ) {
565 $excerpt = get_the_excerpt( $result->ID );
566 } else {
567 $content = $post_obj->post_content;
568
569 // Remove ALL WPBakery shortcodes (paired + self-closing)
570 $content = preg_replace( '/\[vc_[^\]]*\](.*?)\[\/vc_[^\]]*\]/s', '$1', $content ); // paired
571 $content = preg_replace( '/\[vc_[^\]]*\]/s', '', $content ); // self-closing
572 $content = preg_replace('/\[\/?[\w\-]+[^\]]*\]/', '', $content);
573 // Extra: remove any leftover [] shortcodes (just in case)
574 $content = strip_shortcodes( $content );
575
576 // Run through normal WP content filters
577 $content_filtered = apply_filters( 'the_content', $content );
578
579 // Strip HTML tags, then trim
580 $excerpt = wp_trim_words( wp_strip_all_tags( $content_filtered ), 20, '...' );
581 }
582 }
583 }
584
585
586 $response['html'] .= '<div class="wpbot_card_wraper">';
587 $response['html'] .= '<div class="wpbot_card_image ' . ( $result->post_type == 'product' ? 'wp-chatbot-product' : '' ) . ' ' . ( $featured_img_url == '' ? 'wpbot_card_image_saas' : '' ) . '"><a href="' . esc_url( get_permalink( $result->ID ) ) . '" ' . ( $new_window == 1 ? 'target="_blank"' : '' ) . ' ' . ( $result->post_type == 'product' ? 'wp-chatbot-pid="' . absint( $result->ID ) . '"' : '' ) . '>';
588 if ( $featured_img_url != '' ) {
589 $response['html'] .= '<img src="' . esc_url_raw( $featured_img_url ) . '" />';
590 }
591
592 $response['html'] .= '<div class="wpbot_card_caption ' . ( $featured_img_url == '' ? 'wpbot_card_caption_saas' : '' ) . '">';
593 $response['html'] .= '<p class="wpbot_card_caption_title"><span style="padding: 0 5px;color: #1d73b4;display: inline-block;margin: 0 5px 0 0;width: 18px;height: 18px;border-radius: 50%;font-size: 20px;line-height: 22px;"> ✓ </span> ' . esc_html( $result->post_title ) . '</p>';
594 $response['html'] .= '<p class="wpbot_card_description">' . esc_html( $excerpt ) . '</p>';
595 if ( $result->post_type == 'product' ) {
596 if ( class_exists( 'WooCommerce' ) ) {
597 $product = wc_get_product( $result->ID );
598 $response['html'] .= '<p class="wpbot_product_price">' . get_woocommerce_currency_symbol() . $product->get_price_html() . '</p>';
599 }
600 }
601 $response['html'] .= '</div>';
602 $response['html'] .= '</a></div>';
603 $response['html'] .= '</div>';
604
605 }
606
607
608 $response['html'] .= '</div>';
609 $response['status'] = 'success';
610
611 }
612 wp_reset_query();
613
614 if ( $response['status'] != 'success' ) {
615 $texts = maybe_unserialize( get_option( 'qlcd_wp_chatbot_no_result' ) );
616 $selected_lan = isset( $_POST['language'] ) ? sanitize_text_field(wp_unslash($_POST['language'])) : '';
617 if ( ! empty( $texts ) && is_array( $texts ) && isset( $texts[ $selected_lan ][0] ) ) {
618 $texts = str_replace( "\'", "'", $texts[ $selected_lan ][0] );
619 $response['html'] = array( $texts );
620 } else {
621 $response['html'] = array( 'No results found' );
622 }
623 }
624 wp_send_json( $response );
625 die();
626 }
627
628 }
629
630
631
632 add_action( 'wp_ajax_wpbo_search_site_pagination', 'wpbo_search_site_pagination' );
633 add_action( 'wp_ajax_nopriv_wpbo_search_site_pagination', 'wpbo_search_site_pagination' );
634 function qcld_wpbo_search_responseby_intent(){
635
636 global $wpdb;
637
638 $keyword = isset( $_POST['keyword'] ) ? sanitize_text_field(wp_unslash($_POST['keyword'])) : '';
639
640 $table = $wpdb->prefix.'wpbot_response';
641
642 $result = $wpdb->get_row( $wpdb->prepare("SELECT `response` FROM %i WHERE 1 and `intent` = %s", $table, $keyword) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
643
644 $response = array('status'=>'fail');
645
646 if(!empty($result)){
647
648 $response['status'] = 'success';
649 $response['html'] = $result->response;
650
651 }
652
653 echo wp_json_encode($response);
654
655 die();
656
657 }
658
659 add_action( 'wp_ajax_wpbo_search_response_catlist', 'wpbo_search_response_catlist' );
660 add_action( 'wp_ajax_nopriv_wpbo_search_response_catlist', 'wpbo_search_response_catlist' );
661
662 if( !function_exists( 'wpbo_search_response_catlist' )){
663 function wpbo_search_response_catlist(){
664 global $wpdb;
665 $table = $wpdb->prefix.'wpbot_response_category';
666 $status = array('status'=>'fail');
667 $results = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i", $table)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
668 $response_result = array();
669
670 if(!empty($results)){
671 foreach($results as $result){
672
673 $response_result[] = array('name'=>$result->name);
674
675 }
676 }
677
678 if(!empty($response_result)){
679
680 $status = array('status'=>'success', 'data'=>$response_result);
681
682
683 }
684
685 echo wp_json_encode($status);
686
687 die();
688
689 }
690 }
691 add_action( 'wp_ajax_wpbo_search_response', 'qcld_wpbo_search_response' );
692 add_action( 'wp_ajax_nopriv_wpbo_search_response', 'qcld_wpbo_search_response' );
693
694
695
696 function qcld_wpbo_search_response(){
697
698 global $wpdb;
699 $keyword = isset( $_POST['keyword'] ) ? (sanitize_text_field(wp_unslash($_POST['keyword']))) : '';
700 $strid = isset( $_POST['strid'] ) ? (sanitize_text_field(wp_unslash($_POST['strid']))) : '';
701 $table = $wpdb->prefix.'wpbot_response';
702
703
704 $response_result = array();
705
706 $status = array('status'=>'fail', 'multiple'=>false);
707 $field = "ID";
708 if(($strid != '') && empty($response_result)){
709 $results = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i WHERE %i = %d",$table,$field,$strid)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
710 if(!empty($results)){
711 foreach($results as $result){
712
713 $response_result[] = array('id'=>$result->id,'query'=>$result->query, 'response'=>$result->response, 'score'=>1);
714
715 }
716 }
717 }
718 $field = "query";
719 $results = $wpdb->get_results( $wpdb->prepare("SELECT `id`, `query`, `response` FROM %i WHERE 1 and %i = %s", $table, $field,$keyword) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
720
721
722 if(!empty($results)){
723 foreach($results as $result){
724
725 $response_result[] = array('id'=>$result->id,'query'=>$result->query, 'response'=>$result->response, 'score'=>1);
726
727 }
728 }
729
730 $field = "category";
731 if(empty($response_result)){
732 $results = $wpdb->get_results( $wpdb->prepare("SELECT `id`, `query`, `response` FROM %i WHERE 1 and %i = %s", $table,$field, $keyword) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
733
734
735 if(!empty($results)){
736 foreach($results as $result){
737 $response_result[] = array('id'=>$result->id,'query'=>$result->query, 'response'=>$result->response, 'score'=>1);
738 }
739 if(count($response_result)>1){
740 $status = array('status'=>'success','category'=> true, 'multiple'=>true, 'data'=>$response_result);
741 }else{
742 $status = array('status'=>'success', 'category'=> true, 'multiple'=>false, 'data'=>$response_result);
743 }
744
745 echo wp_json_encode($status);
746
747 die();
748 }
749
750 }
751
752 if(class_exists('Qcld_str_pro')){
753 if(get_option('qc_bot_str_remove_stopwords') && get_option('qc_bot_str_remove_stopwords')==1){
754 $keyword = qcld_strpro_remove_stopwords($keyword);
755 }
756 }
757
758
759 if(empty($response_result)){
760
761 $fields = get_option('qc_bot_str_fields');
762
763 $allowed_fields = array('query', 'keyword', 'response');
764 $valid_fields = array();
765
766 if($fields && !empty($fields) && is_array($fields)){
767 foreach($fields as $field){
768 if(in_array($field, $allowed_fields)){
769 $valid_fields[] = '`' . $field . '`';
770 }
771 }
772 }
773
774 if(!empty($valid_fields)){
775 $qfields = implode(', ', $valid_fields);
776 }else{
777 $qfields = '`query`,`keyword`,`response`';
778 }
779
780
781
782 // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.InterpolatedNotPrepared, PluginCheck.Security.DirectDB.UnescapedDBParameter
783 $results = $wpdb->get_results( $wpdb->prepare("SELECT `id`, `query`, `response`, MATCH($qfields) AGAINST(%s IN NATURAL LANGUAGE MODE) as score FROM %i WHERE MATCH($qfields) AGAINST(%s IN NATURAL LANGUAGE MODE) order by score desc limit 15",$keyword,$table,$keyword) );
784
785 $weight = get_option('qc_bot_str_weight')!=''?get_option('qc_bot_str_weight'):'0.4';
786
787 if(!empty($results)){
788 $max_score = max(array_column($results, 'score'));
789 if ($max_score <= 0) {
790 $max_score = 1; // Set to 1 to avoid division by zero
791 }
792 foreach($results as $result){
793 if(($result->score/$max_score) >= $weight){
794 $response_result[] = array('id'=>$result->id,'query'=>$result->query, 'response'=>$result->response, 'score'=>$result->score);
795 }
796 }
797 }
798 }
799 $field = "keyword";
800 if( empty( $response_result ) ){
801
802 $results = $wpdb->get_results($wpdb->prepare("SELECT * FROM %i WHERE %i REGEXP %s", $table,$field,$keyword)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
803
804
805 if(!empty($results)){
806 foreach($results as $result){
807 $response_result[] = array('id'=>$result->id,'query'=>$result->query, 'response'=>$result->response, 'score'=>1);
808 }
809 }
810 }
811 if(!empty($response_result)){
812
813 if(count($response_result)>1){
814 $status = array('status'=>'success', 'multiple'=>true, 'data'=>$response_result);
815 }else{
816 $status = array('status'=>'success', 'multiple'=>false, 'data'=>$response_result);
817 }
818
819 }
820 if(empty($result->query)){
821 $status = array('status'=>'fail', 'multiple'=>false, 'data'=>$response_result);
822 }
823 if(empty($status['data']) || (isset($status['status']) && $status['status']==='fail')){
824 // Check for space before question mark and try again.
825 if(preg_match('/ \?$/', $keyword)){
826 $keyword2 = preg_replace('/ \?$/', '?', $keyword);
827 // Try again with new keyword.
828 // Repeat the main search logic with $keyword2.
829 $response_result = array();
830 $field = "query";
831 $results = $wpdb->get_results( $wpdb->prepare("SELECT `id`, `query`, `response` FROM %i WHERE 1 and %i = %s", $table, $field, $keyword2) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
832 if(!empty($results)){
833 foreach($results as $result){
834 $response_result[] = array('id'=>$result->id,'query'=>$result->query, 'response'=>$result->response, 'score'=>1);
835 }
836 if(count($response_result)>1){
837 $status = array('status'=>'success', 'multiple'=>true, 'data'=>$response_result);
838 }else{
839 $status = array('status'=>'success', 'multiple'=>false, 'data'=>$response_result);
840 }
841 }else{
842 $status = array('status'=>'fail', 'multiple'=>false, 'data'=>[]);
843 }
844 }
845 }
846 if(empty($status['data']) || (isset($status['status']) && $status['status']==='fail')){
847 // Try a partial match if still nothing found.
848 if(empty($status['data'])) {
849 $keyword_like = '%' . preg_replace('/[\\s\\?]+/', '%', $keyword) . '%';
850 $results = $wpdb->get_results( $wpdb->prepare("SELECT `id`, `query`, `response` FROM %i WHERE `query` LIKE %s", $table, $keyword_like) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
851 $response_result = array();
852 if(!empty($results)){
853 foreach($results as $result){
854 $response_result[] = array('id'=>$result->id,'query'=>$result->query, 'response'=>$result->response, 'score'=>1);
855 }
856 if(count($response_result)>1){
857 $status = array('status'=>'success', 'multiple'=>true, 'data'=>$response_result);
858 }else{
859 $status = array('status'=>'success', 'multiple'=>false, 'data'=>$response_result);
860 }
861 } else {
862 $status = array('status'=>'fail', 'multiple'=>false, 'data'=>[], 'message'=>'Sorry, I found nothing');
863 }
864 }
865 }
866 if(empty($status['data'])){
867 $status = array('status'=>'fail', 'multiple'=>false, 'data'=>[], 'message'=>'no result found');
868 }
869 echo wp_json_encode($status);
870
871 die();
872
873 }
874
875 function qcld_strpro_remove_stopwords($keyword){
876
877 if(get_option('qlcd_wp_chatbot_stop_words') && get_option('qlcd_wp_chatbot_stop_words')!=''){
878 $commonWords = explode(',', get_option('qlcd_wp_chatbot_stop_words'));
879 return preg_replace('/\b('.implode('|',$commonWords).')\b/','',$keyword);
880 }else{
881 return $keyword;
882 }
883
884
885
886 }