PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.2.6
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.2.6
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
← All changes | includes/Shortcodes/SearchModal.php +5 -240 4.8.24.2.6 View file →
@@ -1,12 +1,8 @@
1 1 <?php
2 +
2 3 namespace WPDeveloper\BetterDocs\Shortcodes;
3 4
4 -if ( ! defined( 'ABSPATH' ) ) {
5 - exit;
6 -}
7 -
8 -
9 5 use WPDeveloper\BetterDocs\Core\Query;
10 6 use WPDeveloper\BetterDocs\Utils\Helper;
11 7 use WPDeveloper\BetterDocs\Core\Settings;
12 8 use WPDeveloper\BetterDocs\Core\Shortcode;
@@ -27,235 +23,8 @@
27 23 public function get_script_depends() {
28 24 return [ 'betterdocs-search-modal' ];
29 25 }
30 26
31 - /**
32 - * Modify search query to properly handle non-English characters
33 - *
34 - * @param string $search The search SQL for WHERE clause
35 - * @param WP_Query $query The WP_Query instance
36 - * @return string Modified search SQL
37 - */
38 - public function improve_search_for_non_english( $search, $query ) {
39 - global $wpdb;
40 -
41 - // Only modify our BetterDocs search queries
42 - if ( ! isset( $query->query_vars['post_type'] ) || $query->query_vars['post_type'] !== 'docs' ) {
43 - return $search;
44 - }
45 -
46 - // Only modify if there's a search term
47 - if ( empty( $query->query_vars['s'] ) ) {
48 - return $search;
49 - }
50 -
51 - $search_term = $query->query_vars['s'];
52 -
53 - // If the search term contains non-ASCII characters, we need to ensure proper UTF-8 handling
54 - if ( preg_match('/[^\x00-\x7F]/', $search_term) ) {
55 - // Get the search term with proper escaping
56 - $like = '%' . $wpdb->esc_like( $search_term ) . '%';
57 -
58 - // Build a UTF-8 compatible search query
59 - // Search in post_title, post_content, and post_excerpt
60 - // Note: Removed COLLATE clause to avoid collation mismatch with TranslatePress tables
61 - $search = $wpdb->prepare(
62 - " AND (
63 - ({$wpdb->posts}.post_title LIKE %s)
64 - OR ({$wpdb->posts}.post_content LIKE %s)
65 - OR ({$wpdb->posts}.post_excerpt LIKE %s)",
66 - $like,
67 - $like,
68 - $like
69 - );
70 -
71 - // If TranslatePress is active, also search in the translation dictionary
72 - if ( class_exists( '\TRP_Translate_Press' ) ) {
73 - // Get language codes
74 - $lang_codes = $this->get_trp_language_code();
75 - $default_lang = $lang_codes['default_language'];
76 - $current_lang = $lang_codes['current_language'];
77 -
78 - // Only search in translation table if current language is different from default
79 - if ( $default_lang !== $current_lang ) {
80 - $default_lang = preg_replace( '/[^a-z0-9_]/', '', $default_lang );
81 - $current_lang = preg_replace( '/[^a-z0-9_]/', '', $current_lang );
82 - // TranslatePress table naming: wp_trp_dictionary_{default_lang}_{current_lang}
83 - $trp_table = $wpdb->prefix . 'trp_dictionary_' . $default_lang . '_' . $current_lang;
84 -
85 - if ( $this->table_exists( $trp_table ) ) {
86 - // $trp_table is composed from $wpdb->prefix + sanitized lang slugs (preg_replace allowlist above);
87 - // $like is esc_like()-wrapped with intentional % wildcards; CONCAT() wildcards are query literals, not user input.
88 - // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
89 - $search .= $wpdb->prepare(
90 - " OR EXISTS (
91 - SELECT 1 FROM {$trp_table} trp
92 - WHERE (trp.original LIKE %s OR trp.translated LIKE %s)
93 - AND trp.status != 2
94 - AND (
95 - {$wpdb->posts}.post_title COLLATE utf8mb4_unicode_ci = trp.original COLLATE utf8mb4_unicode_ci
96 - OR {$wpdb->posts}.post_content COLLATE utf8mb4_unicode_ci LIKE CONCAT('%%', trp.original COLLATE utf8mb4_unicode_ci, '%%')
97 - OR {$wpdb->posts}.post_excerpt COLLATE utf8mb4_unicode_ci = trp.original COLLATE utf8mb4_unicode_ci
98 - )
99 - )",
100 - $like,
101 - $like
102 - );
103 - // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
104 - }
105 - }
106 - }
107 - $search .= " ) ";
108 - }
109 -
110 - return $search;
111 - }
112 -
113 - /**
114 - * Get TranslatePress language codes (default and current) for table name construction
115 - *
116 - * @return array Array with 'default_language' and 'current_language' keys
117 - */
118 - private function get_trp_language_code() {
119 - $result = [
120 - 'default_language' => 'en_US',
121 - 'current_language' => 'en_US'
122 - ];
123 -
124 - if ( class_exists( '\TRP_Translate_Press' ) ) {
125 - $trp = \TRP_Translate_Press::get_trp_instance();
126 - if ( isset( $trp ) && method_exists( $trp, 'get_component' ) ) {
127 - $trp_settings = $trp->get_component( 'settings' );
128 -
129 - // Get default language from settings
130 - if ( $trp_settings ) {
131 - $settings = $trp_settings->get_settings();
132 - if ( isset( $settings['default-language'] ) ) {
133 - $result['default_language'] = strtolower( $settings['default-language'] );
134 - }
135 - }
136 -
137 - // Get current language from global variable
138 - global $TRP_LANGUAGE;
139 - if ( isset( $TRP_LANGUAGE ) && ! empty( $TRP_LANGUAGE ) ) {
140 - $result['current_language'] = strtolower( $TRP_LANGUAGE );
141 - }
142 - }
143 - }
144 -
145 - return $result;
146 - }
147 -
148 - /**
149 - * Check if a database table exists
150 - */
151 - private function table_exists( $table_name ) {
152 - global $wpdb;
153 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- schema check, caching would mask plugin-activation state.
154 - $result = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) );
155 - return $result === $table_name;
156 - }
157 -
158 - public function get_search_results() {
159 - global $wpdb;
160 - // phpcs:disable WordPress.Security.NonceVerification.Missing -- public live-search endpoint, no state change.
161 - $search_input = isset( $_POST['search_input'] ) ? sanitize_text_field( wp_unslash( $_POST['search_input'] ) ) : '';
162 - $search_cat = isset( $_POST['search_cat'] ) ? wp_strip_all_tags( wp_unslash( $_POST['search_cat'] ) ) : '';
163 - $lang = isset( $_POST['lang'] ) ? wp_strip_all_tags( wp_unslash( $_POST['lang'] ) ) : '';
164 - // phpcs:enable WordPress.Security.NonceVerification.Missing
165 - // Removed preg_replace that was stripping non-Latin characters - sanitize_text_field() already handles security
166 -
167 - $tax_query = [];
168 - if ( $search_cat ) {
169 - $tax_query = [
170 - [
171 - 'taxonomy' => 'doc_category',
172 - 'field' => 'slug',
173 - 'terms' => $search_cat,
174 - 'operator' => 'AND',
175 - 'include_children' => true
176 - ]
177 - ];
178 - }
179 -
180 - $term = get_term_by( 'slug', $search_cat );
181 -
182 - $post_status = ['publish'];
183 -
184 - if( current_user_can( 'read_private_docs' ) ) {
185 - array_push($post_status, 'private');
186 - }
187 -
188 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- search query supports user-selected category filter.
189 - $args = [
190 - 'term_id' => isset( $term->term_id ) ? $term->term_id : 0,
191 - 'post_type' => 'docs',
192 - 'post_status' => $post_status,
193 - 'posts_per_page' => -1,
194 - 'suppress_filters' => false, // Changed to false to allow posts_search filter
195 - 's' => $search_input,
196 - 'orderby' => 'relevance',
197 - // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- category-scoped search is a core BetterDocs feature; the taxonomy filter is intrinsic to the query.
198 - 'tax_query' => $tax_query
199 - ];
200 -
201 - // Handle WPML multilingual search
202 - if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
203 - // If search term contains non-ASCII characters (e.g., Chinese, Japanese, Bangla),
204 - // search across all languages to find translated posts
205 - if ( preg_match('/[^\x00-\x7F]/', $search_input) ) {
206 - // Non-ASCII search: bypass WPML language filtering but allow posts_search filter
207 - // This allows searching across all languages
208 - $args['suppress_filters'] = true; // phpcs:ignore WordPressVIPMinimum.Hooks.PreGetPosts.PreGetPosts,WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- non-ASCII search must reach all WPML translations.
209 - } else {
210 - // ASCII-only search (English), use WPML filters to restrict to current language
211 - $args['suppress_filters'] = false;
212 - $args['lang'] = ICL_LANGUAGE_CODE;
213 - }
214 - }
215 - // Handle TranslatePress - always allow posts_search filter for non-ASCII
216 - elseif ( class_exists( '\TRP_Translate_Press' ) && preg_match('/[^\x00-\x7F]/', $search_input) ) {
217 - // For TranslatePress, we need posts_search filter to run
218 - $args['suppress_filters'] = false;
219 - }
220 -
221 - // Add filter to improve search for non-English characters
222 - add_filter( 'posts_search', [ $this, 'improve_search_for_non_english' ], 10, 2 );
223 -
224 - $search_results = $this->query->get_posts( $args );
225 -
226 - // Remove filter after query to avoid affecting other queries
227 - remove_filter( 'posts_search', [ $this, 'improve_search_for_non_english' ], 10 );
228 -
229 - $response = [];
230 -
231 - ob_start();
232 - betterdocs()->views->get(
233 - 'shortcode-parts/search-results',
234 - [
235 - 'search_results' => $search_results,
236 - 'search_input' => $search_input
237 - ]
238 - );
239 -
240 - $_output = ob_get_clean();
241 -
242 - $_input_not_found = '';
243 - if ( ! $search_results->have_posts() ) {
244 - $_input_not_found = $search_input;
245 - }
246 -
247 - $response['post_lists'] = $_output;
248 -
249 - if ( $_output && strlen( $search_input ) >= 3 ) {
250 - betterdocs()->query->insert_search_keyword( $search_input, $_input_not_found );
251 - }
252 -
253 - wp_reset_postdata();
254 -
255 - wp_send_json_success( $response );
256 - }
257 -
258 27 public function get_name() {
259 28 return 'betterdocs_search_modal';
260 29 }
261 30
@@ -280,10 +49,9 @@
280 49 'doc_ids' => '',
281 50 'doc_categories_ids' => '',
282 51 'enable_docs_search' => true,
283 52 'enable_faq_search' => true,
284 - 'enable_ai_powered_search' => false,
285 - 'kb_based_search' => '' // KB slug to filter search results
53 + 'enable_ai_powered_search' => false
286 54 ]
287 55 );
288 56 }
289 57
@@ -312,10 +80,9 @@
312 80 'doc_ids' => isset( $atts['doc_ids'] ) ? $atts['doc_ids'] : '',
313 81 'doc_categories_ids' => isset( $atts['doc_categories_ids'] ) ? $atts['doc_categories_ids'] : '',
314 82 'enable_faq_search' => isset( $atts['enable_faq_search'] ) ? $atts['enable_faq_search'] : $defaults_attrs['enable_faq_search'],
315 83 'enable_docs_search' => isset( $atts['enable_docs_search'] ) ? $atts['enable_docs_search'] : $defaults_attrs['enable_docs_search'],
316 - 'enable_ai_powered_search' => isset( $atts['enable_ai_powered_search'] ) ? $atts['enable_ai_powered_search'] : $defaults_attrs['enable_ai_powered_search'],
317 - 'kb_based_search' => isset( $atts['kb_based_search'] ) ? $atts['kb_based_search'] : $defaults_attrs['kb_based_search']
84 + 'enable_ai_powered_search' => isset( $atts['enable_ai_powered_search'] ) ? $atts['enable_ai_powered_search'] : $defaults_attrs['enable_ai_powered_search']
318 85 ];
319 86 $attributes = apply_filters( 'betterdocs_search_modal_shortcode_attributes', $attributes );
320 87 echo '<div class="betterdocs-search-modal-layout-1" id="betterdocs-search-modal"';
321 88 foreach ( $attributes as $key => $value ) {
@@ -334,10 +101,9 @@
334 101 'doc_ids' => isset( $atts['doc_ids'] ) ? $atts['doc_ids'] : '',
335 102 'doc_categories_ids' => isset( $atts['doc_categories_ids'] ) ? $atts['doc_categories_ids'] : '',
336 103 'enable_faq_search' => isset( $atts['enable_faq_search'] ) ? $atts['enable_faq_search'] : $defaults_attrs['enable_faq_search'],
337 104 'enable_docs_search' => isset( $atts['enable_docs_search'] ) ? $atts['enable_docs_search'] : $defaults_attrs['enable_docs_search'],
338 - 'enable_ai_powered_search' => isset( $atts['enable_ai_powered_search'] ) ? $atts['enable_ai_powered_search'] : $defaults_attrs['enable_ai_powered_search'],
339 - 'kb_based_search' => isset( $atts['kb_based_search'] ) ? $atts['kb_based_search'] : $defaults_attrs['kb_based_search']
105 + 'enable_ai_powered_search' => isset( $atts['enable_ai_powered_search'] ) ? $atts['enable_ai_powered_search'] : $defaults_attrs['enable_ai_powered_search']
340 106 ];
341 107 $attributes = apply_filters( 'betterdocs_search_modal_shortcode_attributes', $attributes );
342 108
343 109 echo '<div class="betterdocs-search-modal-archive" id="betterdocs-search-modal"';
@@ -356,10 +122,9 @@
356 122 'doc_ids' => isset( $atts['doc_ids'] ) ? $atts['doc_ids'] : '',
357 123 'doc_categories_ids' => isset( $atts['doc_categories_ids'] ) ? $atts['doc_categories_ids'] : '',
358 124 'enable_faq_search' => isset( $atts['enable_faq_search'] ) ? $atts['enable_faq_search'] : $defaults_attrs['enable_faq_search'],
359 125 'enable_docs_search' => isset( $atts['enable_docs_search'] ) ? $atts['enable_docs_search'] : $defaults_attrs['enable_docs_search'],
360 - 'enable_ai_powered_search' => isset( $atts['enable_ai_powered_search'] ) ? $atts['enable_ai_powered_search'] : $defaults_attrs['enable_ai_powered_search'],
361 - 'kb_based_search' => isset( $atts['kb_based_search'] ) ? $atts['kb_based_search'] : $defaults_attrs['kb_based_search']
126 + 'enable_ai_powered_search' => isset( $atts['enable_ai_powered_search'] ) ? $atts['enable_ai_powered_search'] : $defaults_attrs['enable_ai_powered_search']
362 127 ];
363 128 $attributes = apply_filters( 'betterdocs_search_modal_shortcode_attributes', $attributes );
364 129
365 130 echo '<div class="betterdocs-search-modal-sidebar" id="betterdocs-search-modal"';