PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.5.9
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.5.9
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 / FAQBuilder.php

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

656 lines 23.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 WP_Error;
7 use WPDeveloper\BetterDocs\Utils\Base;
8
9 class FAQBuilder extends Base {
10 /**
11 * REST API namespace
12 * @var string
13 */
14 private $namespace = 'betterdocs';
15 public $post_type = 'betterdocs_faq';
16 public $category = 'betterdocs_faq_category';
17
18 /**
19 *
20 * Initialize the class and start calling our hooks and filters
21 *
22 * @since 1.0.0
23 *
24 */
25 public function __construct() {
26 // assign default admin capabilities for docs, doc terms, doc tags, knowledge base
27 add_action( 'init', [$this, 'register_post'] );
28 // fires after a new betterdocs_faq_category is created
29 add_action( 'created_betterdocs_faq_category', [$this, 'action_created_betterdocs_faq_category'], 10, 2 );
30 add_action( 'rest_api_init', [$this, 'register_api_endpoint'] );
31 add_action('rest_betterdocs_faq_category_query', array($this, 'faq_category_orderby_meta'), 10, 2);
32 // Enqueue Scripts
33 add_action( 'admin_enqueue_scripts', [$this, 'enqueue'] );
34 }
35
36 public function enqueue( $hook ) {
37 if ( $hook === 'betterdocs_page_betterdocs-faq' ) {
38 betterdocs()->assets->enqueue( 'betterdocs-admin-faq', 'admin/css/faq.css' );
39 betterdocs()->assets->enqueue( 'betterdocs-admin-faq', 'admin/js/faq.js' );
40
41 // removing emoji support
42 remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
43 remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
44
45 betterdocs()->assets->localize( 'betterdocs-admin-faq', 'betterdocs', [
46 'dir_url' => BETTERDOCS_ABSURL,
47 'rest_url' => esc_url_raw( rest_url() ),
48 'free_version' => betterdocs()->version,
49 'nonce' => wp_create_nonce( 'wp_rest' )
50 ] );
51 }
52 }
53
54 public function output() {
55 betterdocs()->views->get( 'admin/faq-builder' );
56 }
57
58 /**
59 *
60 * Register post type and taxonomies
61 *
62 * @since 1.0.0
63 *
64 */
65 public function register_post() {
66 /**
67 * Register category taxonomy
68 */
69 $category_labels = [
70 'name' => __( 'FAQ Categories', 'betterdocs' ),
71 'singular_name' => __( 'FAQ Category', 'betterdocs' ),
72 'all_items' => __( 'FAQ Categories', 'betterdocs' ),
73 'parent_item' => __( 'Parent FAQ Category', 'betterdocs' ),
74 'parent_item_colon' => __( 'Parent FAQ Category:', 'betterdocs' ),
75 'edit_item' => __( 'Edit Category', 'betterdocs' ),
76 'update_item' => __( 'Update Category', 'betterdocs' ),
77 'add_new_item' => __( 'Add New FAQ Category', 'betterdocs' ),
78 'new_item_name' => __( 'New FAQ Category Name', 'betterdocs' ),
79 'menu_name' => __( 'Categories', 'betterdocs' )
80 ];
81
82 $category_args = [
83 'hierarchical' => true,
84 'public' => false,
85 'labels' => $category_labels,
86 'show_ui' => true,
87 'show_admin_column' => true,
88 'query_var' => true,
89 'show_in_rest' => true,
90 'has_archive' => false,
91 'rewrite' => false,
92 'capabilities' => [
93 'manage_terms' => 'manage_doc_terms',
94 'edit_terms' => 'edit_doc_terms',
95 'delete_terms' => 'delete_doc_terms',
96 'assign_terms' => 'edit_docs'
97 ]
98 ];
99
100 register_taxonomy( $this->category, [$this->post_type], $category_args );
101 register_term_meta( $this->category, 'order', ['show_in_rest' => true] );
102 register_term_meta( $this->category, 'status', ['show_in_rest' => true] );
103 register_term_meta( $this->category, '_betterdocs_faq_order', ['show_in_rest' => true] );
104
105 /**
106 * Register post type
107 */
108 $labels = [
109 'name' => __( 'BetterDocs FAQ', 'betterdocs' ),
110 'singular_name' => __( 'BetterDocs FAQ', 'betterdocs' ),
111 'menu_name' => __( 'FAQ', 'betterdocs' ),
112 'name_admin_bar' => __( 'FAQ', 'betterdocs' ),
113 'add_new' => __( 'Add New', 'betterdocs' ),
114 'add_new_item' => __( 'Add New FAQ', 'betterdocs' ),
115 'new_item' => __( 'New FAQ', 'betterdocs' ),
116 'edit_item' => __( 'Edit FAQ', 'betterdocs' ),
117 'view_item' => __( 'View FAQ', 'betterdocs' ),
118 'all_items' => __( 'All FAQ', 'betterdocs' ),
119 'search_items' => __( 'Search FAQ', 'betterdocs' ),
120 'parent_item_colorn' => null,
121 'not_found' => __( 'No FAQ found', 'betterdocs' ),
122 'not_found_in_trash' => __( 'No FAQ found in trash', 'betterdocs' )
123 ];
124
125 $args = [
126 'labels' => $labels,
127 'description' => __( 'Add new faq from here', 'betterdocs' ),
128 'public' => false,
129 'public_queryable' => true,
130 'exclude_from_search' => false,
131 'show_ui' => true,
132 'show_in_menu' => false,
133 'query_var' => true,
134 'capability_type' => ['doc', 'docs'],
135 'hierarchical' => true,
136 'map_meta_cap' => true,
137 'has_archive' => false,
138 'rewrite' => false,
139 'show_in_rest' => true,
140 'menu_icon' => betterdocs()->assets->icon( 'betterdocs-icon-white.svg' ),
141 'supports' => ['title', 'editor', 'thumbnail', 'excerpt', 'author', 'revisions', 'custom-fields', 'comments']
142 ];
143
144 register_post_type( $this->post_type, $args );
145 }
146
147 /**
148 * Default the taxonomy's terms' order if it's not set.
149 *
150 * @param string $tax_slug The taxonomy's slug.
151 */
152 public function action_created_betterdocs_faq_category( $term_id ) {
153 $order = $this->get_max_taxonomy_order( 'betterdocs_faq_category' );
154 update_term_meta( $term_id, 'order', $order++ );
155 update_term_meta( $term_id, 'status', 1 );
156 }
157
158 /**
159 * Default the taxonomy's terms' order if it's not set.
160 *
161 * @param string $tax_slug The taxonomy's slug.
162 */
163 public function default_term_order( $tax_slug ) {
164 $terms = get_terms( $tax_slug, ['hide_empty' => false] );
165 $order = $this->get_max_taxonomy_order( $tax_slug );
166
167 foreach ( $terms as $term ) {
168 if ( ! get_term_meta( $term->term_id, 'order', true ) ) {
169 update_term_meta( $term->term_id, 'order', $order );
170 $order++;
171 }
172 }
173 }
174
175 /**
176 * Get the maximum order for this taxonomy. This will be applied to terms that don't have a tax position.
177 */
178 private function get_max_taxonomy_order( $tax_slug ) {
179 global $wpdb;
180
181 $max_term_order = $wpdb->get_col(
182 $wpdb->prepare(
183 "SELECT MAX( CAST( tm.meta_value AS UNSIGNED ) )
184 FROM $wpdb->terms t
185 JOIN $wpdb->term_taxonomy tt ON t.term_id = tt.term_id AND tt.taxonomy = '%s'
186 JOIN $wpdb->termmeta tm ON tm.term_id = t.term_id WHERE tm.meta_key = 'order'",
187 $tax_slug
188 )
189 );
190
191 $max_term_order = is_array( $max_term_order ) ? current( $max_term_order ) : 0;
192
193 return (int) $max_term_order === 0 || empty( $max_term_order ) ? 1 : (int) $max_term_order + 1;
194 }
195
196 /**
197 * Re-Order the taxonomies based on the order value.
198 *
199 * @param array $pieces Array of SQL query clauses.
200 * @param array $taxonomies Array of taxonomy names.
201 * @param array $args Array of term query args.
202 */
203 public function set_tax_order( $pieces, $taxonomies, $args ) {
204 foreach ( $taxonomies as $taxonomy ) {
205 global $wpdb;
206
207 if ( $taxonomy === 'betterdocs_faq_category' ) {
208 $join_statement = " LEFT JOIN $wpdb->termmeta AS term_meta ON t.term_id = term_meta.term_id AND term_meta.meta_key = 'order'";
209
210 if ( ! $this->does_substring_exist( $pieces['join'], $join_statement ) ) {
211 $pieces['join'] .= $join_statement;
212 }
213
214 $pieces['orderby'] = 'ORDER BY CAST( term_meta.meta_value AS UNSIGNED )';
215 }
216 }
217
218 return $pieces;
219 }
220
221 /**
222 * Order the taxonomies on the front end.
223 */
224 public function front_end_order_terms() {
225 if ( ! is_admin() ) {
226 add_filter( 'terms_clauses', [$this, 'set_tax_order'], 10, 3 );
227 }
228 }
229
230 /**
231 * Check if a substring exists inside a string.
232 *
233 * @param string $string The main string (haystack) we're searching in.
234 * @param string $substring The substring we're searching for.
235 *
236 * @return bool True if substring exists, else false.
237 */
238 protected function does_substring_exist( $string, $substring ) {
239 return strstr( $string, $substring ) !== false;
240 }
241
242 public function register_api_endpoint() {
243 register_rest_route( $this->namespace, '/faq/sample_data', [
244 'methods' => ['POST'],
245 'callback' => [$this, 'create_faq_sample'],
246 'permission_callback' => function () {
247 return current_user_can( 'edit_others_posts' );
248 }
249 ] );
250
251 register_rest_route( $this->namespace, '/faq/posts/(?P<type>\S+)', [
252 'methods' => ['GET'],
253 'callback' => [$this, 'fetch_faq_posts'],
254 'permission_callback' => '__return_true'
255 ] );
256
257 register_rest_route( $this->namespace, '/faq/create_category', [
258 'methods' => ['POST'],
259 'callback' => [$this, 'create_faq_category'],
260 'permission_callback' => function () {
261 return current_user_can( 'edit_others_posts' );
262 }
263 ] );
264
265 register_rest_route( $this->namespace, '/faq/update_category', [
266 'methods' => ['POST'],
267 'callback' => [$this, 'update_faq_category'],
268 'permission_callback' => function () {
269 return current_user_can( 'edit_others_posts' );
270 }
271 ] );
272
273 register_rest_route( $this->namespace, '/faq/delete_category', [
274 'methods' => ['POST'],
275 'callback' => [$this, 'delete_faq_category'],
276 'permission_callback' => function () {
277 return current_user_can( 'edit_others_posts' );
278 }
279 ] );
280
281 register_rest_route( $this->namespace, '/faq/create_post', [
282 'methods' => ['POST'],
283 'callback' => [$this, 'create_betterdocs_faq'],
284 'permission_callback' => function () {
285 return current_user_can( 'edit_others_posts' );
286 }
287 ] );
288
289 register_rest_route( $this->namespace, '/faq/update_post', [
290 'methods' => ['POST'],
291 'callback' => [$this, 'update_betterdocs_faq'],
292 'permission_callback' => function () {
293 return current_user_can( 'edit_others_posts' );
294 }
295 ] );
296
297 register_rest_route( $this->namespace, '/faq/delete_post', [
298 'methods' => ['POST'],
299 'callback' => [$this, 'delete_betterdocs_faq'],
300 'permission_callback' => function () {
301 return current_user_can( 'edit_others_posts' );
302 }
303 ] );
304
305 register_rest_route( $this->namespace, '/faq/category_status', [
306 'methods' => ['POST'],
307 'callback' => [$this, 'update_category_status'],
308 'permission_callback' => function () {
309 return current_user_can( 'edit_others_posts' );
310 }
311 ] );
312
313 register_rest_route( $this->namespace, '/faq/category_order', [
314 'methods' => ['POST'],
315 'callback' => [$this, 'update_faq_category_order'],
316 'permission_callback' => function () {
317 return current_user_can( 'edit_others_posts' );
318 }
319 ] );
320
321 register_rest_route( $this->namespace, '/faq/update_order_by_category', [
322 'methods' => ['POST'],
323 'callback' => [$this, 'update_faq_order_by_category'],
324 'permission_callback' => function () {
325 return current_user_can( 'edit_others_posts' );
326 }
327 ] );
328
329 register_rest_route( $this->namespace, '/faq/uncategorised', [
330 'methods' => ['GET'],
331 'callback' => [$this, 'get_uncategorised_faq'],
332 'permission_callback' => '__return_true'
333 ] );
334
335 register_rest_route( $this->namespace, '/faq/category_search', [
336 'methods' => ['GET'],
337 'callback' => [$this, 'category_search'],
338 'permission_callback' => '__return_true',
339 'args' => array(
340 'title' => array(
341 'type' => 'string',
342 'required' => true
343 ),
344 ),
345 ] );
346 }
347
348 public function create_faq_sample( $params ) {
349 $sample_data = json_decode( $params->get_param( 'sample_data' ), true );
350 foreach ( $sample_data as $key => $value ) {
351 $insert_term = wp_insert_term(
352 $key,
353 'betterdocs_faq_category'
354 );
355 if ( $insert_term ) {
356 foreach ( $value['posts'] as $key => $value ) {
357 $this->insert_betterdocs_faq( $value['post_title'], $value['post_content'], $insert_term['term_id'] );
358 }
359 }
360 }
361 return true;
362 }
363
364 public function create_faq_category( $params ) {
365 $title = $params->get_param( 'title' );
366 $description = $params->get_param( 'description' );
367 $description = ( $description !== 'undefined' ) ? $description : '';
368 $slug = $params->get_param( 'slug' );
369 return $this->insert_betterdocs_faq_category( $title, $description, $slug );
370 }
371
372 public function update_faq_category( $params ) {
373 $term_id = $params->get_param( 'term_id' );
374 $title = $params->get_param( 'title' );
375 $description = $params->get_param( 'description' );
376 $description = ( $description !== 'undefined' ) ? $description : '';
377 $slug = $params->get_param( 'slug' );
378 $update = wp_update_term( $term_id, 'betterdocs_faq_category', [
379 'name' => $title,
380 'slug' => $slug,
381 'description' => $description
382 ] );
383
384 if ( is_wp_error( $update ) ) {
385 return $update;
386 } else {
387 return true;
388 }
389 }
390
391 public function delete_faq_category( $params ) {
392 $term_id = $params->get_param( 'term_id' );
393 $delete = wp_delete_term( $term_id, 'betterdocs_faq_category' );
394
395 if ( is_wp_error( $delete ) ) {
396 return $delete;
397 } else {
398 return true;
399 }
400 }
401
402 public function insert_betterdocs_faq_category( $title, $description, $slug = '' ) {
403 $insert_term = wp_insert_term(
404 $title,
405 'betterdocs_faq_category',
406 [
407 'slug' => $slug,
408 'description' => $description
409 ]
410 );
411
412 if ( is_wp_error( $insert_term ) ) {
413 return $insert_term;
414 } else {
415 return true;
416 }
417 }
418
419 public function update_faq_category_order( $params ) {
420 $faq_category_order = $params->get_param( 'faq_category_order' );
421 $faq_category_order = json_decode( $faq_category_order, true );
422
423 foreach ( $faq_category_order as $order_data ) {
424 if ( (int) $order_data['current_position'] != (int) $order_data['updated_position'] ) {
425 update_term_meta( $order_data['id'], 'order', ( (int) $order_data['updated_position'] ) );
426 }
427 }
428 return true;
429 }
430
431 public function insert_betterdocs_faq( $post_title, $post_content, $term_id ) {
432 $post = wp_insert_post(
433 [
434 'post_type' => 'betterdocs_faq',
435 'post_title' => wp_strip_all_tags( $post_title ),
436 'post_content' => $post_content,
437 'post_status' => 'publish'
438 ]
439 );
440
441 if ( $term_id ) {
442 $set_terms = wp_set_object_terms( $post, $term_id, 'betterdocs_faq_category' );
443 if ( is_wp_error( $set_terms ) ) {
444 return $set_terms;
445 } else {
446 return $this->update_faq_order_on_insert( $term_id, $post );
447 }
448 } else {
449 return $post;
450 }
451 }
452
453 public function update_faq_order_on_insert( $term_id, $post ) {
454 $term_meta = get_term_meta( $term_id, '_betterdocs_faq_order' );
455 if ( ! empty( $term_meta ) ) {
456 $term_meta_arr = explode( ",", $term_meta[0] );
457 if ( ! in_array( $post, $term_meta_arr ) ) {
458 array_unshift( $term_meta_arr, $post );
459 $docs_ordering_data = filter_var_array( wp_unslash( $term_meta_arr ), FILTER_SANITIZE_NUMBER_INT );
460 return update_term_meta( $term_id, '_betterdocs_faq_order', implode( ',', $docs_ordering_data ) );
461 }
462 } else {
463 return update_term_meta( $term_id, '_betterdocs_faq_order', $post );
464 }
465 }
466
467 /**
468 * Update _betterdocs_faq_order meta when new post created
469 */
470
471 public function update_faq_order_by_category( $params ) {
472 $term_id = $params->get_param( 'term_id' );
473 $posts = $params->get_param( 'posts' );
474 return update_term_meta( $term_id, '_betterdocs_faq_order', $posts );
475 }
476
477 public function create_betterdocs_faq( $params ) {
478 $post_title = $params->get_param( 'post_title' );
479 $post_content = $params->get_param( 'post_content' );
480 $term_id = $params->get_param( 'term_id' );
481 return $this->insert_betterdocs_faq( $post_title, $post_content, $term_id );
482 }
483
484 public function update_betterdocs_faq( $params ) {
485 $post_id = $params->get_param( 'post_id' );
486 $post_title = $params->get_param( 'post_title' );
487 $post_content = $params->get_param( 'post_content' );
488 $status = $params->get_param( 'status' );
489 $term_id = $params->get_param( 'term_id' );
490 if ( $status ) {
491 $data = [
492 'post_type' => 'betterdocs_faq',
493 'ID' => $post_id,
494 'status' => $status
495 ];
496 } else {
497 $data = [
498 'post_type' => 'betterdocs_faq',
499 'ID' => $post_id,
500 'post_title' => $post_title,
501 'post_content' => $post_content
502 ];
503
504 if ( $term_id ) {
505 $data['tax_input'] = [
506 "betterdocs_faq_category" => $term_id
507 ];
508
509 $term_meta = get_term_meta( $term_id, '_betterdocs_faq_order' );
510 $term_meta_arr = explode( ",", $term_meta[0] );
511 if ( ! in_array( $post_id, $term_meta_arr ) ) {
512 array_unshift( $term_meta_arr, $post_id );
513 $docs_ordering_data = filter_var_array( wp_unslash( $term_meta_arr ), FILTER_SANITIZE_NUMBER_INT );
514 update_term_meta( $term_id, '_betterdocs_faq_order', implode( ',', $docs_ordering_data ) );
515 }
516 }
517 }
518
519 return wp_update_post( $data );
520 }
521
522 public function delete_betterdocs_faq( $params ) {
523 $post_id = $params->get_param( 'post_id' );
524 return wp_delete_post( $post_id );
525 }
526
527 public function faq_post_loop( $args ) {
528 $posts = [];
529 $query = new WP_Query( $args );
530 if ( $query->have_posts() ):
531 while ( $query->have_posts() ): $query->the_post();
532 $posts[get_the_ID()]['title'] = get_the_title();
533 $posts[get_the_ID()]['content'] = get_the_content();
534 endwhile;
535 endif;
536
537 return $posts;
538 }
539
540 public function update_category_status( $params ) {
541 $term_id = $params->get_param( 'term_id' );
542 $status = $params->get_param( 'status' );
543 return update_term_meta( $term_id, 'status', $status );
544 }
545
546 public function fetch_faq_posts( $params ) {
547 $faq = [];
548 $type = $params->get_param( 'type' );
549
550 if ( $type == 'category' ) {
551 $taxonomy_objects = get_terms( 'betterdocs_faq_category', [
552 'hide_empty' => false
553 ] );
554
555 if ( $taxonomy_objects && ! is_wp_error( $taxonomy_objects ) ):
556 foreach ( $taxonomy_objects as $term ):
557 $args = [
558 'post_type' => 'betterdocs_faq',
559 'post_status' => 'publish',
560 'post_per_page' => -1,
561 'tax_query' => [
562 [
563 'taxonomy' => 'betterdocs_faq_category',
564 'field' => 'term_id',
565 'terms' => $term->term_id
566 ]
567 ]
568 ];
569
570 $posts = $this->faq_post_loop( $args );
571
572 $faq[$term->slug] = [
573 (array) $term,
574 'posts' => $posts
575 ];
576 endforeach;
577 endif;
578 } else {
579 $args = [
580 'post_type' => 'betterdocs_faq',
581 'post_status' => 'publish',
582 'post_per_page' => -1
583 ];
584 $posts = $this->faq_post_loop( $args );
585 $faq['posts'] = $posts;
586 }
587
588 return $faq;
589 }
590
591 public function get_uncategorised_faq() {
592 $available_terms = array_map( function ( $term ) {
593 return $term->term_id;
594 }, get_terms( [
595 'taxonomy' => 'betterdocs_faq_category',
596 'hide_empty' => false
597 ] ) );
598
599 return get_posts( [
600 'post_type' => 'betterdocs_faq',
601 'post_status' => ['publish', 'draft'],
602 'posts_per_page' => -1,
603 'tax_query' => [
604 'relation' => 'AND',
605 [
606 'taxonomy' => 'betterdocs_faq_category',
607 'field' => 'term_id',
608 'terms' => $available_terms,
609 'operator' => 'NOT IN'
610 ]
611 ]
612 ] );
613 }
614
615 public function category_search( $request ) {
616
617 $title = $request['title'];
618
619 // Perform the taxonomy search
620 $taxonomy_args = array(
621 'name__like' => $title,
622 'taxonomy' => 'betterdocs_faq_category',
623 'hide_empty' => false
624 );
625
626 $taxonomies = get_terms($taxonomy_args);
627
628 if (!empty($taxonomies)) {
629 $result = array();
630 foreach ($taxonomies as $taxonomy) {
631 $result[] = array(
632 'id' => $taxonomy->term_id,
633 'count' => $taxonomy->count,
634 'description' => $taxonomy->description,
635 'name' => $taxonomy->name,
636 'slug' => $taxonomy->slug
637 // Add more fields as needed
638 );
639 }
640 // Return the taxonomy data
641 return $result;
642 } else {
643 // Taxonomy not found
644 return new WP_Error('taxonomy_not_found', 'Taxonomy not found.', array('status' => 404));
645 }
646 }
647
648 public function faq_category_orderby_meta($args, $request) {
649 if ($args['taxonomy'] === 'betterdocs_faq_category') {
650 $args['orderby'] = 'meta_value_num';
651 $args['meta_key'] = 'order';
652 }
653 return $args;
654 }
655 }
656