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 / FAQBuilder.php

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

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