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

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