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/Core/Query.php +295 -83 4.7.04.9.2 View file →
@@ -26,9 +26,24 @@
26 26 add_action( 'parse_term_query', array( $this, 'parse_term_query' ) );
27 27 // add_action( 'parse_query', [$this, 'parse_query'], 1 );
28 28 add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ), 1 );
29 29 add_filter( 'betterdocs_base_terms_args', array( $this, 'modify_terms_args_for_private_docs' ), 10, 1 );
30 + // Drops terms whose private-inclusive count is 0 after `hide_empty` was
31 + // turned off by modify_terms_args_for_private_docs().
32 + add_filter( 'get_terms', array( $this, 'filter_terms_for_private_docs' ), 10, 3 );
30 33
34 + // Invalidate cached doc-category counts on any write that could change them.
35 + add_action( 'save_post_docs', array( $this, 'flush_term_counts_cache' ) );
36 + add_action( 'deleted_post', array( $this, 'flush_term_counts_cache_on_post' ), 10, 2 );
37 + add_action( 'edited_doc_category', array( $this, 'flush_term_counts_cache' ) );
38 + add_action( 'created_doc_category', array( $this, 'flush_term_counts_cache' ) );
39 + add_action( 'delete_doc_category', array( $this, 'flush_term_counts_cache' ) );
40 + add_action( 'set_object_terms', array( $this, 'flush_term_counts_cache_on_set' ), 10, 4 );
41 + // wp_update_term_count_now() — the `wp term recount` repair path, and any core
42 + // count update — fires edited_term_taxonomy, not any of the above. Without this
43 + // the recount fixes the DB while we keep serving the cached counts (#166).
44 + add_action( 'edited_term_taxonomy', array( $this, 'flush_term_counts_cache_on_term_taxonomy' ), 10, 2 );
45 +
31 46 /**
32 47 * These below filters are hooked for navigation only.
33 48 *
34 49 * For old version of this portion.
@@ -77,26 +92,53 @@
77 92 public function init() {
78 93 }
79 94
80 95 /**
81 - * Modify terms args to include terms with only private docs for users with read_private_docs capability
96 + * Modify terms args to include terms whose only docs are private, for users allowed to read them.
82 97 *
98 + * Gated on being logged in rather than on `read_private_docs`, because a doc's own
99 + * author may read their private doc without holding that capability. Which docs
100 + * actually count is decided per post by can_read_doc().
101 + *
83 102 * @param array $args
84 103 * @return array
85 104 */
86 105 public function modify_terms_args_for_private_docs( $args ) {
87 - // Only modify for users with read_private_docs capability and supported taxonomies
88 - if ( ! current_user_can( 'read_private_docs' ) || ! isset( $args[ 'taxonomy' ] ) ) {
106 + // Logged-out visitors always get WordPress' stock `hide_empty` behaviour.
107 + if ( ! is_user_logged_in() || ! isset( $args[ 'taxonomy' ] ) ) {
89 108 return $args;
90 109 }
91 110
92 - // Only support doc_category taxonomy for private docs filtering
93 - // knowledge_base terms don't have docs directly assigned to them
94 - $supported_taxonomies = array( 'doc_category' );
95 - if ( ! in_array( $args[ 'taxonomy' ], $supported_taxonomies ) ) {
111 + /**
112 + * Let add-ons opt their own taxonomies into the private-docs rescue path.
113 + *
114 + * BetterDocs Pro hooks this for `knowledge_base` (Multiple KB), where docs
115 + * ARE assigned to the KB term directly.
116 + *
117 + * @param array $args Term query args.
118 + */
119 + $_filtered = apply_filters( 'betterdocs_modify_terms_args_for_private_docs', $args );
120 + if ( is_array( $_filtered ) ) {
121 + $args = $_filtered;
122 + }
123 +
124 + // Already handled by an add-on (e.g. Pro's Multiple KB).
125 + if ( ! empty( $args[ '_betterdocs_filter_private' ] ) ) {
96 126 return $args;
97 127 }
98 128
129 + /**
130 + * Taxonomies whose terms have docs assigned directly, so a private-inclusive
131 + * object count can rescue a term WordPress dropped for `hide_empty`.
132 + *
133 + * `knowledge_base` is included so a KB whose only docs are private still shows
134 + * up for privileged users even on older Pro builds that predate the filter above.
135 + */
136 + $supported_taxonomies = array( 'doc_category', 'knowledge_base' );
137 + if ( ! in_array( $args[ 'taxonomy' ], $supported_taxonomies, true ) ) {
138 + return $args;
139 + }
140 +
99 141 // If hide_empty is true, we need to modify the logic to include terms with private docs
100 142 if ( isset( $args[ 'hide_empty' ] ) && $args[ 'hide_empty' ] ) {
101 143 // Set hide_empty to false and we'll filter manually later
102 144 $args[ 'hide_empty' ] = false;
@@ -309,14 +351,18 @@
309 351
310 352 if ( empty( $_docs_order ) ) {
311 353 $statuses = array( 'publish' );
312 354
313 - if ( current_user_can( 'read_private_docs' ) ) {
355 + if ( is_user_logged_in() ) {
314 356 $statuses[ ] = 'private';
315 357 }
316 358
317 359 $_args = array(
318 360 'post_status' => $statuses,
361 + // Required: with an explicit `post_status` WordPress only narrows private
362 + // docs to the ones the user may read when `perm` is `readable`. Without it,
363 + // listing `private` would expose every private doc to any logged-in user.
364 + 'perm' => 'readable',
319 365 'term_id' => $_terms[ 0 ]->term_id
320 366 );
321 367 if ( isset( $wp_query->query_vars[ 'doc_category' ] ) ) {
322 368 $_args[ 'tax_query' ][ ] = array(
@@ -457,14 +503,13 @@
457 503 public function get_terms( $args ) {
458 504 $parsed_args = $this->parse_terms_args( $args );
459 505 $terms = get_terms( $parsed_args );
460 506
461 - // Filter terms manually if we need to consider private docs for users with read_private_docs capability
462 - if ( isset( $parsed_args[ '_betterdocs_filter_private' ] ) && $parsed_args[ '_betterdocs_filter_private' ] && current_user_can( 'read_private_docs' ) ) {
507 + // Filter terms manually if we need to consider private docs the current user may read
508 + if ( isset( $parsed_args[ '_betterdocs_filter_private' ] ) && $parsed_args[ '_betterdocs_filter_private' ] && is_user_logged_in() ) {
463 509 $terms = array_filter( $terms, function ( $term ) {
464 510 // Get the actual count including private docs for users with read_private_docs capability
465 - $actual_count = $this->get_docs_count( $term, false );
466 - return $actual_count > 0;
511 + return $this->get_private_inclusive_docs_count( $term ) > 0;
467 512 } );
468 513 }
469 514
470 515 return $terms;
@@ -469,8 +514,124 @@
469 514
470 515 return $terms;
471 516 }
472 517
518 + /**
519 + * Drop terms with no visible docs after `hide_empty` was disabled for private docs.
520 + *
521 + * modify_terms_args_for_private_docs() turns `hide_empty` off so WordPress stops
522 + * dropping terms whose only docs are private. Without this counterpart, every
523 + * genuinely empty term would leak into the listing for privileged users. Runs only
524 + * for queries carrying the `_betterdocs_filter_private` flag, so anonymous and
525 + * unflagged queries are untouched.
526 + *
527 + * @param array|WP_Error $terms
528 + * @param array|null $taxonomies
529 + * @param array $args
530 + * @return array|WP_Error
531 + */
532 + public function filter_terms_for_private_docs( $terms, $taxonomies, $args ) {
533 + if ( empty( $args[ '_betterdocs_filter_private' ] ) || empty( $terms ) || is_wp_error( $terms ) ) {
534 + return $terms;
535 + }
536 +
537 + if ( ! is_user_logged_in() ) {
538 + return $terms;
539 + }
540 +
541 + // Only term objects can be counted; `ids`, `names`, `count`, ... pass through.
542 + $_fields = isset( $args[ 'fields' ] ) ? $args[ 'fields' ] : 'all';
543 + if ( ! in_array( $_fields, array( 'all', 'all_with_object_id' ), true ) ) {
544 + return $terms;
545 + }
546 +
547 + $_filtered = array_filter( $terms, function ( $term ) {
548 + if ( ! is_object( $term ) ) {
549 + return true;
550 + }
551 +
552 + return $this->get_private_inclusive_docs_count( $term ) > 0;
553 + } );
554 +
555 + return array_values( $_filtered );
556 + }
557 +
558 + /**
559 + * Docs count for a term, including private docs when the user may read them.
560 + *
561 + * KB terms need object-in-term counting rather than the doc_category count path,
562 + * which BetterDocs Pro provides through the filter below; get_docs_count() is the
563 + * fallback and already counts via get_objects_in_term() for the term's own taxonomy.
564 + *
565 + * @param WP_Term $term
566 + * @return int
567 + */
568 + /**
569 + * Whether the current user may see a doc in a listing.
570 + *
571 + * Public docs are visible to everyone. A private doc is visible to whoever may read
572 + * it — `read_post` maps to the base `read` capability for the doc's own author and
573 + * to `read_private_docs` for everyone else, so owners see their own private docs
574 + * without needing the capability. Other non-public statuses (draft, pending) stay
575 + * out of listings for everyone, as before.
576 + *
577 + * @param int $post_id
578 + * @return bool
579 + */
580 + /**
581 + * Cache-key fragment identifying whose visibility a cached count reflects.
582 + *
583 + * Everyone who can read every private doc sees the same numbers, so they share one
584 + * `priv` bucket; authors differ from each other and get their own. Logged-out
585 + * visitors all share bucket `0`, keeping the anonymous cache as hot as before.
586 + *
587 + * @return string
588 + */
589 + protected function count_cache_viewer_key() {
590 + if ( ! is_user_logged_in() ) {
591 + return '0';
592 + }
593 +
594 + if ( current_user_can( 'read_private_docs' ) ) {
595 + return 'priv';
596 + }
597 +
598 + return 'u' . get_current_user_id();
599 + }
600 +
601 + public function can_read_doc( $post_id ) {
602 + if ( is_post_publicly_viewable( $post_id ) ) {
603 + return true;
604 + }
605 +
606 + return 'private' === get_post_status( $post_id ) && current_user_can( 'read_post', $post_id );
607 + }
608 +
609 + protected function get_private_inclusive_docs_count( $term ) {
610 + /**
611 + * Let add-ons supply their own private-inclusive count for a term.
612 + *
613 + * Passing `null` means "not handled" — implementations that don't recognise the
614 + * term's taxonomy return the value untouched, and we fall back to get_docs_count().
615 + *
616 + * @param int|null $count
617 + * @param WP_Term $term
618 + */
619 + $_count = apply_filters( 'betterdocs_get_term_docs_count_for_private_filter', null, $term );
620 +
621 + if ( null !== $_count ) {
622 + return (int) $_count;
623 + }
624 +
625 + // Count nested sub-category docs too, so a parent whose docs live only in its
626 + // children stays visible to logged-in users exactly as it does for anonymous
627 + // visitors (whose grid is descendant-aware). get_docs_count( …, true ) already
628 + // filters each descendant doc through can_read_doc() via get_doc_ids_by_term(),
629 + // so private/unreadable docs never resurrect a term. A non-nested count here
630 + // hid those parents from logged-in users only.
631 + return (int) $this->get_docs_count( $term, true );
632 + }
633 +
473 634 public function get_child_terms( $args ) {
474 635 if ( ! isset( $args[ 'number' ] ) ) {
475 636 global $wp_query;
476 637 if ( null === $wp_query->query || ( isset( $wp_query->query[ 'post_type' ] ) && 'docs' != $wp_query->query[ 'post_type' ] ) ) {
@@ -668,57 +829,35 @@
668 829 *
669 830 * @return array An array of non-empty child term IDs.
670 831 */
671 832 public function get_all_child_term_ids( $taxonomy, $parent_id ) {
672 - // Set up the arguments for retrieving child terms
833 + $version = $this->database->get_cache_version( 'betterdocs_term_counts' );
834 + $cache_key = "bd_term_counts_v{$version}_child_ids_{$taxonomy}_{$parent_id}";
835 +
836 + $cached = wp_cache_get( $cache_key, 'betterdocs' );
837 + if ( false !== $cached ) {
838 + return $cached;
839 + }
840 +
841 + // get_terms( child_of => X ) walks the full descendant tree using WP's
842 + // internally cached term hierarchy, replacing the previous recursive
843 + // get_term()-in-a-loop pattern with one call.
673 844 $args = apply_filters(
674 845 'betterdocs_get_child_term_ids_args',
675 846 array(
676 - 'taxonomy' => $taxonomy,
677 - 'parent' => $parent_id,
847 + 'taxonomy' => $taxonomy,
848 + 'child_of' => $parent_id,
678 849 'hide_empty' => true,
679 - 'fields' => 'ids'
850 + 'fields' => 'ids',
680 851 )
681 852 );
682 853
683 - // Get the terms based on the arguments
684 - $terms = get_terms( $args );
854 + $ids = get_terms( $args );
855 + $ids = ( is_wp_error( $ids ) || ! is_array( $ids ) ) ? array() : array_map( 'intval', $ids );
685 856
686 - // Initialize an empty array to hold non-empty child term IDs
687 - $non_empty_children = array();
857 + wp_cache_set( $cache_key, $ids, 'betterdocs', HOUR_IN_SECONDS * 6 );
688 858
689 - // Check if terms were retrieved without errors and that the result isn't empty
690 - if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
691 - $term_ids = array();
692 -
693 - // Loop through each term ID
694 - foreach ( $terms as $term_id ) {
695 - // Add the current term ID to the term_ids array
696 - $term_ids[ ] = $term_id;
697 -
698 - // Recursively get child terms for the current term
699 - $child_term_ids = $this->get_all_child_term_ids( $taxonomy, $term_id );
700 -
701 - // If there are child terms, merge them into the term_ids array
702 - if ( ! empty( $child_term_ids ) ) {
703 - $term_ids = array_merge( $term_ids, $child_term_ids );
704 - }
705 - }
706 -
707 - // Loop through all retrieved term IDs to filter out empty ones
708 - foreach ( $term_ids as $term_id ) {
709 - // Get the term object for the current term ID
710 - $child_term = get_term( $term_id, $taxonomy );
711 -
712 - // Only include terms that have a non-zero post count
713 - if ( $child_term && isset( $child_term->term_id ) && $child_term->count > 0 ) {
714 - $non_empty_children[ ] = $child_term->term_id;
715 - }
716 - }
717 - }
718 -
719 - // Return the final array of non-empty child term IDs
720 - return $non_empty_children;
859 + return $ids;
721 860 }
722 861
723 862 /**
724 863 * Get all nested child term IDs of a specific parent term in a taxonomy and return as a comma-separated string.
@@ -1127,8 +1266,47 @@
1127 1266
1128 1267 return $terms;
1129 1268 }
1130 1269
1270 + public function flush_term_counts_cache() {
1271 + $this->database->bump_cache_version( 'betterdocs_term_counts' );
1272 + }
1273 +
1274 + public function flush_term_counts_cache_on_post( $post_id, $post = null ) {
1275 + if ( $post && isset( $post->post_type ) && $post->post_type === 'docs' ) {
1276 + $this->flush_term_counts_cache();
1277 + }
1278 + }
1279 +
1280 + public function flush_term_counts_cache_on_set( $object_id, $terms, $tt_ids, $taxonomy ) {
1281 + if ( $taxonomy === 'doc_category' ) {
1282 + $this->flush_term_counts_cache();
1283 + }
1284 + }
1285 +
1286 + /**
1287 + * Invalidate the count cache when a term count is recalculated.
1288 + *
1289 + * wp_update_term_count_now() (a recount, `wp term recount`, or any core count
1290 + * update) fires edited_term_taxonomy for each affected term. The version bump is
1291 + * a single update_option, so it is debounced to once per request with a static
1292 + * flag: one bump already invalidates every cached count, and a bulk recount would
1293 + * otherwise write the option once per term. (#166)
1294 + *
1295 + * @param int $tt_id Term taxonomy id.
1296 + * @param string $taxonomy Taxonomy name.
1297 + */
1298 + public function flush_term_counts_cache_on_term_taxonomy( $tt_id, $taxonomy ) {
1299 + static $flushed = false;
1300 +
1301 + if ( $flushed || ! in_array( $taxonomy, array( 'doc_category', 'knowledge_base' ), true ) ) {
1302 + return;
1303 + }
1304 +
1305 + $this->flush_term_counts_cache();
1306 + $flushed = true;
1307 + }
1308 +
1131 1309 public function get_docs_count( $term, $nested_subcategory = false, $args = array() ) {
1132 1310 // Validate term object
1133 1311 if ( ! is_object( $term ) ) {
1134 1312 return 0;
@@ -1135,42 +1313,55 @@
1135 1313 }
1136 1314
1137 1315 $counts = isset( $term->count ) ? $term->count : 0;
1138 1316
1317 + if ( ! isset( $term->term_id ) || ! is_numeric( $term->term_id ) ) {
1318 + return apply_filters( 'betterdocs_docs_count', $counts, $term, $nested_subcategory, $args );
1319 + }
1320 +
1321 + $version = $this->database->get_cache_version( 'betterdocs_term_counts' );
1322 + // Counts depend on who is asking: an author sees their own private docs, so the
1323 + // key carries the user id. Logged-out visitors all share user 0.
1324 + $viewer = $this->count_cache_viewer_key();
1325 + $kb_slug = isset( $args['kb_slug'] ) ? $args['kb_slug'] : '';
1326 + $multi = ! empty( $args['multiple_knowledge_base'] ) ? 1 : 0;
1327 + $nested = $nested_subcategory ? 1 : 0;
1328 + $cache_key = "bd_term_counts_v{$version}_docs_count_{$term->term_id}_{$nested}_{$viewer}_{$kb_slug}_{$multi}";
1329 +
1330 + $cached = wp_cache_get( $cache_key, 'betterdocs' );
1331 + if ( false !== $cached ) {
1332 + return apply_filters( 'betterdocs_docs_count', $cached, $term, $nested_subcategory, $args );
1333 + }
1334 +
1139 1335 if ( false == $nested_subcategory ) {
1140 1336 // For non-nested categories, we need to recalculate counts based on user capabilities
1141 1337 // Only proceed if we have a valid term with required properties
1142 - if ( isset( $term->term_id ) && isset( $term->taxonomy ) && is_numeric( $term->term_id ) ) {
1338 + if ( isset( $term->taxonomy ) ) {
1143 1339 // Get all post IDs for this term
1144 1340 $post_ids = get_objects_in_term( $term->term_id, $term->taxonomy );
1145 1341
1146 1342 if ( ! empty( $post_ids ) ) {
1147 - if ( current_user_can( 'read_private_docs' ) ) {
1148 - // For users with read_private_docs capability, include both private and public posts
1149 - $filtered_post_ids = array_filter( $post_ids, function ( $post_id ) {
1150 - $post_status = get_post_status( $post_id );
1151 - return 'private' === $post_status || is_post_publicly_viewable( $post_id );
1152 - } );
1153 - } else {
1154 - // For users without read_private_docs capability, only include public posts
1155 - $filtered_post_ids = array_filter( $post_ids, function ( $post_id ) {
1156 - return is_post_publicly_viewable( $post_id );
1157 - } );
1158 - }
1343 + _prime_post_caches( $post_ids, false, false );
1159 1344
1345 + // Public docs for everyone, plus any private doc this user may read
1346 + // (its own author, or a `read_private_docs` holder).
1347 + $filtered_post_ids = array_filter( $post_ids, function ( $post_id ) {
1348 + return $this->can_read_doc( $post_id );
1349 + } );
1350 +
1160 1351 $counts = count( $filtered_post_ids );
1161 1352 } else {
1162 1353 $counts = 0;
1163 1354 }
1164 1355 }
1165 -
1166 - return apply_filters( 'betterdocs_docs_count', $counts, $term, $nested_subcategory, $args );
1356 + } else {
1357 + $_child_terms_docs_ids = $this->get_doc_ids_by_term( $term, null, $nested_subcategory );
1358 + if ( is_array( $_child_terms_docs_ids ) ) {
1359 + $counts = count( $_child_terms_docs_ids );
1360 + }
1167 1361 }
1168 1362
1169 - $_child_terms_docs_ids = $this->get_doc_ids_by_term( $term, null, $nested_subcategory );
1170 - if ( is_array( $_child_terms_docs_ids ) ) {
1171 - $counts = count( $_child_terms_docs_ids );
1172 - }
1363 + wp_cache_set( $cache_key, $counts, 'betterdocs', HOUR_IN_SECONDS * 6 );
1173 1364
1174 1365 return apply_filters( 'betterdocs_docs_count', $counts, $term, $nested_subcategory, $args );
1175 1366 }
1176 1367
@@ -1179,8 +1370,19 @@
1179 1370 if ( ! is_object( $term ) || ! isset( $term->term_id ) || ! isset( $term->taxonomy ) || ! is_numeric( $term->term_id ) ) {
1180 1371 return false;
1181 1372 }
1182 1373
1374 + $version = $this->database->get_cache_version( 'betterdocs_term_counts' );
1375 + $viewer = $this->count_cache_viewer_key();
1376 + $nested = $nested_subcategory ? 1 : 0;
1377 + $optional_id = is_object( $optional ) && isset( $optional->term_id ) ? (int) $optional->term_id : 0;
1378 + $cache_key = "bd_term_counts_v{$version}_doc_ids_{$term->term_id}_{$nested}_{$optional_id}_{$viewer}";
1379 +
1380 + $cached = wp_cache_get( $cache_key, 'betterdocs' );
1381 + if ( false !== $cached ) {
1382 + return $cached;
1383 + }
1384 +
1183 1385 $args = array(
1184 1386 'taxonomy' => $term->taxonomy,
1185 1387 'include' => $term->term_id,
1186 1388 );
@@ -1204,16 +1406,19 @@
1204 1406 $_optional_doc_ids = get_objects_in_term( $optional->term_id, $optional->taxonomy );
1205 1407 $_child_terms_docs_ids = array_intersect( $_child_terms_docs_ids, $_optional_doc_ids );
1206 1408 }
1207 1409
1208 - return array_filter( $_child_terms_docs_ids, function ( $doc_id ) {
1209 - if ( ! current_user_can( 'read_private_docs' ) ) {
1210 - return is_post_publicly_viewable( $doc_id );
1211 - }
1410 + if ( ! empty( $_child_terms_docs_ids ) ) {
1411 + _prime_post_caches( $_child_terms_docs_ids, false, false );
1412 + }
1212 1413
1213 - $_status = get_post_status( $doc_id );
1214 - return 'private' == $_status || is_post_publicly_viewable( $doc_id );
1414 + $filtered = array_filter( $_child_terms_docs_ids, function ( $doc_id ) {
1415 + return $this->can_read_doc( $doc_id );
1215 1416 } );
1417 +
1418 + wp_cache_set( $cache_key, $filtered, 'betterdocs', HOUR_IN_SECONDS * 6 );
1419 +
1420 + return $filtered;
1216 1421 }
1217 1422
1218 1423 /**
1219 1424 * Get the common query arguments for WP_Query.
@@ -1311,16 +1516,22 @@
1311 1516 }
1312 1517
1313 1518 global $wpdb;
1314 1519
1520 + $keyword_hash = md5( $search_input );
1521 +
1315 1522 // phpcs:disable WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- live search-keyword analytics; cache would defeat the purpose.
1316 - // Use BINARY comparison to avoid collation mismatch errors
1317 - // This works across all character sets (latin1, utf8, utf8mb4, etc.)
1523 + // Matched on keyword_hash first so the index can serve the lookup — the
1524 + // BINARY comparison that follows is what actually decides equality (it
1525 + // avoids collation mismatch errors across latin1/utf8/utf8mb4), but no
1526 + // index can serve it, so on its own it full-scanned the table on every
1527 + // single front-end search.
1318 1528 $search = $wpdb->get_results(
1319 1529 $wpdb->prepare(
1320 1530 "SELECT *
1321 1531 FROM {$wpdb->prefix}betterdocs_search_keyword
1322 - WHERE BINARY keyword = %s",
1532 + WHERE keyword_hash = %s AND BINARY keyword = %s",
1533 + $keyword_hash,
1323 1534 $search_input
1324 1535 )
1325 1536 );
1326 1537
@@ -1379,12 +1590,13 @@
1379 1590 } else {
1380 1591 $insert = $wpdb->query(
1381 1592 $wpdb->prepare(
1382 1593 "INSERT INTO {$wpdb->prefix}betterdocs_search_keyword
1383 - ( keyword )
1384 - VALUES ( %s )",
1594 + ( keyword, keyword_hash )
1595 + VALUES ( %s, %s )",
1385 1596 array(
1386 - $search_input
1597 + $search_input,
1598 + $keyword_hash
1387 1599 )
1388 1600 )
1389 1601 );
1390 1602