PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
4.9.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 All 200 releases
← All changes | includes/Shortcodes/SearchForm.php +23 -9 4.5.14.9.2 View file →
@@ -1,8 +1,12 @@
1 1 <?php
2 +namespace WPDeveloper\BetterDocs\Shortcodes;
2 3
3 -namespace WPDeveloper\BetterDocs\Shortcodes;
4 +if ( ! defined( 'ABSPATH' ) ) {
5 + exit;
6 +}
4 7
8 +
5 9 use WPDeveloper\BetterDocs\Core\Query;
6 10 use WPDeveloper\BetterDocs\Utils\Helper;
7 11 use WPDeveloper\BetterDocs\Core\Settings;
8 12 use WPDeveloper\BetterDocs\Core\Shortcode;
@@ -64,13 +68,17 @@
64 68 $current_lang = $lang_codes['current_language'];
65 69
66 70 // Only search in translation table if current language is different from default
67 71 if ( $default_lang !== $current_lang ) {
72 + $default_lang = preg_replace( '/[^a-z0-9_]/', '', $default_lang );
73 + $current_lang = preg_replace( '/[^a-z0-9_]/', '', $current_lang );
68 74 // TranslatePress table naming: wp_trp_dictionary_{default_lang}_{current_lang}
69 75 $trp_table = $wpdb->prefix . 'trp_dictionary_' . $default_lang . '_' . $current_lang;
70 -
76 +
71 77 if ( $this->table_exists( $trp_table ) ) {
72 -
78 + // $trp_table is composed from $wpdb->prefix + sanitized lang slugs (preg_replace allowlist above);
79 + // $like is esc_like()-wrapped with intentional % wildcards; CONCAT() wildcards are query literals, not user input.
80 + // phpcs:disable WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
73 81 $trp_search = $wpdb->prepare(
74 82 " OR EXISTS (
75 83 SELECT 1 FROM {$trp_table} trp
76 84 WHERE (trp.original LIKE %s OR trp.translated LIKE %s)
@@ -83,9 +91,10 @@
83 91 )",
84 92 $like,
85 93 $like
86 94 );
87 -
95 + // phpcs:enable WordPress.DB.PreparedSQLPlaceholders.LikeWildcardsInQuery,WordPress.DB.PreparedSQL.InterpolatedNotPrepared
96 +
88 97 $search .= $trp_search;
89 98 }
90 99 }
91 100 }
@@ -136,8 +145,9 @@
136 145 * Check if a database table exists
137 146 */
138 147 private function table_exists( $table_name ) {
139 148 global $wpdb;
149 + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- schema check, caching would mask plugin-activation state.
140 150 $result = $wpdb->get_var( $wpdb->prepare( "SHOW TABLES LIKE %s", $table_name ) );
141 151 return $result === $table_name;
142 152 }
143 153
@@ -156,12 +166,14 @@
156 166 }
157 167
158 168 public function get_search_results() {
159 169 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
170 + // phpcs:disable WordPress.Security.NonceVerification.Missing -- public live-search endpoint, no state change.
171 + $search_input = isset( $_POST['search_input'] ) ? sanitize_text_field( wp_unslash( $_POST['search_input'] ) ) : '';
172 + $search_cat = isset( $_POST['search_cat'] ) ? wp_strip_all_tags( wp_unslash( $_POST['search_cat'] ) ) : '';
173 + $lang = isset( $_POST['lang'] ) ? wp_strip_all_tags( wp_unslash( $_POST['lang'] ) ) : '';
174 + $kb_slug = isset( $_POST['kb_slug'] ) ? sanitize_text_field( wp_unslash( $_POST['kb_slug'] ) ) : '';
175 + // phpcs:enable WordPress.Security.NonceVerification.Missing
164 176
165 177 $tax_query = [];
166 178 if ( $search_cat ) {
167 179 $tax_query = [
@@ -185,8 +197,9 @@
185 197 if( current_user_can( 'read_private_docs' ) ) {
186 198 array_push($post_status, 'private');
187 199 }
188 200
201 + // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- search query supports user-selected category filter.
189 202 $args = [
190 203 'term_id' => isset( $term->term_id ) ? $term->term_id : 0,
191 204 'post_type' => 'docs',
192 205 'post_status' => $post_status,
@@ -193,8 +206,9 @@
193 206 'posts_per_page' => -1,
194 207 'suppress_filters' => false, // Changed to false to allow posts_search filter
195 208 's' => $search_input,
196 209 'orderby' => 'relevance',
210 + // 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.
197 211 'tax_query' => $tax_query,
198 212 'kb_slug' => $kb_slug // Pass kb_slug for filter hooks
199 213 ];
200 214
@@ -204,9 +218,9 @@
204 218 // search across all languages to find translated posts
205 219 if ( preg_match('/[^\x00-\x7F]/', $search_input) ) {
206 220 // Non-ASCII search: bypass WPML language filtering but allow posts_search filter
207 221 // This allows searching across all languages
208 - $args['suppress_filters'] = true;
222 + $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 223 } else {
210 224 // ASCII-only search (English), use WPML filters to restrict to current language
211 225 $args['suppress_filters'] = false;
212 226 $args['lang'] = ICL_LANGUAGE_CODE;