PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.9.1
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.9.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 3.9.1, at includes/Core/Query.php

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