PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.4.1
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.4.1
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 / Core / Query.php

Query.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.4.1, at includes/Core/Query.php

1,373 lines 48.7 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\Core;
4
5 use WP_Query;
6 use WPDeveloper\BetterDocs\Utils\Base;
7 use WPDeveloper\BetterDocs\Utils\Database;
8 use WPDeveloper\BetterDocs\Dependencies\DI\Container;
9
10 class Query extends Base {
11 private $container;
12 protected $database;
13 protected $settings;
14
15 public function __construct( Container $container, Database $database, Settings $settings ) {
16 $this->container = $container;
17 $this->database = $database;
18 $this->settings = $settings;
19
20 add_action( 'parse_term_query', array( $this, 'parse_term_query' ) );
21 // add_action( 'parse_query', [$this, 'parse_query'], 1 );
22 add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ), 1 );
23 add_filter( 'betterdocs_base_terms_args', array( $this, 'modify_terms_args_for_private_docs' ), 10, 1 );
24
25 /**
26 * These below filters are hooked for navigation only.
27 *
28 * For old version of this portion.
29 * @see `betterdocs_single_post_nav` filter
30 *
31 * For details:
32 * @see https://developer.wordpress.org/reference/functions/get_next_post/
33 * @see https://developer.wordpress.org/reference/functions/get_previous_post/
34 *
35 * @link https://developer.wordpress.org/reference/hooks/get_adjacent_post_where/
36 */
37 add_filter( 'get_next_post_where', array( $this, 'next_post_where' ), 99, 5 );
38 add_filter( 'get_previous_post_where', array( $this, 'previous_post_where' ), 99, 5 );
39
40 $this->init();
41
42 /**
43 * Modify Popular Docs Query (For Shortcode & Widget)
44 */
45 add_filter( 'posts_clauses', array( $this, 'mod_query_popular_docs' ), 10, 2 );
46 }
47
48 public function mod_query_popular_docs( $clauses, $wp_query ) {
49 if ( isset( $wp_query->query[ 'meta_key' ] ) ) {
50 if ( '_betterdocs_meta_views' == $wp_query->query[ 'meta_key' ] ) {
51 global $wpdb;
52 $order = isset( $wp_query->query[ 'order' ] ) ? $wp_query->query[ 'order' ] : '';
53 $order_by_query = ( 'ASC' == $order || 'DESC' == $order ) ? "SUM({$wpdb->prefix}betterdocs_analytics.impressions) {$order}" : ( 'MODIFIED' == $order ? "{$wpdb->prefix}posts.post_modified_gmt DESC" : "{$wpdb->prefix}posts.post_date_gmt DESC" );
54 $clauses[ 'join' ] = "JOIN {$wpdb->prefix}betterdocs_analytics ON {$wpdb->prefix}posts.ID = {$wpdb->prefix}betterdocs_analytics.post_id";
55 $clauses[ 'where' ] = ! current_user_can( 'read_private_docs' ) ? "AND ( ( {$wpdb->prefix}posts.post_type = 'docs' ) AND ( {$wpdb->prefix}posts.post_status = 'publish' OR {$wpdb->prefix}posts.post_status = 'future' ) )" : "AND ( ( {$wpdb->prefix}posts.post_type = 'docs' ) AND ( {$wpdb->prefix}posts.post_status = 'publish' OR {$wpdb->prefix}posts.post_status = 'future' OR {$wpdb->prefix}posts.post_status = 'draft' OR {$wpdb->prefix}posts.post_status = 'pending' OR {$wpdb->prefix}posts.post_status = 'private' ) )";
56 $clauses[ 'orderby' ] = $order_by_query;
57 $clauses[ 'groupby' ] = "{$wpdb->prefix}betterdocs_analytics.post_id";
58 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
59 global $sitepress;
60 if ( $sitepress->is_setup_complete() ) {
61 $constant_language_code = ICL_LANGUAGE_CODE;
62 $clauses[ 'join' ] .= " JOIN {$wpdb->prefix}icl_translations ON {$wpdb->prefix}posts.ID = {$wpdb->prefix}icl_translations.element_id";
63 $clauses[ 'where' ] .= " AND ( {$wpdb->prefix}icl_translations.language_code = '{$constant_language_code}' ) AND ( {$wpdb->prefix}icl_translations.element_type = CONCAT('post_', {$wpdb->prefix}posts.post_type ) )";
64 }
65 }
66 }
67 }
68 return $clauses;
69 }
70
71 public function init() {
72 }
73
74 /**
75 * Modify terms args to include terms with only private docs for users with read_private_docs capability
76 *
77 * @param array $args
78 * @return array
79 */
80 public function modify_terms_args_for_private_docs( $args ) {
81 // Only modify for users with read_private_docs capability and supported taxonomies
82 if ( ! current_user_can( 'read_private_docs' ) || ! isset( $args[ 'taxonomy' ] ) ) {
83 return $args;
84 }
85
86 // Only support doc_category taxonomy for private docs filtering
87 // knowledge_base terms don't have docs directly assigned to them
88 $supported_taxonomies = array( 'doc_category' );
89 if ( ! in_array( $args[ 'taxonomy' ], $supported_taxonomies ) ) {
90 return $args;
91 }
92
93 // If hide_empty is true, we need to modify the logic to include terms with private docs
94 if ( isset( $args[ 'hide_empty' ] ) && $args[ 'hide_empty' ] ) {
95 // Set hide_empty to false and we'll filter manually later
96 $args[ 'hide_empty' ] = false;
97 // Add a flag to indicate we need to filter manually
98 $args[ '_betterdocs_filter_private' ] = true;
99 }
100
101 return $args;
102 }
103
104 public function parse_term_query( $term_query ) {
105 if ( empty( $term_query->query_vars[ 'taxonomy' ] ) ) {
106 return;
107 }
108
109 if ( ! in_array( 'doc_category', $term_query->query_vars[ 'taxonomy' ], true ) ) {
110 return;
111 }
112
113 global $current_screen;
114
115 if ( null == $current_screen ) {
116 return;
117 }
118
119 if ( 'doc_category' !== $current_screen->taxonomy || 'edit-doc_category' != $current_screen->id ) {
120 return;
121 }
122
123 // Use base meta key for admin listing - fallback logic is handled in set_tax_order
124 $meta_key = 'doc_category_order';
125
126 $term_query->query_vars[ 'meta_query' ] = array(
127 array(
128 'key' => $meta_key,
129 'type' => 'NUMERIC'
130 )
131 );
132
133 $term_query->query_vars[ 'orderby' ] = 'meta_value_num';
134 }
135
136 public function parse_query( &$query ) {
137 // dump( is_single(), $query->query_vars );
138 // if ( is_single() && isset( $query->query_vars['post_type'] ) && $query->query_vars['post_type'] == 'docs' ) {
139 // $query->is_single = false;
140 // $query->is_archive = true;
141 // $query->set( 'knowledge_base', $query->query_vars['docs'] );
142 // }
143 }
144
145 public function pre_get_posts( &$query ) {
146 if ( is_admin() || ! $query->is_main_query() ) {
147 return;
148 }
149
150 if ( is_tax( 'doc_category' ) ) {
151 $query->set( 'post_type', 'docs' );
152 $query->set( 'posts_per_archive_page', -1 );
153
154 $term = get_term_by( 'slug', $query->get( 'doc_category', '' ), 'doc_category' );
155
156 if ( $term && isset( $term->term_id ) ) {
157 $post__in = $this->get_docs_order_by_terms( $term->term_id );
158 } else {
159 $post__in = array();
160 }
161 if ( ! empty( $post__in ) ) {
162 $query->set( 'orderby', 'post__in' );
163 $query->set( 'post__in', $post__in );
164 }
165
166 // if ( ! empty( $query->query_vars['knowledge_base'] ) ) {
167 // $query->betterdocs_terms = get_terms( [
168 // 'taxonomy' => 'doc_category',
169 // 'parent' => 0,
170 // 'hide_empty' => true,
171 // 'meta_query' => [
172 // 'relation' => 'OR',
173 // [
174 // 'key' => 'doc_category_knowledge_base',
175 // 'value' => $query->query_vars['knowledge_base'],
176 // 'compare' => 'LIKE'
177 // ]
178 // ]
179 // ] );
180 // }
181 }
182
183 // dump( is_archive(), $query->query_vars );
184 }
185
186 /**
187 * Get docs orders by term_id.
188 *
189 * @since 2.5.0
190 * @param int $term_id
191 *
192 * @return array
193 */
194 public function get_docs_order_by_terms( $term_id ) {
195 global $wpdb;
196
197 // Get language-specific meta key with fallback
198 $meta_key = \WPDeveloper\BetterDocs\Utils\Helper::get_meta_key_with_fallback( '_docs_order', $term_id );
199 $_docs_order = get_term_meta( $term_id, $meta_key, true );
200
201 $query = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}term_relationships WHERE term_taxonomy_id = %d", $term_id );
202 $query_key = "docs_order_by_terms_{$term_id}_{$meta_key}_" . md5( $query );
203
204 if ( ( $results = $this->database->get_cache( $query_key ) ) !== false ) {
205 return $results;
206 }
207
208 if ( ! empty( $_docs_order ) ) {
209 $_docs_order = explode( ',', $_docs_order );
210 $new_ids = array();
211
212 $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
213
214 if ( is_array( $results ) && ! empty( $results ) ) {
215 $object_ids = array_filter(
216 $results,
217 function ( $value ) use ( $_docs_order ) {
218 return ! in_array( $value->object_id, $_docs_order );
219 }
220 );
221
222 if ( ! empty( $object_ids ) ) {
223 array_walk(
224 $object_ids,
225 function ( $value ) use ( &$new_ids ) {
226 $new_ids[ ] = $value->object_id;
227 }
228 );
229 }
230 }
231
232 $_docs_order = array_merge( $new_ids, $_docs_order );
233 $this->database->set_cache( $query_key, $_docs_order, 1 );
234
235 return $_docs_order;
236 }
237
238 return array();
239 }
240
241 /**
242 * For determine the next post ID
243 *
244 * @link https://developer.wordpress.org/reference/hooks/get_adjacent_post_where/
245 *
246 * @param mixed $where
247 * @param mixed $in_same_term
248 * @param mixed $excluded_terms
249 * @param mixed $taxonomy
250 * @param mixed $post
251 * @return mixed
252 */
253 public function next_post_where( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) {
254 return $this->get_adjacent_post_id( 'next', $where, $post, $taxonomy );
255 }
256
257 /**
258 * For determine the previous post ID
259 *
260 * @link https://developer.wordpress.org/reference/hooks/get_adjacent_post_where/
261 *
262 * @param mixed $where
263 * @param mixed $in_same_term
264 * @param mixed $excluded_terms
265 * @param mixed $taxonomy
266 * @param mixed $post
267 * @return mixed
268 */
269 public function previous_post_where( $where, $in_same_term, $excluded_terms, $taxonomy, $post ) {
270 return $this->get_adjacent_post_id( 'previous', $where, $post, $taxonomy );
271 }
272
273 /**
274 * Get where clause for next/previous post ID mainly used in post navigation.
275 *
276 * @see `previous_post_where` and `next_post_where` methods.
277 *
278 * @param mixed $adjacent
279 * @param mixed $where
280 * @param mixed $post
281 * @param mixed $taxonomy
282 * @return mixed
283 */
284 private function get_adjacent_post_id( $adjacent, $where, $post, $taxonomy ) {
285 if ( 'doc_category' !== $taxonomy ) {
286 return $where;
287 }
288
289 $_id = null;
290 $_terms = get_the_terms( $post->ID, 'doc_category' );
291 if ( empty( $_terms ) ) {
292 return $where;
293 }
294
295 global $wp_query, $wpdb;
296
297 $_docs_order = $this->get_docs_order_by_terms( $_terms[ 0 ]->term_id );
298
299 $_orderby = $this->settings->get( 'alphabetically_order_post', 'betterdocs_order' );
300 $_order = $this->settings->get( 'docs_order', 'ASC' );
301 $_docs_order = 'betterdocs_order' === $_orderby ? $_docs_order : array();
302
303 if ( empty( $_docs_order ) ) {
304 $statuses = array( 'publish' );
305
306 if ( current_user_can( 'read_private_docs' ) ) {
307 $statuses[ ] = 'private';
308 }
309
310 $_args = array(
311 'post_status' => $statuses,
312 'term_id' => $_terms[ 0 ]->term_id
313 );
314 if ( isset( $wp_query->query_vars[ 'doc_category' ] ) ) {
315 $_args[ 'tax_query' ][ ] = array(
316 'taxonomy' => 'doc_category',
317 'field' => 'slug',
318 'terms' => $wp_query->query_vars[ 'doc_category' ],
319 'operator' => 'AND',
320 'include_children' => false
321 );
322 } elseif ( isset( $wp_query->query_vars[ 'name' ] ) && isset( $wp_query->query_vars[ 'post_type' ] ) && 'docs' == $wp_query->query_vars[ 'post_type' ] ) {
323 $_post = get_page_by_path( $wp_query->query_vars[ 'name' ], OBJECT, 'docs' );
324 $doc_terms = array();
325
326 if ( isset( $_post->ID ) ) {
327 $terms = get_the_terms( $_post->ID, 'doc_category' );
328 if ( ! empty( $terms ) ) {
329 $doc_terms = wp_list_pluck( $terms, 'term_id' );
330 }
331 }
332
333 if ( ! empty( $doc_terms ) ) {
334 $_args[ 'tax_query' ][ ] = array(
335 'taxonomy' => 'doc_category',
336 'field' => 'term_id',
337 'terms' => $doc_terms,
338 'operator' => 'AND',
339 'include_children' => false
340 );
341 $_args[ 'term_id' ] = $doc_terms[ 0 ];
342 }
343 } else {
344 // Fallback: use the term from current post
345 $_args[ 'tax_query' ][ ] = array(
346 'taxonomy' => 'doc_category',
347 'field' => 'term_id',
348 'terms' => $_terms[ 0 ]->term_id,
349 'operator' => 'AND',
350 'include_children' => false
351 );
352 }
353
354 $_args[ 'orderby' ] = $_orderby;
355 if ( 'betterdocs_order' != $_orderby ) {
356 $_args[ 'order' ] = $_order;
357 }
358
359 /**
360 * Before Query
361 */
362 do_action_ref_array( 'betterdocs_navigation_docs_query', array( &$_args ) );
363 $docs = $this->get_posts( $this->docs_query_args( $_args ) );
364
365 $_docs = array();
366 if ( $docs->have_posts() ) {
367 array_map(
368 function ( $_post ) use ( &$_docs ) {
369 $_docs[ ] = $_post->ID;
370 },
371 $docs->posts
372 );
373 }
374
375 $_docs_order = $_docs;
376 }
377
378 $_docs_order = apply_filters( 'betterdocs_adjacent_docs_order', $_docs_order, $_terms );
379
380 // Ensure consistent type comparison by converting all IDs to integers
381 $_docs_order = array_map( 'intval', $_docs_order );
382 $_docs_order = array_values( $_docs_order ); // Re-index array after type conversion
383
384 $_id_index = array_search( (int) $post->ID, $_docs_order, true );
385
386 // If current post not found in order list, return original where clause
387 if ( false === $_id_index ) {
388 return $where;
389 }
390
391 $_id_index = 'next' === $adjacent ? $_id_index + 1 : $_id_index - 1;
392 $_id = isset( $_docs_order[ $_id_index ] ) ? (int) $_docs_order[ $_id_index ] : null;
393
394 // Fix: replace only ID comparison in where clause using regex
395 if ( $_id ) {
396 // This replaces any 'p.ID < N', 'p.ID > N', 'p.ID <= N', or 'p.ID >= N' etc, with 'p.ID = $_id'
397 $where = preg_replace( '/p\.ID\s*[<>!=]+\s*\d+/', 'p.ID = ' . (int) $_id, $where );
398 }
399
400 return $where;
401 }
402
403 public function parse_terms_args( $args = array() ) {
404 $_default_args = array(
405 'hide_empty' => true,
406 'taxonomy' => 'doc_category'
407 );
408
409 // OrderBy & Order
410 $_orderby = ! empty( $args[ 'orderby' ] ) && 1 != $args[ 'orderby' ] ? $args[ 'orderby' ] : 'name';
411 $_order = ! empty( $args[ 'order' ] ) ? $args[ 'order' ] : '';
412
413 if ( 'betterdocs_order' == $_orderby ) {
414 // Use base meta key - fallback logic will be handled in the terms_clauses filter
415 $args[ 'meta_key' ] = 'doc_category_order';
416 $args[ 'orderby' ] = 'meta_value_num';
417 $args[ 'order' ] = 'ASC';
418 } else {
419 $args[ 'orderby' ] = $_orderby;
420 $args[ 'order' ] = $_order;
421 }
422
423 // Nested Sub Category
424 // $args['parent'] = 0;
425 // if ( $nested_subcategory == true ) {
426 // }
427
428 if ( ! isset( $args[ 'number' ] ) ) {
429 global $wp_query;
430 if ( null === $wp_query->query || ( isset( $wp_query->query[ 'post_type' ] ) && 'docs' != $wp_query->query[ 'post_type' ] ) ) {
431 $args[ 'number' ] = 4;
432 }
433 }
434
435 // Includes
436 if ( isset( $args[ 'include' ] ) ) {
437 $_include = ! is_array( $args[ 'include' ] ) ? explode( ',', $args[ 'include' ] ) : $args[ 'include' ];
438 $args[ 'include' ] = $_include;
439 $args[ 'orderby' ] = 'include';
440
441 unset( $args[ 'parent' ] );
442 }
443
444 $_meta_query = ! empty( $args[ 'meta_query' ] ) ? $args[ 'meta_query' ] : array();
445 $args[ 'meta_query' ] = apply_filters( 'betterdocs_taxonomy_object_meta_query', $_meta_query, $args );
446
447 return apply_filters( 'betterdocs_category_terms_object', wp_parse_args( $args, $_default_args ), $args );
448 }
449
450 public function get_terms( $args ) {
451 $parsed_args = $this->parse_terms_args( $args );
452 $terms = get_terms( $parsed_args );
453
454 // Filter terms manually if we need to consider private docs for users with read_private_docs capability
455 if ( isset( $parsed_args[ '_betterdocs_filter_private' ] ) && $parsed_args[ '_betterdocs_filter_private' ] && current_user_can( 'read_private_docs' ) ) {
456 $terms = array_filter( $terms, function ( $term ) {
457 // Get the actual count including private docs for users with read_private_docs capability
458 $actual_count = $this->get_docs_count( $term, false );
459 return $actual_count > 0;
460 } );
461 }
462
463 return $terms;
464 }
465
466 public function get_child_terms( $args ) {
467 if ( ! isset( $args[ 'number' ] ) ) {
468 global $wp_query;
469 if ( null === $wp_query->query || ( isset( $wp_query->query[ 'post_type' ] ) && 'docs' != $wp_query->query[ 'post_type' ] ) ) {
470 $args[ 'number' ] = 1;
471 }
472 }
473
474 return $this->get_terms( $args );
475 }
476
477 /**
478 * Get POSTs of Docs type.
479 *
480 * @param mixed $args
481 * @return WP_Query
482 */
483 public function get_posts( $args, $ignore = false ) {
484 if ( ! $ignore ) {
485 $args = $this->docs_query_args( $args );
486 }
487
488 return new WP_Query( $args );
489 }
490
491 public function get_taxonomy( $tax = '' ) {
492 global $wp_query;
493 if ( is_tax( 'knowledge_base' ) ) {
494 $_tax = $wp_query->tax_query->queried_terms;
495 if ( array_key_exists( 'doc_category', $_tax ) ) {
496 $tax = 'doc_category';
497 } else {
498 $tax = 'knowledge_base';
499 }
500 } elseif ( is_tax( 'doc_category' ) ) {
501 $tax = 'doc_category';
502 }
503
504 return $tax;
505 }
506
507 public function terms_query( $args = array() ) {
508 global $wp_query;
509 $_origin_args = $args;
510
511 $default_args = array(
512 'hide_empty' => true,
513 'taxonomy' => 'doc_category',
514 'orderby' => 'name'
515 );
516
517 /**
518 * Number Set
519 *
520 * @FIX: If Built-in Docs page off and docs_page in use, then Terms Query Number 4|1 set.
521 */
522 // if ( $wp_query->query === NULL || ( isset( $wp_query->query['post_type'] ) && $wp_query->query['post_type'] != 'docs' ) ) {
523 // $default_args['number'] = 1;
524 // }
525
526 /**
527 * Nested Sub Category
528 */
529 if ( isset( $args[ 'nested_subcategory' ] ) && true == $args[ 'nested_subcategory' ] ) {
530 $default_args[ 'parent' ] = 0;
531 unset( $args[ 'nested_subcategory' ] );
532 // if ( $wp_query->query === NULL || ( isset( $wp_query->query['post_type'] ) && $wp_query->query['post_type'] != 'docs' ) ) {
533 // $default_args['number'] = 4;
534 // }
535 }
536
537 /**
538 * OrderBy and Order
539 */
540 if ( ! isset( $args[ 'orderby' ] ) ) {
541 $_orderby = $this->settings->get( 'terms_orderby', 'name' );
542 $_order = $this->settings->get( 'terms_order', '' );
543 } else {
544 $_orderby = $args[ 'orderby' ];
545 $_order = ! empty( $args[ 'order' ] ) ? $args[ 'order' ] : '';
546 }
547
548 if ( 'betterdocs_order' === $_orderby ) {
549 // Use different meta keys for different taxonomies
550 if ( isset( $args[ 'taxonomy' ] ) && 'knowledge_base' === $args[ 'taxonomy' ] ) {
551 $default_args[ 'meta_key' ] = 'kb_order';
552 } else {
553 $default_args[ 'meta_key' ] = 'doc_category_order';
554 }
555 $_orderby = 'meta_value_num';
556 $_order = 'ASC';
557 } elseif ( true === $_orderby ) {
558 $_orderby = 'name';
559 }
560
561 $args[ 'orderby' ] = $_orderby;
562 if ( ! empty( $_order ) ) {
563 $args[ 'order' ] = $_order;
564 }
565
566 /**
567 * @todo old hook
568 * hook: betterdocs_child_taxonomy_meta_query
569 */
570 $_multiple_kb = apply_filters(
571 'betterdocs_query_args_multiple_kb_enabled',
572 isset( $args[ 'multiple_kb' ] ) ? (bool) $args[ 'multiple_kb' ] : false,
573 $_origin_args
574 );
575
576 $_kb_slug = isset( $args[ 'kb_slug' ] ) ? trim( $args[ 'kb_slug' ] ) : '';
577
578 unset( $args[ 'multiple_kb' ] );
579 unset( $args[ 'kb_slug' ] );
580
581 $meta_query = ! empty( $args[ 'meta_query' ] ) ? $args[ 'meta_query' ] : array();
582 $meta_query = apply_filters( 'betterdocs_terms_meta_query_args', $meta_query, $_multiple_kb, $_kb_slug, $_origin_args );
583
584 if ( ! empty( $meta_query ) ) {
585 $default_args[ 'meta_query' ] = $meta_query;
586 }
587
588 if ( ! empty( $args[ 'terms' ] ) ) {
589 $args[ 'include' ] = explode( ',', $args[ 'terms' ] );
590 $args[ 'orderby' ] = 'include';
591 $args[ 'order' ] = 'ASC';
592
593 unset( $default_args[ 'parent' ] );
594 unset( $args[ 'parent' ] );
595 unset( $args[ 'terms' ] );
596 }
597
598 $_query_args = wp_parse_args( $args, $default_args );
599 $_query_args = apply_filters( 'betterdocs_terms_query_args', $_query_args, $_origin_args );
600
601 // Apply private docs logic for logged-in users
602 $_query_args = $this->modify_terms_args_for_private_docs( $_query_args );
603
604 return $_query_args;
605 }
606
607 public function get_term_parents( $term_id, $taxonomy = 'doc_category', $args = array() ) {
608 $term = get_term( $term_id, $taxonomy );
609 if ( is_wp_error( $term ) ) {
610 return $term;
611 }
612
613 if ( ! $term || ! isset( $term->term_id ) ) {
614 return array();
615 }
616
617 $_lists = array();
618 $origin_term = $term_id;
619 $term_id = $term->term_id;
620
621 $defaults = array(
622 'format' => 'name',
623 'inclusive' => true
624 );
625
626 $args = wp_parse_args( $args, $defaults );
627
628 $args[ 'inclusive' ] = wp_validate_boolean( $args[ 'inclusive' ] );
629
630 $parents = get_ancestors( $term_id, $taxonomy );
631
632 if ( $args[ 'inclusive' ] ) {
633 array_unshift( $parents, $term_id );
634 }
635
636 foreach ( array_reverse( $parents ) as $term_id ) {
637 $parent = get_term( $term_id, $taxonomy );
638 $name = ( 'slug' === $args[ 'format' ] ) ? $parent->slug : $parent->name;
639 $term_permalink = get_term_link( $parent->term_id, $taxonomy );
640 $term_permalink = apply_filters( 'betterdocs_breadcrumb_term_permalink', $term_permalink, $term_id );
641
642 $_item = array(
643 'url' => $term_permalink,
644 'text' => $name
645 );
646
647 $_lists[ ] = $_item;
648 }
649
650 return apply_filters( 'betterdocs_breadcrumb_archive_lists', $_lists, $origin_term );
651 }
652
653 /**
654 * Get all non-empty child term IDs recursively for a given taxonomy and parent term.
655 *
656 * This function retrieves all child terms for a specified taxonomy and parent term,
657 * recursively fetching child terms of child terms, and returns only those with a non-zero post count.
658 *
659 * @param string $taxonomy The taxonomy name (e.g., 'doc_category').
660 * @param int $parent_id The ID of the parent term to start retrieving children from.
661 *
662 * @return array An array of non-empty child term IDs.
663 */
664 public function get_all_child_term_ids( $taxonomy, $parent_id ) {
665 // Set up the arguments for retrieving child terms
666 $args = apply_filters(
667 'betterdocs_get_child_term_ids_args',
668 array(
669 'taxonomy' => $taxonomy,
670 'parent' => $parent_id,
671 'hide_empty' => true,
672 'fields' => 'ids'
673 )
674 );
675
676 // Get the terms based on the arguments
677 $terms = get_terms( $args );
678
679 // Initialize an empty array to hold non-empty child term IDs
680 $non_empty_children = array();
681
682 // Check if terms were retrieved without errors and that the result isn't empty
683 if ( ! is_wp_error( $terms ) && ! empty( $terms ) ) {
684 $term_ids = array();
685
686 // Loop through each term ID
687 foreach ( $terms as $term_id ) {
688 // Add the current term ID to the term_ids array
689 $term_ids[ ] = $term_id;
690
691 // Recursively get child terms for the current term
692 $child_term_ids = $this->get_all_child_term_ids( $taxonomy, $term_id );
693
694 // If there are child terms, merge them into the term_ids array
695 if ( ! empty( $child_term_ids ) ) {
696 $term_ids = array_merge( $term_ids, $child_term_ids );
697 }
698 }
699
700 // Loop through all retrieved term IDs to filter out empty ones
701 foreach ( $term_ids as $term_id ) {
702 // Get the term object for the current term ID
703 $child_term = get_term( $term_id, $taxonomy );
704
705 // Only include terms that have a non-zero post count
706 if ( $child_term && isset( $child_term->term_id ) && $child_term->count > 0 ) {
707 $non_empty_children[ ] = $child_term->term_id;
708 }
709 }
710 }
711
712 // Return the final array of non-empty child term IDs
713 return $non_empty_children;
714 }
715
716 /**
717 * Get all nested child term IDs of a specific parent term in a taxonomy and return as a comma-separated string.
718 *
719 * @param string $taxonomy The taxonomy.
720 * @param int $parent_id The parent term ID.
721 * @return string The comma-separated term IDs.
722 */
723 public function get_child_term_ids_by_parent_id( $taxonomy, $parent_id ) {
724 $term_ids = $this->get_all_child_term_ids( $taxonomy, $parent_id );
725 return implode( ',', $term_ids );
726 }
727
728 public function get_terms_children( $taxonomy, $parent_id ) {
729 $children = get_term_children( $parent_id, $taxonomy );
730 $non_empty_children = array();
731 if ( ! is_wp_error( $children ) && ! empty( $children ) ) {
732 foreach ( $children as $child_id ) {
733 $child_term = get_term( $child_id, $taxonomy );
734
735 // Only include non-empty terms
736 if ( $child_term && $child_term->count > 0 ) {
737 $non_empty_children[ ] = $child_term;
738 }
739 }
740 }
741
742 return $non_empty_children;
743 }
744
745 public function get_terms_children_ids( $taxonomy, $parent_id ) {
746 $children = $this->get_terms_children( $taxonomy, $parent_id );
747 $term_ids = wp_list_pluck( $children, 'term_id' );
748
749 return implode( ',', $term_ids );
750 }
751
752 public function count_terms_children( $taxonomy, $parent_id ) {
753 return count( $this->get_terms_children( $taxonomy, $parent_id ) );
754 }
755
756 public function is_inner_templates() {
757 if ( is_tax( 'knowledge_base' ) || is_tax( 'doc_category' ) || is_tax( 'doc_tag' ) || is_singular( 'docs' ) ) {
758 return true;
759 }
760 return false;
761 }
762
763 /**
764 * Summary of docs_query_args
765 * @param mixed $args
766 * @throws \Exception
767 * @return mixed
768 */
769 public function docs_query_args( $args, $filter = array() ) {
770 $_origin_args = $args;
771
772 $default_args = array(
773 'post_type' => 'docs'
774 );
775
776 if ( ! empty( $args[ 'post_type' ] ) && trim( $args[ 'post_type' ] ) === 'docs_any' ) {
777 $default_args[ 'post_type' ] = 'docs';
778 $default_args[ 'post_status' ] = 'any';
779
780 unset( $args[ 'post_type' ] );
781 }
782
783 /**
784 * OrderBy and Order
785 */
786 if ( ! isset( $args[ 'orderby' ] ) ) {
787 $_orderby = $this->settings->get( 'alphabetically_order_post' );
788 $_order = $this->settings->get( 'docs_order', 'ASC' );
789 } else {
790 $_orderby = $args[ 'orderby' ];
791 $_order = ! empty( $args[ 'order' ] ) ? $args[ 'order' ] : 'ASC';
792 }
793
794 if ( 'betterdocs_order' != $_orderby ) {
795 if ( true === $_orderby ) {
796 $args[ 'orderby' ] = 'title';
797 } else {
798 $args[ 'orderby' ] = $_orderby;
799 }
800
801 $args[ 'order' ] = $_order;
802 } elseif ( 'betterdocs_order' == $_orderby ) {
803 unset( $args[ 'orderby' ] );
804 }
805
806 if ( empty( $args[ 'orderby' ] ) ) {
807 unset( $args[ 'order' ] );
808 }
809
810 /**
811 * Term ID
812 */
813 $_term_id = null;
814 if ( ! empty( $args[ 'term_id' ] ) ) {
815 $_term_id = intval( $args[ 'term_id' ] );
816 unset( $args[ 'term_id' ] );
817 }
818
819 // if ( $_term_id == null ) {
820 // throw new \Exception( __( '$args["term_id"] cannot be null.', 'betterdocs' ) );
821 // }
822
823 /**
824 * Term Slug for tax_query
825 */
826 $_term_slug = '';
827 if ( ! empty( $args[ 'term_slug' ] ) ) {
828 $_term_slug = trim( $args[ 'term_slug' ] );
829 unset( $args[ 'term_slug' ] );
830 }
831
832 /**
833 * @todo old hook
834 * hook: betterdocs_cat_template_multikb
835 */
836
837 $_multiple_kb = apply_filters(
838 'betterdocs_enable_multiple_knowledge_base',
839 isset( $args[ 'multiple_kb' ] ) ? (bool) $args[ 'multiple_kb' ] : false,
840 $_origin_args
841 );
842
843 $_kb_slug = isset( $args[ 'kb_slug' ] ) ? trim( $args[ 'kb_slug' ] ) : '';
844
845 unset( $args[ 'multiple_kb' ] );
846 unset( $args[ 'kb_slug' ] );
847
848 $tax_query = array(
849 array(
850 'taxonomy' => 'doc_category',
851 'field' => 'slug',
852 'terms' => $_term_slug,
853 'operator' => 'AND',
854 'include_children' => false
855 )
856 );
857
858 if ( ! isset( $args[ 'orderby' ] ) || 'betterdocs_order' == $args[ 'orderby' ] ) {
859 $args[ 'orderby' ] = 'post__in';
860 $args[ 'post__in' ] = $this->get_docs_order_by_terms( $_term_id );
861 }
862
863 if ( isset( $args[ 'tax_query' ] ) ) {
864 $tax_query = $args[ 'tax_query' ];
865 }
866
867 $args[ 'tax_query' ] = apply_filters(
868 'betterdocs_docs_tax_query_args',
869 $tax_query,
870 $_multiple_kb,
871 $_term_slug,
872 $_kb_slug,
873 $_origin_args,
874 $this->is_inner_templates()
875 );
876 /**
877 * Final parse args
878 */
879 $args = wp_parse_args( $args, $default_args );
880
881 if ( ! empty( $filter ) ) {
882 $filter = array_flip( $filter );
883 $args = array_filter(
884 $args,
885 function ( $item ) use ( $filter ) {
886 return ! array_key_exists( $item, $filter );
887 },
888 ARRAY_FILTER_USE_KEY
889 );
890 }
891
892 $final_args = apply_filters( 'betterdocs_articles_args', $args, $_term_id, $_origin_args );
893
894 // Process betterdocs_order AFTER all filters have run
895 if ( isset( $final_args[ 'orderby' ] ) && 'betterdocs_order' == $final_args[ 'orderby' ] ) {
896 $docs_order = $this->get_docs_order_by_terms( $_term_id );
897
898 if ( ! empty( $docs_order ) ) {
899 $final_args[ 'orderby' ] = 'post__in';
900 $final_args[ 'post__in' ] = $docs_order;
901 } else {
902 $final_args[ 'orderby' ] = 'menu_order';
903 $final_args[ 'order' ] = 'ASC';
904 }
905 }
906
907 return $final_args;
908 }
909
910 public function faq_terms_query_args( $includes = '', $excludes = '', $args = array() ) {
911 $_args = array(
912 'taxonomy' => 'betterdocs_faq_category',
913 'meta_key' => 'order',
914 'orderby' => 'meta_value_num',
915 'order' => 'ASC',
916 'include' => $includes,
917 'exclude' => $excludes,
918 'meta_query' => array(
919 array(
920 'key' => 'status',
921 'value' => 1,
922 'compare' => '=='
923 )
924 )
925 );
926
927 if ( 'all' == $_args[ 'include' ] ) {
928 unset( $_args[ 'include' ] );
929 unset( $_args[ 'exclude' ] );
930 }
931
932 if ( empty( $_args[ 'exclude' ] ) ) {
933 unset( $_args[ 'exclude' ] );
934 }
935
936 if ( empty( $_args[ 'include' ] ) ) {
937 unset( $_args[ 'include' ] );
938 }
939
940 return wp_parse_args( $args, $_args );
941 }
942
943 public function get_faq_by_term( $term_id ) {
944 global $wpdb;
945
946 $args = array(
947 'post_type' => 'betterdocs_faq',
948 'post_status' => 'publish',
949 'tax_query' => array(
950 array(
951 'taxonomy' => 'betterdocs_faq_category',
952 'field' => 'term_id',
953 'terms' => $term_id,
954 'operator' => 'AND'
955 )
956 ),
957 'posts_per_page' => -1
958 );
959
960 $args[ 'orderby' ] = 'post__in';
961 $args[ 'post__in' ] = $this->get_faq_orders( $term_id );
962
963 return new WP_Query( $args );
964 }
965
966 public function get_faq_orders( $term_id = null ) {
967 global $wpdb;
968 $faq_order = get_term_meta( $term_id, '_betterdocs_faq_order', true );
969 $faq_order = explode( ',', $faq_order );
970
971 $query = $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}term_relationships WHERE term_taxonomy_id = %d", $term_id );
972 $query_key = 'betterdocs_faq_order_' . md5( $query );
973
974 if ( ( $results = $this->database->get_cache( $query_key ) ) !== false ) {
975 return $results;
976 }
977
978 if ( ! empty( $faq_order ) ) {
979 $new_ids = array();
980 $results = $wpdb->get_results( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
981
982 if ( ! is_null( $results ) && ! empty( $results ) && is_array( $results ) ) {
983 $object_ids = array_filter(
984 $results,
985 function ( $value ) use ( $faq_order ) {
986 return ! in_array( $value->object_id, $faq_order );
987 }
988 );
989
990 if ( ! empty( $object_ids ) ) {
991 array_walk(
992 $object_ids,
993 function ( $value ) use ( &$new_ids ) {
994 $new_ids[ ] = $value->object_id;
995 }
996 );
997 }
998 }
999
1000 $faq_order = array_merge( $new_ids, $faq_order );
1001 }
1002
1003 $this->database->set_cache( $query_key, $faq_order, 1 );
1004
1005 return $faq_order;
1006 }
1007
1008 public function get_faq_terms( $terms = array() ) {
1009 $_terms = get_terms(
1010 array(
1011 'taxonomy' => 'betterdocs_faq_category',
1012 'hide_empty' => true,
1013 'orderby' => 'name',
1014 'order' => 'ASC',
1015 'meta_query' => array(
1016 array(
1017 'key' => 'status',
1018 'value' => 1,
1019 'compare' => '=='
1020 )
1021 )
1022 )
1023 );
1024
1025 if ( ! is_wp_error( $_terms ) ) {
1026 foreach ( $_terms as $term ) {
1027 $terms[ $term->term_id ] = $term->name;
1028 }
1029 }
1030
1031 return $terms;
1032 }
1033
1034 public function get_doc_terms( $terms = array() ) {
1035 $_terms = get_terms(
1036 array(
1037 'taxonomy' => 'doc_category',
1038 'hide_empty' => true,
1039 'orderby' => 'name',
1040 'order' => 'ASC'
1041 )
1042 );
1043
1044 if ( ! is_wp_error( $_terms ) ) {
1045 foreach ( $_terms as $term ) {
1046 $terms[ $term->term_id ] = $term->name;
1047 }
1048 }
1049
1050 return $terms;
1051 }
1052
1053 public function get_docs_count( $term, $nested_subcategory = false, $args = array() ) {
1054 // Validate term object
1055 if ( ! is_object( $term ) ) {
1056 return 0;
1057 }
1058
1059 $counts = isset( $term->count ) ? $term->count : 0;
1060
1061 if ( false == $nested_subcategory ) {
1062 // For non-nested categories, we need to recalculate counts based on user capabilities
1063 // Only proceed if we have a valid term with required properties
1064 if ( isset( $term->term_id ) && isset( $term->taxonomy ) && is_numeric( $term->term_id ) ) {
1065 // Get all post IDs for this term
1066 $post_ids = get_objects_in_term( $term->term_id, $term->taxonomy );
1067
1068 if ( ! empty( $post_ids ) ) {
1069 if ( current_user_can( 'read_private_docs' ) ) {
1070 // For users with read_private_docs capability, include both private and public posts
1071 $filtered_post_ids = array_filter( $post_ids, function ( $post_id ) {
1072 $post_status = get_post_status( $post_id );
1073 return 'private' === $post_status || is_post_publicly_viewable( $post_id );
1074 } );
1075 } else {
1076 // For users without read_private_docs capability, only include public posts
1077 $filtered_post_ids = array_filter( $post_ids, function ( $post_id ) {
1078 return is_post_publicly_viewable( $post_id );
1079 } );
1080 }
1081
1082 $counts = count( $filtered_post_ids );
1083 } else {
1084 $counts = 0;
1085 }
1086 }
1087
1088 return apply_filters( 'betterdocs_docs_count', $counts, $term, $nested_subcategory, $args );
1089 }
1090
1091 $_child_terms_docs_ids = $this->get_doc_ids_by_term( $term, null, $nested_subcategory );
1092 if ( is_array( $_child_terms_docs_ids ) ) {
1093 $counts = count( $_child_terms_docs_ids );
1094 }
1095
1096 return apply_filters( 'betterdocs_docs_count', $counts, $term, $nested_subcategory, $args );
1097 }
1098
1099 public function get_doc_ids_by_term( $term, $optional = null, $nested_subcategory = false ) {
1100 // Check if term has required properties and is a valid object
1101 if ( ! is_object( $term ) || ! isset( $term->term_id ) || ! isset( $term->taxonomy ) || ! is_numeric( $term->term_id ) ) {
1102 return false;
1103 }
1104
1105 $args = array( 'include' => $term->term_id );
1106 if ( $nested_subcategory ) {
1107 $args[ 'child_of' ] = $term->term_id;
1108 unset( $args[ 'include' ] );
1109 }
1110 $_child_terms = get_terms( $term->taxonomy, $args );
1111
1112 if ( ! is_array( $_child_terms ) ) {
1113 return false;
1114 }
1115
1116 array_unshift( $_child_terms, $term );
1117
1118 $_child_terms_ids = array_column( $_child_terms, 'term_id' );
1119 $_child_terms_taxs = array_column( $_child_terms, 'taxonomy' );
1120 $_child_terms_docs_ids = get_objects_in_term( $_child_terms_ids, $_child_terms_taxs );
1121
1122 if ( null !== $optional ) {
1123 $_optional_doc_ids = get_objects_in_term( $optional->term_id, $optional->taxonomy );
1124 $_child_terms_docs_ids = array_intersect( $_child_terms_docs_ids, $_optional_doc_ids );
1125 }
1126
1127 return array_filter( $_child_terms_docs_ids, function ( $doc_id ) {
1128 if ( ! current_user_can( 'read_private_docs' ) ) {
1129 return is_post_publicly_viewable( $doc_id );
1130 }
1131
1132 $_status = get_post_status( $doc_id );
1133 return 'private' == $_status || is_post_publicly_viewable( $doc_id );
1134 } );
1135 }
1136
1137 /**
1138 * Get the common query arguments for WP_Query.
1139 *
1140 * @param string $terms The taxonomy.
1141 * @param string $term_slug The taxonomy term slug.
1142 * @param array $additional_args Additional arguments to merge with the common arguments.
1143 * @return array The query arguments.
1144 */
1145 private function tax_query_args( $terms, $term_slug, $additional_args = array() ) {
1146 $common_args = array(
1147 'post_type' => 'docs',
1148 'post_status' => 'publish',
1149 'tax_query' => array(
1150 array(
1151 'taxonomy' => $terms,
1152 'field' => 'slug',
1153 'terms' => $term_slug
1154 )
1155 )
1156 );
1157 return array_merge( $common_args, $additional_args );
1158 }
1159
1160 /**
1161 * Get the latest updated date for a specific taxonomy term.
1162 *
1163 * @param string $terms The taxonomy.
1164 * @param string $term_slug The taxonomy term slug.
1165 * @return string|null The latest modified date or null if no posts found.
1166 */
1167 public function latest_updated_date( $terms, $term_slug ) {
1168 $args = $this->tax_query_args(
1169 $terms,
1170 $term_slug,
1171 array(
1172 'posts_per_page' => 1,
1173 'orderby' => 'modified',
1174 'order' => 'DESC'
1175 )
1176 );
1177
1178 $query = new WP_Query( $args );
1179
1180 if ( $query->have_posts() ) {
1181 while ( $query->have_posts() ) {
1182 $query->the_post();
1183 $latest_post = get_the_modified_date();
1184 wp_reset_postdata();
1185 return $latest_post;
1186 }
1187 } else {
1188 return null;
1189 }
1190 }
1191
1192 /**
1193 * Check if there are any new posts within the last 7 days for a specific taxonomy term.
1194 *
1195 * @param string $terms The taxonomy.
1196 * @param string $term_slug The taxonomy term slug.
1197 * @return bool True if there are new posts, false otherwise.
1198 */
1199 public function check_new_posts( $terms, $term_slug ) {
1200 $date_7_days_ago = date( 'Y-m-d H:i:s', strtotime( '-7 days' ) );
1201
1202 $args = $this->tax_query_args(
1203 $terms,
1204 $term_slug,
1205 array(
1206 'posts_per_page' => 1,
1207 'orderby' => 'modified',
1208 'order' => 'DESC',
1209 'date_query' => array(
1210 array(
1211 'after' => $date_7_days_ago,
1212 'inclusive' => true
1213 )
1214 )
1215 )
1216 );
1217
1218 $query = new WP_Query( $args );
1219
1220 $has_new_posts = $query->have_posts();
1221
1222 wp_reset_postdata();
1223
1224 return $has_new_posts;
1225 }
1226
1227 public function insert_search_keyword( $search_input, $input_not_found ) {
1228 if ( empty( $search_input ) ) {
1229 return false;
1230 }
1231
1232 global $wpdb;
1233
1234 // Use BINARY comparison to avoid collation mismatch errors
1235 // This works across all character sets (latin1, utf8, utf8mb4, etc.)
1236 $search = $wpdb->get_results(
1237 $wpdb->prepare(
1238 "SELECT *
1239 FROM {$wpdb->prefix}betterdocs_search_keyword
1240 WHERE BINARY keyword = %s",
1241 $search_input
1242 )
1243 );
1244
1245 if ( ! empty( $search ) ) {
1246 $search_log = $wpdb->get_results(
1247 $wpdb->prepare(
1248 "SELECT *
1249 FROM {$wpdb->prefix}betterdocs_search_log
1250 WHERE created_at = %s AND keyword_id = %d",
1251 date( 'Y-m-d' ),
1252 $search[ 0 ]->id
1253 )
1254 );
1255
1256 if ( ! empty( $search_log ) ) {
1257 if ( ! empty( $input_not_found ) ) {
1258 $tbl_field = 'not_found_count';
1259 $count = $search_log[ 0 ]->not_found_count + 1;
1260 } else {
1261 $tbl_field = 'count';
1262 $count = $search_log[ 0 ]->count + 1;
1263 }
1264 // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
1265 $insert = $wpdb->query(
1266 $wpdb->prepare(
1267 "UPDATE {$wpdb->prefix}betterdocs_search_log
1268 SET " . $tbl_field . ' = ' . $count . '
1269 WHERE created_at = %s AND keyword_id = %d',
1270 $search_log[ 0 ]->created_at,
1271 $search_log[ 0 ]->keyword_id
1272 )
1273 );
1274 } else {
1275 if ( ! empty( $input_not_found ) ) {
1276 $count = 0;
1277 $not_found_count = 1;
1278 } else {
1279 $count = 1;
1280 $not_found_count = 0;
1281 }
1282 $insert = $wpdb->query(
1283 $wpdb->prepare(
1284 "INSERT INTO {$wpdb->prefix}betterdocs_search_log
1285 ( keyword_id, count, not_found_count, created_at )
1286 VALUES ( %d, %d, %d, %s )",
1287 array(
1288 $search[ 0 ]->id,
1289 $count,
1290 $not_found_count,
1291 date( 'Y-m-d' )
1292 )
1293 )
1294 );
1295 }
1296 } else {
1297 $insert = $wpdb->query(
1298 $wpdb->prepare(
1299 "INSERT INTO {$wpdb->prefix}betterdocs_search_keyword
1300 ( keyword )
1301 VALUES ( %s )",
1302 array(
1303 $search_input
1304 )
1305 )
1306 );
1307
1308 if ( $insert ) {
1309 if ( ! empty( $input_not_found ) ) {
1310 $count = 0;
1311 $not_found_count = 1;
1312 } else {
1313 $count = 1;
1314 $not_found_count = 0;
1315 }
1316 $insert = $wpdb->query(
1317 $wpdb->prepare(
1318 "INSERT INTO {$wpdb->prefix}betterdocs_search_log
1319 ( keyword_id, count, not_found_count, created_at )
1320 VALUES ( %d, %d, %d, %s )",
1321 array(
1322 $wpdb->insert_id,
1323 $count,
1324 $not_found_count,
1325 date( 'Y-m-d' )
1326 )
1327 )
1328 );
1329 }
1330 }
1331 return $insert;
1332 }
1333
1334 /**
1335 * Counts the number of 'doc_category' terms assigned to a specific 'knowledge_base' by slug.
1336 *
1337 * This method retrieves all terms in the 'doc_category' taxonomy and checks the term meta
1338 * 'doc_category_knowledge_base' to determine how many 'doc_category' terms are associated
1339 * with a given 'knowledge_base' slug.
1340 *
1341 * @param string $knowledge_base_slug The slug of the 'knowledge_base' to count the assignments for.
1342 *
1343 * @return int The count of 'doc_category' terms assigned to the specified 'knowledge_base'.
1344 */
1345 public function count_doc_categories_for_knowledge_base( $knowledge_base_slug ) {
1346 $count = 0;
1347
1348 $doc_categories = get_terms(
1349 array(
1350 'taxonomy' => 'doc_category',
1351 'hide_empty' => true
1352 )
1353 );
1354
1355 if ( ! empty( $doc_categories ) && ! is_wp_error( $doc_categories ) ) {
1356 foreach ( $doc_categories as $term ) {
1357 $meta_value = get_term_meta( $term->term_id, 'doc_category_knowledge_base', true );
1358
1359 if ( $meta_value ) {
1360 $knowledge_bases = maybe_unserialize( $meta_value );
1361
1362 // Check if the specific knowledge base slug is present in the meta value array
1363 if ( is_array( $knowledge_bases ) && in_array( $knowledge_base_slug, $knowledge_bases ) ) {
1364 ++$count;
1365 }
1366 }
1367 }
1368 }
1369
1370 return $count;
1371 }
1372 }
1373