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

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

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