PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.5.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.5.2
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
betterdocs / includes / Shortcodes / SearchForm.php

SearchForm.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.5.2, at includes/Shortcodes/SearchForm.php

295 lines 9.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\Shortcodes;
4
5 use WPDeveloper\BetterDocs\Core\Query;
6 use WPDeveloper\BetterDocs\Utils\Helper;
7 use WPDeveloper\BetterDocs\Core\Settings;
8 use WPDeveloper\BetterDocs\Core\Shortcode;
9 use WPDeveloper\BetterDocs\Admin\Customizer\Defaults;
10
11 class SearchForm extends Shortcode {
12 public function __construct( Settings $settings, Query $query, Helper $helper, Defaults $defaults ) {
13 parent::__construct( $settings, $query, $helper, $defaults );
14
15 add_action( 'wp_ajax_nopriv_betterdocs_get_search_result', [ $this, 'get_search_results' ] );
16 add_action( 'wp_ajax_betterdocs_get_search_result', [ $this, 'get_search_results' ] );
17 }
18
19 /**
20 * Modify search query to properly handle non-English characters
21 *
22 * @param string $search The search SQL for WHERE clause
23 * @param WP_Query $query The WP_Query instance
24 * @return string Modified search SQL
25 */
26 public function improve_search_for_non_english( $search, $query ) {
27 global $wpdb;
28
29 // Only modify our BetterDocs search queries
30 if ( ! isset( $query->query_vars['post_type'] ) || $query->query_vars['post_type'] !== 'docs' ) {
31 return $search;
32 }
33
34 // Only modify if there's a search term
35 if ( empty( $query->query_vars['s'] ) ) {
36 return $search;
37 }
38
39 $search_term = $query->query_vars['s'];
40
41 // If the search term contains non-ASCII characters, we need to ensure proper UTF-8 handling
42 if ( preg_match('/[^\x00-\x7F]/', $search_term) ) {
43 // Get the search term with proper escaping
44 $like = '%' . $wpdb->esc_like( $search_term ) . '%';
45
46 // Build a UTF-8 compatible search query
47 // Search in post_title, post_content, and post_excerpt
48 // Note: Removed COLLATE clause to avoid collation mismatch with TranslatePress tables
49 $search = $wpdb->prepare(
50 " AND (
51 ({$wpdb->posts}.post_title LIKE %s)
52 OR ({$wpdb->posts}.post_content LIKE %s)
53 OR ({$wpdb->posts}.post_excerpt LIKE %s)",
54 $like,
55 $like,
56 $like
57 );
58
59 // If TranslatePress is active, also search in the translation dictionary
60 if ( class_exists( '\TRP_Translate_Press' ) ) {
61 // Get language codes
62 $lang_codes = $this->get_trp_language_code();
63 $default_lang = $lang_codes['default_language'];
64 $current_lang = $lang_codes['current_language'];
65
66 // Only search in translation table if current language is different from default
67 if ( $default_lang !== $current_lang ) {
68 // TranslatePress table naming: wp_trp_dictionary_{default_lang}_{current_lang}
69 $trp_table = $wpdb->prefix . 'trp_dictionary_' . $default_lang . '_' . $current_lang;
70
71 if ( $this->table_exists( $trp_table ) ) {
72
73 $trp_search = $wpdb->prepare(
74 " OR EXISTS (
75 SELECT 1 FROM {$trp_table} trp
76 WHERE (trp.original LIKE %s OR trp.translated LIKE %s)
77 AND trp.status != 2
78 AND (
79 {$wpdb->posts}.post_title COLLATE utf8mb4_unicode_ci = trp.original COLLATE utf8mb4_unicode_ci
80 OR {$wpdb->posts}.post_content COLLATE utf8mb4_unicode_ci LIKE CONCAT('%%', trp.original COLLATE utf8mb4_unicode_ci, '%%')
81 OR {$wpdb->posts}.post_excerpt COLLATE utf8mb4_unicode_ci = trp.original COLLATE utf8mb4_unicode_ci
82 )
83 )",
84 $like,
85 $like
86 );
87
88 $search .= $trp_search;
89 }
90 }
91 }
92
93
94 $search .= " ) ";
95 }
96
97 return $search;
98 }
99
100 /**
101 * Get TranslatePress language codes (default and current) for table name construction
102 *
103 * @return array Array with 'default_language' and 'current_language' keys
104 */
105 private function get_trp_language_code() {
106 $result = [
107 'default_language' => 'en_US',
108 'current_language' => 'en_US'
109 ];
110
111 if ( class_exists( '\TRP_Translate_Press' ) ) {
112 $trp = \TRP_Translate_Press::get_trp_instance();
113 if ( isset( $trp ) && method_exists( $trp, 'get_component' ) ) {
114 $trp_settings = $trp->get_component( 'settings' );
115
116 // Get default language from settings
117 if ( $trp_settings ) {
118 $settings = $trp_settings->get_settings();
119 if ( isset( $settings['default-language'] ) ) {
120 $result['default_language'] = strtolower( $settings['default-language'] );
121 }
122 }
123
124 // Get current language from global variable
125 global $TRP_LANGUAGE;
126 if ( isset( $TRP_LANGUAGE ) && ! empty( $TRP_LANGUAGE ) ) {
127 $result['current_language'] = strtolower( $TRP_LANGUAGE );
128 }
129 }
130 }
131
132 return $result;
133 }
134
135 /**
136 * Check if a database table exists
137 */
138 private function table_exists( $table_name ) {
139 global $wpdb;
140 $result = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) );
141 return $result === $table_name;
142 }
143
144 public function get_style_depends() {
145 $handlers = [ 'betterdocs-search' ];
146 return $handlers;
147 }
148
149 public function get_script_depends() {
150 $handlers = [ 'betterdocs-search'];
151
152 if ( is_tax() ) {
153 $handlers[] = 'betterdocs-glossaries';
154 }
155 return $handlers;
156 }
157
158 public function get_search_results() {
159 global $wpdb;
160 $search_input = isset( $_POST['search_input'] ) ? sanitize_text_field( $_POST['search_input'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
161 $search_cat = isset( $_POST['search_cat'] ) ? wp_strip_all_tags( $_POST['search_cat'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
162 $lang = isset( $_POST['lang'] ) ? wp_strip_all_tags( $_POST['lang'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
163 $kb_slug = isset( $_POST['kb_slug'] ) ? sanitize_text_field( $_POST['kb_slug'] ) : ''; // phpcs:ignore WordPress.Security.NonceVerification.Missing
164
165 $tax_query = [];
166 if ( $search_cat ) {
167 $tax_query = [
168 [
169 'taxonomy' => 'doc_category',
170 'field' => 'slug',
171 'terms' => $search_cat,
172 'operator' => 'AND',
173 'include_children' => true
174 ]
175 ];
176 }
177
178 // Don't build KB tax_query here - let the MultipleKB filter handle it
179 // We just pass kb_slug in the args
180
181 $term = get_term_by( 'slug', $search_cat );
182
183 $post_status = ['publish'];
184
185 if( current_user_can( 'read_private_docs' ) ) {
186 array_push($post_status, 'private');
187 }
188
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,
195 's' => $search_input,
196 'orderby' => 'relevance',
197 'tax_query' => $tax_query,
198 'kb_slug' => $kb_slug
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;
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 public function get_name() {
259 return 'betterdocs_search_form';
260 }
261
262 /**
263 * Summary of default_attributes
264 * @return array
265 */
266 public function default_attributes() {
267 return apply_filters(
268 'betterdocs_search_form_attr',
269 [
270 'placeholder' => __( 'Search', 'betterdocs' ),
271 'heading' => '',
272 'subheading' => '',
273 'heading_tag' => 'h1',
274 'subheading_tag' => 'p',
275 'kb_based_search' => ''
276 ]
277 );
278 }
279
280 public function render( $atts, $content = null ) {
281 $kb_based_search = isset( $atts['kb_based_search'] ) ? sanitize_text_field( $atts['kb_based_search'] ) : '';
282
283 betterdocs()->assets->localize(
284 'betterdocs-search',
285 'betterdocsSearchConfigTwo',
286 [
287 'is_post_type_archive' => is_post_type_archive( 'docs' ),
288 'kb_based_search' => $kb_based_search
289 ]
290 );
291
292 $this->views( 'shortcodes/search' );
293 }
294 }
295