PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.8.9
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.8.9
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/REST/Docs.php +34 -416 4.9.23.8.9 View file →
@@ -1,13 +1,8 @@
1 1 <?php
2 -// phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query,WordPress.DB.SlowDBQuery.slow_db_query_meta_key,WordPress.DB.SlowDBQuery.slow_db_query_tax_query -- core docs REST endpoints; meta/tax filtering required.
2 +
3 3 namespace WPDeveloper\BetterDocs\REST;
4 4
5 -if ( ! defined( 'ABSPATH' ) ) {
6 - exit;
7 -}
8 -
9 -
10 5 use Error;
11 6 use WP_Query;
12 7 use WP_REST_Response;
13 8 use WPDeveloper\BetterDocs\Core\BaseAPI;
@@ -17,23 +12,13 @@
17 12 return true;
18 13 }
19 14
20 15 public function register() {
21 - $this->get( 'search', [$this, 'search_posts'], [
22 - 'password' => [
23 - 'description' => __( 'The password for password-protected docs.', 'betterdocs' ),
24 - 'type' => 'string',
25 - ],
26 - ] );
16 + $this->get( 'search', [$this, 'search_posts'] );
27 17 $this->get( 'search-insert', [$this, 'search_insert'] );
28 18 $this->get( 'get-terms', [$this, 'get_terms_name_and_slug'] );
29 19 $this->get( 'months-with-posts', [$this, 'get_months_with_posts'] );
30 - $this->get( 'order_docs', [$this, 'render_betterdocs_order_docs'], [
31 - 'password' => [
32 - 'description' => __( 'The password for password-protected docs.', 'betterdocs' ),
33 - 'type' => 'string',
34 - ],
35 - ] );
20 + $this->get( 'order_docs', [$this, 'render_betterdocs_order_docs'] );
36 21 $this->register_field( 'docs', 'year_month', [
37 22 'get_callback' => [$this, 'year_month']
38 23 ] );
39 24
@@ -45,9 +30,8 @@
45 30 ]
46 31 );
47 32
48 33 add_filter( 'rest_docs_query', [ $this, 'filter_docs_query' ], 10, 2 );
49 - $this->get( 'docs-faq-count', [ $this, 'get_docs_faq_counts' ] );
50 34 }
51 35
52 36 public function render_betterdocs_order_docs($request) {
53 37 $doc_category = $request->get_param('doc_category');
@@ -66,18 +50,12 @@
66 50 'posts_per_page' => $per_page
67 51 ];
68 52
69 53 $args = betterdocs()->query->docs_query_args($args);
70 -
71 - // Exclude password-protected posts unless user has permission or provided password
72 - if ( ! current_user_can( 'edit_posts' ) ) {
73 - $args['has_password'] = false;
74 - }
75 -
76 54 $posts = betterdocs()->query->get_posts( $args, true );
77 55
78 56 if ( ! $posts->have_posts() ) {
79 - wp_reset_postdata();
57 + wp_reset_query();
80 58 }
81 59
82 60 $post_datas = [];
83 61
@@ -82,157 +60,25 @@
82 60 $post_datas = [];
83 61
84 62 while ( $posts->have_posts() ):
85 63 $posts->the_post();
86 - $post_obj = get_post( get_the_ID() );
87 -
88 - // Double-check password protection for individual posts
89 - if ( ! empty( $post_obj->post_password ) ) {
90 - $can_access = $this->can_access_password_content( $post_obj, $request );
91 - if ( ! $can_access ) {
92 - continue; // Skip this post
93 - }
94 - }
95 -
96 - $post_data = $this->get_doc_data( get_the_ID(), $request );
64 + $post_data = $this->get_doc_data( get_the_ID() );
97 65 array_push( $post_datas, $post_data );
98 66 endwhile;
99 67
100 68 wp_reset_postdata();
101 - wp_reset_postdata();
69 + wp_reset_query();
102 70
103 71 return $post_datas;
104 72 }
105 73
106 - public function get_docs_faq_counts() {
107 - // Initialize the return array
108 - $counts = [
109 - 'created_docs' => 0,
110 - 'published_docs' => 0,
111 - 'created_faq' => 0,
112 - 'published_faq' => 0,
113 - 'created_product_faq' => 0,
114 - 'published_product_faq' => 0
115 - ];
116 -
117 - // QA-004: this endpoint is public (Docs::permission_check() returns true),
118 - // so the draft-inclusive "created" totals must not leak unpublished-content
119 - // volume to anonymous / low-privilege callers. Only users who can edit
120 - // others' posts see the any-status counts; everyone else gets published
121 - // counts (created_* == published_*).
122 - $created_status = current_user_can( 'edit_others_posts' ) ? 'any' : 'publish';
123 -
124 - // Get all docs (created = any status for privileged users, else published)
125 - $all_docs_query = new WP_Query([
126 - 'post_type' => 'docs',
127 - 'post_status' => $created_status,
128 - 'posts_per_page' => -1,
129 - 'fields' => 'ids',
130 - 'no_found_rows' => true,
131 - ]);
132 - $counts['created_docs'] = $all_docs_query->post_count;
133 -
134 - // Get published docs only
135 - $published_docs_query = new WP_Query([
136 - 'post_type' => 'docs',
137 - 'post_status' => 'publish',
138 - 'posts_per_page' => -1,
139 - 'fields' => 'ids',
140 - 'no_found_rows' => true,
141 - ]);
142 - $counts['published_docs'] = $published_docs_query->post_count;
143 -
144 - // Product FAQ groups share the betterdocs_faq post type but live in the
145 - // betterdocs_product_faq_category taxonomy. Split the counts so the
146 - // General FAQ Builder stats exclude Product FAQs (no leakage) and the
147 - // WooCommerce tab can show its own Product FAQ totals.
148 - $product_terms = get_terms([
149 - 'taxonomy' => 'betterdocs_product_faq_category',
150 - 'hide_empty' => false,
151 - 'fields' => 'ids',
152 - ]);
153 - $product_terms = ( ! is_wp_error( $product_terms ) && ! empty( $product_terms ) ) ? $product_terms : [];
154 -
155 - $exclude_product_tax_query = ! empty( $product_terms ) ? [
156 - [
157 - 'taxonomy' => 'betterdocs_product_faq_category',
158 - 'field' => 'term_id',
159 - 'terms' => $product_terms,
160 - 'operator' => 'NOT IN',
161 - ],
162 - ] : [];
163 -
164 - $include_product_tax_query = ! empty( $product_terms ) ? [
165 - [
166 - 'taxonomy' => 'betterdocs_product_faq_category',
167 - 'field' => 'term_id',
168 - 'terms' => $product_terms,
169 - 'operator' => 'IN',
170 - ],
171 - ] : [];
172 -
173 - // General FAQs (created = any status for privileged users), excluding Product FAQs.
174 - $all_faq_query = new WP_Query([
175 - 'post_type' => 'betterdocs_faq',
176 - 'post_status' => $created_status,
177 - 'posts_per_page' => -1,
178 - 'fields' => 'ids',
179 - 'no_found_rows' => true,
180 - 'tax_query' => $exclude_product_tax_query,
181 - ]);
182 - $counts['created_faq'] = $all_faq_query->post_count;
183 -
184 - // Published General FAQs, excluding Product FAQs.
185 - $published_faq_query = new WP_Query([
186 - 'post_type' => 'betterdocs_faq',
187 - 'post_status' => 'publish',
188 - 'posts_per_page' => -1,
189 - 'fields' => 'ids',
190 - 'no_found_rows' => true,
191 - 'tax_query' => $exclude_product_tax_query,
192 - ]);
193 - $counts['published_faq'] = $published_faq_query->post_count;
194 -
195 - if ( ! empty( $product_terms ) ) {
196 - // Product FAQs (created = any status for privileged users).
197 - $all_product_faq_query = new WP_Query([
198 - 'post_type' => 'betterdocs_faq',
199 - 'post_status' => $created_status,
200 - 'posts_per_page' => -1,
201 - 'fields' => 'ids',
202 - 'no_found_rows' => true,
203 - 'tax_query' => $include_product_tax_query,
204 - ]);
205 - $counts['created_product_faq'] = $all_product_faq_query->post_count;
206 -
207 - // Published Product FAQs.
208 - $published_product_faq_query = new WP_Query([
209 - 'post_type' => 'betterdocs_faq',
210 - 'post_status' => 'publish',
211 - 'posts_per_page' => -1,
212 - 'fields' => 'ids',
213 - 'no_found_rows' => true,
214 - 'tax_query' => $include_product_tax_query,
215 - ]);
216 - $counts['published_product_faq'] = $published_product_faq_query->post_count;
217 - }
218 -
219 - return $counts;
220 - }
221 -
222 74 /**
223 75 * Get Doc Data Based On Doc ID
224 76 *
225 - * @param int $id Post ID
226 - * @param WP_REST_Request $request REST request object
227 77 * @return array
228 78 */
229 - public function get_doc_data( $id, $request = null ) {
79 + public function get_doc_data( $id ) {
230 80 $post_data = get_post( $id );
231 -
232 - // Check if user can access password-protected content
233 - $can_access_password_content = $this->can_access_password_content( $post_data, $request );
234 -
235 81 $data = [
236 82 'author' => (int) $post_data->post_author,
237 83 'author_info' => [
238 84 'name' => get_the_author_meta( 'display_name', $post_data->post_author ),
@@ -250,30 +96,12 @@
250 96 'date' => $post_data->post_date,
251 97 'date_gmt' => $post_data->post_date_gmt,
252 98 'doc_category' => wp_get_post_terms( $id, 'doc_category', ["fields" => "ids"] ),
253 99 'doc_tag' => wp_get_post_terms( $id, 'doc_tag', ["fields" => "ids"] ),
100 + 'password' => $post_data->post_password,
254 101 'comment_status' => $post_data->comment_status
255 102 ];
256 103
257 - // Only include password field if user has edit permissions
258 - if ( current_user_can( 'edit_post', $id ) ) {
259 - $data['password'] = $post_data->post_password;
260 - }
261 -
262 - // Add password protection indicator
263 - if ( ! empty( $post_data->post_password ) ) {
264 - $data['password_protected'] = true;
265 -
266 - // If user cannot access password-protected content, hide sensitive data
267 - if ( ! $can_access_password_content ) {
268 - // Keep basic info but indicate it's protected
269 - $data['title']['rendered'] = $post_data->post_title; // WordPress doesn't prefix in REST API
270 - $data['excerpt'] = ''; // Hide excerpt for password-protected posts
271 - }
272 - } else {
273 - $data['password_protected'] = false;
274 - }
275 -
276 104 if ( taxonomy_exists( 'knowledge_base' ) ) {
277 105 $data['knowledge_base'] = wp_get_post_terms( $id, 'knowledge_base', ["fields" => "ids"] );
278 106 }
279 107
@@ -280,41 +108,8 @@
280 108 return $data;
281 109 }
282 110
283 111 /**
284 - * Checks if the user can access password-protected content.
285 - *
286 - * This method determines whether we need to override the regular password
287 - * check in core with a filter.
288 - *
289 - * @param WP_Post $post Post to check against.
290 - * @param WP_REST_Request $request Request data to check.
291 - * @return bool True if the user can access password-protected content, otherwise false.
292 - */
293 - public function can_access_password_content( $post, $request ) {
294 - if ( empty( $post->post_password ) ) {
295 - // No filter required.
296 - return true;
297 - }
298 -
299 - /*
300 - * Users always get access to password protected content if they have
301 - * the `edit_post` meta capability.
302 - */
303 - if ( current_user_can( 'edit_post', $post->ID ) ) {
304 - return true;
305 - }
306 -
307 - // No password provided in request, no auth.
308 - if ( empty( $request ) || empty( $request['password'] ) ) {
309 - return false;
310 - }
311 -
312 - // Double-check the request password.
313 - return hash_equals( $post->post_password, $request['password'] );
314 - }
315 -
316 - /**
317 112 * Retrieves the months and years that have posts of the type 'docs' and formats them.
318 113 *
319 114 * This function queries the WordPress database for all unique months and years
320 115 * in which 'docs' post type posts have been published. The results are then
@@ -331,9 +126,8 @@
331 126 public function get_months_with_posts() {
332 127 global $wpdb;
333 128
334 129 // Query to get distinct year and month from posts of type 'docs'
335 - // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared -- aggregation across the posts table; no user input.
336 130 $results = $wpdb->get_results(
337 131 "SELECT DISTINCT YEAR(post_date) AS year, MONTH(post_date) AS month
338 132 FROM $wpdb->posts
339 133 WHERE post_type = 'docs'
@@ -378,9 +172,9 @@
378 172 return $formatted_date;
379 173 }
380 174
381 175 /**
382 - * Filter the docs query by year_month and status parameters.
176 + * Filter the docs query by year_month parameters.
383 177 *
384 178 * @param array $args The query arguments.
385 179 * @param WP_REST_Request $request The current REST API request.
386 180 * @return array Modified query arguments.
@@ -402,22 +196,13 @@
402 196 ]
403 197 ];
404 198 }
405 199
406 - // Filter by post status
407 - // When status is 'any' and user has edit_docs capability, show all post statuses
408 - if ( isset( $request['status'] ) && $request['status'] === 'any' && current_user_can( 'edit_docs' ) ) {
409 - $args['post_status'] = [ 'publish', 'draft', 'pending', 'private', 'future' ];
410 - }
411 -
412 200 return $args;
413 201 }
414 202
415 203
416 204 public function get_post_password( $object, $field_name, $request ) {
417 - // Suppress unused parameter warnings
418 - unset( $field_name, $request );
419 -
420 205 if ( current_user_can( 'edit_docs' ) ) {
421 206 return isset( $object['password'] ) ? $object['password'] : '';
422 207 } else {
423 208 return '';
@@ -424,57 +209,31 @@
424 209 }
425 210 }
426 211
427 212 public function search_posts( $request ) {
428 - $search_query = sanitize_text_field( $request->get_param( 's' ) );
429 - $doc_category = sanitize_text_field( $request->get_param( 'doc_category' ) );
430 - $kb_slug = sanitize_text_field( $request->get_param( 'knowledge_base' ) );
431 - $number = (int) $request->get_param( 'per_page' ) ? (int) $request->get_param( 'per_page' ) : 5;
432 - $docs_ids = ! empty( $request->get_param( 'doc_ids' ) ) ? explode( ',', $request->get_param( 'doc_ids' ) ) : [];
433 - $doc_term_ids = ! empty( $request->get_param( 'doc_categories_ids' ) ) ? explode( ',', $request->get_param( 'doc_categories_ids' ) ) : [];
434 - $faq_term_ids = ! empty( $request->get_param( 'faq_categories_ids' ) ) ? explode( ',', $request->get_param( 'faq_categories_ids' ) ) : [];
435 - $posts = array();
436 - $post_status = ['publish'];
213 + $search_query = sanitize_text_field( $request->get_param( 's' ) );
214 + $doc_category = sanitize_text_field( $request->get_param( 'doc_category' ) );
215 + $number = (int) $request->get_param( 'per_page' ) ? (int) $request->get_param( 'per_page' ) : 5;
216 + $docs_ids = ! empty( $request->get_param( 'doc_ids' ) ) ? explode( ',', $request->get_param( 'doc_ids' ) ) : [];
217 + $doc_term_ids = ! empty( $request->get_param( 'doc_categories_ids' ) ) ? explode( ',', $request->get_param( 'doc_categories_ids' ) ) : [];
218 + $faq_term_ids = ! empty( $request->get_param( 'faq_categories_ids' ) ) ? explode( ',', $request->get_param( 'faq_categories_ids' ) ) : [];
219 + $posts = array();
437 220
438 - if( current_user_can( 'read_private_docs' ) ) {
439 - array_push($post_status, 'private');
440 - }
441 -
442 221 // Common query args
443 222 $common_args = [
444 - 'post_status' => $post_status,
445 - 'suppress_filters' => true, // phpcs:ignore WordPressVIPMinimum.Hooks.PreGetPosts.PreGetPosts,WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- search bypasses content filters; WPML override below.
223 + 'post_status' => 'publish',
224 + 'suppress_filters' => true,
446 225 'orderby' => 'relevance',
447 226 ];
448 227
449 - // Exclude password-protected posts unless user has permission
450 - if ( ! current_user_can( 'edit_posts' ) ) {
451 - $common_args['has_password'] = false;
452 - }
453 -
454 - // Handle WPML multilingual search
455 228 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
456 - // If search term contains non-ASCII characters (e.g., Chinese, Japanese, Bangla),
457 - // search across all languages to find translated posts
458 - if ( $search_query && preg_match('/[^\x00-\x7F]/', $search_query) ) {
459 - // Non-ASCII search: bypass ALL filters including WPML language filtering
460 - // This allows searching across all languages
461 - $common_args['suppress_filters'] = true; // phpcs:ignore WordPressVIPMinimum.Hooks.PreGetPosts.PreGetPosts,WordPressVIPMinimum.Performance.WPQueryParams.SuppressFilters_suppress_filters -- non-ASCII search must reach all WPML translations.
462 - } else {
463 - // ASCII-only search (English), use WPML filters to restrict to current language
464 - $common_args['suppress_filters'] = false;
465 - $common_args['lang'] = ICL_LANGUAGE_CODE;
466 - }
229 + $common_args['suppress_filters'] = false;
230 + $common_args['lang'] = ICL_LANGUAGE_CODE;
467 231 }
468 232
469 233 if ( $search_query ) {
470 234 $common_args['s'] = $search_query;
471 - // Respect per_page from the client; cap at 50 so a slow LIKE query can't load thousands of rows.
472 - $common_args['posts_per_page'] = $number > 0 ? min( $number, 50 ) : 20;
473 -
474 - // SearchExtender's posts_search filter must run so docs with matching
475 - // tag/category term names are included in results.
476 - $common_args['suppress_filters'] = false;
235 + $common_args['posts_per_page'] = -1;
477 236 } else {
478 237 $common_args['posts_per_page'] = $number;
479 238 }
480 239
@@ -486,18 +245,12 @@
486 245 ]
487 246 );
488 247
489 248 if ( ! $search_query ) {
490 - // Use date ordering when KB filter is present to avoid analytics query conflicts
491 - if ( ! empty( $kb_slug ) ) {
492 - $docs_args['orderby'] = 'date';
493 - $docs_args['order'] = 'DESC';
494 - } else {
495 249 $docs_args['meta_key'] = '_betterdocs_meta_views';
496 250 $docs_args['orderby'] = 'meta_value_num';
497 251 $docs_args['order'] = 'DESC';
498 252 }
499 - }
500 253
501 254 if ( ! empty( $docs_ids ) ) {
502 255 unset( $docs_args['meta_key'] );
503 256 $docs_args['posts_per_page'] = -1;
@@ -529,15 +282,8 @@
529 282 ],
530 283 ];
531 284 }
532 285
533 - // Knowledge base filter for docs
534 - // Pass kb_slug in args to let MultipleKB filter handle it (avoid duplicate filters)
535 - if ( ! empty( $kb_slug ) && taxonomy_exists( 'knowledge_base' ) ) {
536 - $docs_args['kb_slug'] = $kb_slug;
537 - }
538 -
539 -
540 286 // FAQ-specific query
541 287 $faq_args = array_merge(
542 288 $common_args,
543 289 [
@@ -558,27 +304,17 @@
558 304 ]
559 305 ];
560 306 }
561 307
308 + // Run individual queries
562 309 $docs_query = betterdocs()->query->get_posts( $docs_args );
310 + $faq_query = new WP_Query( $faq_args );
563 311
564 - $faq_query = new WP_Query( $faq_args );
565 -
566 312 // Process docs posts
567 313 if ( $docs_query->have_posts() ) {
568 314 while ( $docs_query->have_posts() ) {
569 315 $docs_query->the_post();
570 316
571 - $post_obj = get_post( get_the_ID() );
572 -
573 - // Check if user can access password-protected content
574 - $can_access = $this->can_access_password_content( $post_obj, $request );
575 -
576 - // Skip password-protected posts if user cannot access them
577 - if ( ! empty( $post_obj->post_password ) && ! $can_access ) {
578 - continue;
579 - }
580 -
581 317 $taxonomies = array();
582 318 $terms = get_the_terms( get_the_ID(), 'doc_category' );
583 319 if ( $terms && ! is_wp_error( $terms ) ) {
584 320 $taxonomies = wp_list_pluck( $terms, 'name' );
@@ -583,91 +319,12 @@
583 319 if ( $terms && ! is_wp_error( $terms ) ) {
584 320 $taxonomies = wp_list_pluck( $terms, 'name' );
585 321 }
586 322
587 - // Get the correct permalink with language parameter if needed
588 - $post_id = get_the_ID();
589 - $permalink = get_the_permalink( $post_id );
590 -
591 - // If WPML is active and post language differs from site language, add lang parameter
592 - if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
593 - // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedHooknameFound -- WPML public integration filter.
594 - $post_language = apply_filters( 'wpml_element_language_code', null, array( 'element_id' => $post_id, 'element_type' => 'post_docs' ) );
595 -
596 - if ( $post_language ) {
597 - global $sitepress;
598 - $current_lang = $sitepress ? $sitepress->get_current_language() : '';
599 -
600 - // If post language is different from current site language, add language parameter
601 - if ( $post_language !== $current_lang ) {
602 - $permalink = add_query_arg( 'lang', $post_language, $permalink );
603 - }
604 - }
605 - }
606 - // Handle TranslatePress permalinks
607 - elseif ( class_exists( '\TRP_Translate_Press' ) ) {
608 - global $TRP_LANGUAGE;
609 - $trp = \TRP_Translate_Press::get_trp_instance();
610 - if ( isset( $trp ) && method_exists( $trp, 'get_component' ) ) {
611 - $trp_settings = $trp->get_component( 'settings' );
612 - $trp_url_converter = $trp->get_component( 'url_converter' );
613 -
614 - if ( $trp_settings && $trp_url_converter && isset( $TRP_LANGUAGE ) ) {
615 - $settings = $trp_settings->get_settings();
616 - $default_lang = isset( $settings['default-language'] ) ? $settings['default-language'] : 'en_US';
617 -
618 - // If we're not on the default language, ensure the URL has the language prefix
619 - if ( $TRP_LANGUAGE && $TRP_LANGUAGE !== $default_lang ) {
620 - $permalink = $trp_url_converter->get_url_for_language( $TRP_LANGUAGE, $permalink );
621 - // Remove the #TRPLINKPROCESSED marker that TranslatePress adds
622 - $permalink = str_replace( '#TRPLINKPROCESSED', '', $permalink );
623 - }
624 - }
625 - }
626 - }
627 -
628 -
629 - // Get the title - apply TranslatePress translation if active
630 - $title = get_the_title();
631 - if ( class_exists( '\TRP_Translate_Press' ) ) {
632 - global $TRP_LANGUAGE, $wpdb;
633 - if ( isset( $TRP_LANGUAGE ) ) {
634 - $trp = \TRP_Translate_Press::get_trp_instance();
635 - if ( isset( $trp ) && method_exists( $trp, 'get_component' ) ) {
636 - $trp_settings = $trp->get_component( 'settings' );
637 - if ( $trp_settings ) {
638 - $settings = $trp_settings->get_settings();
639 - $default_lang = isset( $settings['default-language'] ) ? strtolower( $settings['default-language'] ) : 'en_us';
640 - $current_lang = strtolower( $TRP_LANGUAGE );
641 -
642 - // Only query translation if not on default language
643 - if ( $default_lang !== $current_lang ) {
644 - $default_lang = preg_replace( '/[^a-z0-9_]/', '', $default_lang );
645 - $current_lang = preg_replace( '/[^a-z0-9_]/', '', $current_lang );
646 - $trp_table = $wpdb->prefix . 'trp_dictionary_' . $default_lang . '_' . $current_lang;
647 -
648 - // Query the translation dictionary for this title.
649 - // $trp_table is composed from $wpdb->prefix + sanitized lang slugs (preg_replace allowlist above), safe to interpolate.
650 - // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter -- TranslatePress dynamic dictionary table; cache would defeat live translation lookup.
651 - $translated = $wpdb->get_var( $wpdb->prepare(
652 - "SELECT translated FROM {$trp_table} WHERE original = %s AND status != 2 LIMIT 1",
653 - $title
654 - ) );
655 - // phpcs:enable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.InterpolatedNotPrepared,PluginCheck.Security.DirectDB.UnescapedDBParameter
656 -
657 - if ( $translated && ! empty( $translated ) ) {
658 - $title = $translated;
659 - }
660 - }
661 - }
662 - }
663 - }
664 - }
665 -
666 323 $posts[] = array(
667 - 'title' => $title,
324 + 'title' => get_the_title(),
668 325 'post_type' => get_post_type(),
669 - 'permalink' => $permalink,
326 + 'permalink' => get_the_permalink(),
670 327 'taxonomies' => implode( ', ', $taxonomies ),
671 328 );
672 329 }
673 330 wp_reset_postdata();
@@ -703,57 +360,21 @@
703 360 public function search_insert( $request ) {
704 361 $search_input = sanitize_text_field( $request->get_param( 's' ) );
705 362 $no_result = sanitize_text_field( $request->get_param( 'no_result' ) );
706 363
707 - $result = betterdocs()->query->insert_search_keyword( $search_input, $no_result );
708 -
709 - if ( ! empty( $search_input ) ) {
710 - /**
711 - * Fires after a front-end docs search is logged.
712 - *
713 - * Pro hooks this to record a 'search' event into the raw events
714 - * table, which the Advanced Analytics aggregator rolls up into
715 - * betterdocs_analytics_search (search volume + zero-result rate —
716 - * the primary content-gap signal).
717 - *
718 - * @param string $search_input The search term.
719 - * @param bool $no_result True when the search returned no results.
720 - * @param WP_REST_Request $request The ingest request.
721 - */
722 - do_action( 'betterdocs_analytics_search_recorded', $search_input, ! empty( $no_result ), $request );
723 - }
724 -
725 - return $result;
364 + return betterdocs()->query->insert_search_keyword( $search_input, $no_result );
726 365 }
727 366
728 367
729 368 public function get_terms_name_and_slug( $request ) {
730 - $default_params = [
731 - 'taxonomy' => $request->get_param( 'taxonomy' ),
732 - 'hide_empty' => false,
733 - 'fields' => 'all',
734 - ];
735 -
736 - if ( betterdocs()->settings->get( 'child_category_exclude' ) ) { //disable child terms if this is enabled
737 - $default_params['parent'] = 0;
738 - }
739 -
740 - // Add KB filtering if knowledge_base parameter is provided
741 - $kb_slug = $request->get_param( 'knowledge_base' );
742 - if ( ! empty( $kb_slug ) && $request->get_param( 'taxonomy' ) === 'doc_category' ) {
743 - // Categories can belong to multiple KBs (stored as serialized array in doc_category_knowledge_base)
744 - // We need to filter categories that have the KB slug in their serialized array
745 - $default_params['meta_query'] = [
746 - [
747 - 'key' => 'doc_category_knowledge_base',
748 - 'value' => serialize(strval($kb_slug)),
749 - 'compare' => 'LIKE'
750 - ]
751 - ];
752 - }
753 -
754 369 // Retrieve all terms for the specified taxonomy, including empty ones
755 - $terms = get_terms($default_params);
370 + $terms = get_terms(
371 + [
372 + 'taxonomy' => $request->get_param( 'taxonomy' ),
373 + 'hide_empty' => false,
374 + 'fields' => 'all',
375 + ]
376 + );
756 377
757 378 // Initialize an empty array to hold the term data
758 379 $term_data = [];
759 380
@@ -772,9 +393,6 @@
772 393 // Return the array of term data
773 394 return $term_data;
774 395 }
775 396
776 - public function get_faq_categories( $request ) {
777 - // Suppress unused parameter warning
778 - unset( $request );
779 - }
397 + public function get_faq_categories( $request ) {}
780 398 }