PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.5.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.5.0
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 / Glossaries.php

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

968 lines 28.1 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 use WPDeveloper\BetterDocs\Utils\Helper;
9
10 class Glossaries extends Base {
11 /**
12 * REST API namespace
13 * @var string
14 */
15 private $namespace = 'betterdocs';
16 public $post_type = 'docs';
17 public $category = 'glossaries';
18
19 /**
20 *
21 * Initialize the class and start calling our hooks and filters
22 *
23 * @since 1.0.0
24 *
25 */
26 public function __construct() {
27 add_action( 'init', [ $this, 'register_post' ] );
28 // fires after a new betterdocs_glossaries is created
29 add_action( 'created_glossaries', [ $this, 'action_created_betterdocs_glossaries' ], 10, 2 );
30 add_action( 'rest_api_init', [ $this, 'register_api_endpoint' ] );
31 add_action( 'rest_api_init', [ $this, 'register_glossary_rest_fields' ] );
32 add_action( 'rest_glossaries_query', array( $this, 'glossaries_orderby_meta' ), 10, 2 );
33 add_action( 'rest_glossaries_query', array( $this, 'disable_language_filtering_for_admin_rest' ), 5, 2 );
34 // Ensure meta fields are properly exposed in REST API
35 add_filter( 'rest_prepare_glossaries', array( $this, 'add_meta_to_rest_response' ), 10, 3 );
36 // Enqueue Scripts
37 add_action( 'admin_enqueue_scripts', [ $this, 'enqueue' ] );
38 // Ensure existing glossaries have proper status
39 add_action( 'admin_init', [ $this, 'ensure_glossaries_have_status' ] );
40 }
41
42 public function register_post() {
43 // Register term meta fields for glossaries taxonomy
44 // Force register without duplicate checks to ensure they're properly registered
45 register_term_meta(
46 $this->category,
47 'status',
48 [
49 'show_in_rest' => true,
50 'single' => true,
51 'type' => 'string', // Changed to string to match React expectation
52 'default' => '1',
53 'sanitize_callback' => 'sanitize_text_field'
54 ]
55 );
56
57 register_term_meta(
58 $this->category,
59 'order',
60 [
61 'show_in_rest' => true,
62 'single' => true,
63 'type' => 'string', // Changed to string for consistency
64 'default' => '0',
65 'sanitize_callback' => 'sanitize_text_field'
66 ]
67 );
68
69 register_term_meta(
70 $this->category,
71 'glossary_term_description',
72 [
73 'show_in_rest' => true,
74 'single' => true,
75 'type' => 'string',
76 'default' => '',
77 'sanitize_callback' => 'wp_kses_post'
78 ]
79 );
80 }
81
82 public function enqueue( $hook ) {
83 if ( $hook === 'betterdocs_page_betterdocs-glossaries' ) {
84 betterdocs()->assets->enqueue( 'betterdocs-admin-glossaries', 'admin/css/faq.css' );
85
86 betterdocs()->assets->enqueue( 'betterdocs-admin-glossaries', 'admin/js/glossaries.js' );
87
88 // removing emoji support
89 remove_action( 'wp_head', 'print_emoji_detection_script', 7 );
90 remove_action( 'admin_print_scripts', 'print_emoji_detection_script' );
91
92 betterdocs()->assets->localize(
93 'betterdocs-admin-glossaries',
94 'betterdocs',
95 [
96 'dir_url' => BETTERDOCS_ABSURL,
97 'rest_url' => esc_url_raw( rest_url() ),
98 'free_version' => betterdocs()->version,
99 'nonce' => wp_create_nonce( 'wp_rest' ),
100 'current_language' => Helper::get_current_language(),
101 'is_multilingual' => Helper::is_multilingual_active()
102 ]
103 );
104 }
105 }
106
107 public function output() {
108 betterdocs()->views->get( 'admin/glossaries' );
109 }
110
111 /**
112 * Ensure existing glossaries have proper status meta
113 * This fixes the issue where existing glossaries appear disabled
114 */
115 public function ensure_glossaries_have_status() {
116 // Run this every time in admin to ensure status is properly set
117 // Get all glossaries terms
118 $all_terms = get_terms( array(
119 'taxonomy' => 'glossaries',
120 'hide_empty' => false,
121 'suppress_filters' => true // Bypass language filtering
122 ) );
123
124 if ( ! empty( $all_terms ) && ! is_wp_error( $all_terms ) ) {
125 foreach ( $all_terms as $term ) {
126 $current_status = get_term_meta( $term->term_id, 'status', true );
127
128 // If status is empty or not set, set it to enabled ('1')
129 if ( empty( $current_status ) || $current_status === '' ) {
130 update_term_meta( $term->term_id, 'status', '1' );
131 }
132
133 // Also ensure order meta exists
134 $current_order = get_term_meta( $term->term_id, 'order', true );
135 if ( empty( $current_order ) || $current_order === '' ) {
136 update_term_meta( $term->term_id, 'order', '0' );
137 }
138 }
139 }
140 }
141
142
143 /**
144 * Default the taxonomy's terms' order if it's not set.
145 *
146 * @param string $tax_slug The taxonomy's slug.
147 */
148 public function action_created_betterdocs_glossaries( $term_id ) {
149 $order = $this->get_max_taxonomy_order( 'glossaries' );
150 // update_term_meta( $term_id, 'order', $order++ );
151 update_term_meta( $term_id, 'status', '1' ); // Set as string
152 update_term_meta( $term_id, 'order', '0' ); // Also set order
153 }
154
155 /**
156 * Default the taxonomy's terms' order if it's not set.
157 *
158 * @param string $tax_slug The taxonomy's slug.
159 */
160 public function default_term_order( $tax_slug ) {
161 $terms = get_terms(
162 [
163 'taxonomy' => $tax_slug,
164 'hide_empty' => false,
165 ]
166 );
167
168 $order = $this->get_max_taxonomy_order( $tax_slug );
169
170 foreach ( $terms as $term ) {
171 if ( ! get_term_meta( $term->term_id, 'order', true ) ) {
172 update_term_meta( $term->term_id, 'order', $order );
173 ++$order;
174 }
175 }
176 }
177
178 /**
179 * Get the maximum order for this taxonomy. This will be applied to terms that don't have a tax position.
180 */
181 private function get_max_taxonomy_order( $tax_slug ) {
182 global $wpdb;
183
184 $max_term_order = $wpdb->get_col(
185 $wpdb->prepare(
186 "SELECT MAX( CAST( tm.meta_value AS UNSIGNED ) )
187 FROM $wpdb->terms t
188 JOIN $wpdb->term_taxonomy tt ON t.term_id = tt.term_id AND tt.taxonomy = '%s'
189 JOIN $wpdb->termmeta tm ON tm.term_id = t.term_id WHERE tm.meta_key = 'order'",
190 $tax_slug
191 )
192 );
193
194 $max_term_order = is_array( $max_term_order ) ? current( $max_term_order ) : 0;
195
196 return (int) $max_term_order === 0 || empty( $max_term_order ) ? 1 : (int) $max_term_order + 1;
197 }
198
199 /**
200 * Re-Order the taxonomies based on the order value.
201 *
202 * @param array $pieces Array of SQL query clauses.
203 * @param array $taxonomies Array of taxonomy names.
204 * @param array $args Array of term query args.
205 */
206 public function set_tax_order( $pieces, $taxonomies, $args ) {
207 foreach ( $taxonomies as $taxonomy ) {
208 global $wpdb;
209
210 if ( $taxonomy === 'betterdocs_glossaries' ) {
211 $join_statement = " LEFT JOIN $wpdb->termmeta AS term_meta ON t.term_id = term_meta.term_id AND term_meta.meta_key = 'order'";
212
213 if ( ! $this->does_substring_exist( $pieces['join'], $join_statement ) ) {
214 $pieces['join'] .= $join_statement;
215 }
216
217 $pieces['orderby'] = 'ORDER BY CAST( term_meta.meta_value AS UNSIGNED )';
218 }
219 }
220
221 return $pieces;
222 }
223
224 /**
225 * Order the taxonomies on the front end.
226 */
227 public function front_end_order_terms() {
228 if ( ! is_admin() ) {
229 add_filter( 'terms_clauses', [ $this, 'set_tax_order' ], 10, 3 );
230 }
231 }
232
233 /**
234 * Check if a substring exists inside a string.
235 *
236 * @param string $string The main string (haystack) we're searching in.
237 * @param string $substring The substring we're searching for.
238 *
239 * @return bool True if substring exists, else false.
240 */
241 protected function does_substring_exist( $string, $substring ) {
242 return strstr( $string, $substring ) !== false;
243 }
244
245 public function register_api_endpoint() {
246 register_rest_route(
247 $this->namespace,
248 '/glossary/sample_data',
249 [
250 'methods' => [ 'POST' ],
251 'callback' => [ $this, 'create_glossary_sample' ],
252 'permission_callback' => function () {
253 return current_user_can( 'edit_others_posts' );
254 }
255 ]
256 );
257
258 register_rest_route(
259 $this->namespace,
260 '/glossary/posts/(?P<type>\S+)',
261 [
262 'methods' => [ 'GET' ],
263 'callback' => [ $this, 'fetch_faq_posts' ],
264 'permission_callback' => '__return_true'
265 ]
266 );
267
268 register_rest_route(
269 $this->namespace,
270 '/glossary/create_glossary',
271 [
272 'methods' => [ 'POST' ],
273 'callback' => [ $this, 'create_glossaries' ],
274 'permission_callback' => function () {
275 return current_user_can( 'edit_others_posts' );
276 }
277 ]
278 );
279
280 register_rest_route(
281 $this->namespace,
282 '/glossary/update_glossary',
283 [
284 'methods' => [ 'POST' ],
285 'callback' => [ $this, 'update_glossaries' ],
286 'permission_callback' => function () {
287 return current_user_can( 'edit_others_posts' );
288 }
289 ]
290 );
291
292 register_rest_route(
293 $this->namespace,
294 '/glossary/delete_glossary',
295 [
296 'methods' => [ 'POST' ],
297 'callback' => [ $this, 'delete_glossaries' ],
298 'permission_callback' => function () {
299 return current_user_can( 'edit_others_posts' );
300 }
301 ]
302 );
303
304 register_rest_route(
305 $this->namespace,
306 '/glossary/glossary_status',
307 [
308 'methods' => [ 'POST' ],
309 'callback' => [ $this, 'update_glossary_status' ],
310 'permission_callback' => function () {
311 return current_user_can( 'edit_others_posts' );
312 }
313 ]
314 );
315
316 register_rest_route(
317 $this->namespace,
318 '/glossary/glossaries_order',
319 [
320 'methods' => [ 'POST' ],
321 'callback' => [ $this, 'update_glossaries_order' ],
322 'permission_callback' => function () {
323 return current_user_can( 'edit_others_posts' );
324 }
325 ]
326 );
327
328 register_rest_route(
329 $this->namespace,
330 '/glossary/update_order_by_glossary',
331 [
332 'methods' => [ 'POST' ],
333 'callback' => [ $this, 'update_faq_order_by_glossary' ],
334 'permission_callback' => function () {
335 return current_user_can( 'edit_others_posts' );
336 }
337 ]
338 );
339
340 register_rest_route(
341 $this->namespace,
342 '/glossary/glossary_search',
343 [
344 'methods' => [ 'GET' ],
345 'callback' => [ $this, 'glossary_search' ],
346 'permission_callback' => '__return_true',
347 'args' => array(
348 'title' => array(
349 'type' => 'string',
350 'required' => true
351 ),
352 ),
353 ]
354 );
355
356 register_rest_route(
357 $this->namespace,
358 '/glossary/glossary_count',
359 [
360 'methods' => [ 'GET' ],
361 'callback' => [ $this, 'get_glossary_count' ],
362 'permission_callback' => function () {
363 return current_user_can( 'edit_others_posts' );
364 }
365 ]
366 );
367
368 register_rest_route(
369 $this->namespace,
370 '/glossary/check_existing',
371 [
372 'methods' => [ 'POST' ],
373 'callback' => [ $this, 'check_existing_glossaries' ],
374 'permission_callback' => function () {
375 return current_user_can( 'edit_others_posts' );
376 },
377 'args' => array(
378 'terms' => array(
379 'type' => 'array',
380 'required' => true,
381 'items' => array( 'type' => 'string' ),
382 ),
383 ),
384 ]
385 );
386 register_rest_route(
387 $this->namespace,
388 '/glossary/get_glossaries',
389 [
390 'methods' => [ 'GET' ],
391 'callback' => [ $this, 'get_glossaries' ],
392 'permission_callback' => function () {
393 return current_user_can( 'edit_others_posts' );
394 }
395 ]
396 );
397 }
398
399 public function create_glossary_sample( $params ) {
400 $sample_data = json_decode( $params->get_param( 'sample_data' ), true );
401 foreach ( $sample_data as $key => $value ) {
402 $insert_term = wp_insert_term(
403 $key,
404 'glossaries'
405 );
406 if ( $insert_term ) {
407 foreach ( $value['posts'] as $key => $value ) {
408 $this->insert_betterdocs_faq( $value['post_title'], $value['post_content'], $insert_term['term_id'] );
409 }
410 }
411 }
412 return true;
413 }
414
415 public function create_glossaries( $params ) {
416 $title = $params->get_param( 'title' );
417 $description = $params->get_param( 'description' );
418 $slug = $params->get_param( 'slug' );
419 $language = $params->get_param( 'language' );
420
421 // Create the term
422 $new_term = wp_insert_term(
423 $title,
424 'glossaries',
425 [
426 'slug' => $slug,
427 ]
428 );
429
430 if ( is_wp_error( $new_term ) ) {
431 return ['status' => 'failed', 'data' => $new_term];
432 }
433
434 // Set the custom field description
435 $term_id = $new_term['term_id'];
436 update_term_meta( $term_id, 'glossary_term_description', $description ); //phpcs:ignore inline styles are need for the front-end
437
438 // Set language for multilingual plugins
439 if ( $language && Helper::is_multilingual_active() ) {
440 // WPML Support
441 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
442 global $sitepress;
443 if ( $sitepress && method_exists( $sitepress, 'set_element_language_details' ) ) {
444 $sitepress->set_element_language_details( $term_id, 'tax_glossaries', null, $language );
445 }
446 }
447 // Polylang Support
448 elseif ( function_exists( 'pll_set_term_language' ) ) {
449 pll_set_term_language( $term_id, $language );
450 }
451 }
452
453 $new_term = $this->glossary_term_in_rest_api_schema($term_id, $description);
454 return ['status' => 'success', 'data' => $new_term];
455 }
456
457 /**
458 * Form Glossary Term In Rest Api Schema Format
459 *
460 * @param int $term_id
461 * @param string $meta_description
462 * @return array
463 */
464 public function glossary_term_in_rest_api_schema( $term_id, $meta_description = '' ) {
465 $term = get_term_by('id', $term_id, 'glossaries');
466 return [
467 'count' => $term->count,
468 'description' => $term->description,
469 'glossary_term_description' => $meta_description, //get the current description
470 'id' => $term->term_id,
471 'link' => get_permalink($term->term_id),
472 'meta' => [
473 'status' => get_term_meta( $term->term_id, 'status', true )
474 ],
475 'name' => $term->name,
476 'parent' => $term->parent,
477 'slug' => $term->slug,
478 'taxonomy' => $term->taxonomy
479 ];
480 }
481
482 public function update_glossaries( $request ) {
483 $term_id = $request->get_param( 'term_id' );
484 $title = $request->get_param( 'title' );
485 $description = $request->get_param( 'description' );
486 $description = ( $description !== 'undefined' ) ? $description : '';
487 $slug = $request->get_param( 'slug' );
488 $language = $request->get_param( 'language' );
489
490 // Verify that we're updating the correct language version of the term
491 if ( $language && Helper::is_multilingual_active() ) {
492 $term_language = null;
493
494 // WPML Support
495 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
496 global $sitepress;
497 if ( $sitepress && method_exists( $sitepress, 'get_element_language_details' ) ) {
498 $lang_details = $sitepress->get_element_language_details( $term_id, 'tax_glossaries' );
499 $term_language = $lang_details ? $lang_details->language_code : null;
500 }
501 }
502 // Polylang Support
503 elseif ( function_exists( 'pll_get_term_language' ) ) {
504 $term_language = pll_get_term_language( $term_id );
505 }
506
507 // Only update if the term belongs to the current language
508 if ( $term_language && $term_language !== $language ) {
509 return ['status' => 'failed', 'data' => new \WP_Error( 'wrong_language', 'Cannot update term from different language' )];
510 }
511 }
512
513 // Check if there's old data in the default description field and transfer it to the custom field
514 $old_description = get_term_field( 'description', $term_id, 'glossaries' );
515 if ( ! empty( $old_description ) && empty( get_term_meta( $term_id, 'glossary_term_description', true ) ) ) {
516 update_term_meta( $term_id, 'glossary_term_description', wp_kses_post( $old_description ) );
517 wp_update_term( $term_id, $this->glossaries, [ 'description' => '' ] );
518 }
519
520 // Update the term
521 $update = wp_update_term(
522 $term_id,
523 'glossaries',
524 [
525 'name' => $title,
526 'slug' => $slug,
527 ]
528 );
529
530 if ( is_wp_error( $update ) ) {
531 return ['status' => 'failed', 'data' => $update];
532 } else {
533 // Update the custom field description
534 update_term_meta( $term_id, 'glossary_term_description', $description );
535 return ['status' => 'success', 'data' => $this->glossary_term_in_rest_api_schema($term_id, $description)];
536 }
537 }
538
539 public function save_glossary_term_meta( $term_id, $tt_id ) {
540 if ( isset( $_POST['glossary_term_description'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
541 $description = wp_kses_post( wp_unslash( $_POST['glossary_term_description'] ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
542 update_term_meta( $term_id, 'glossary_term_description', $description );
543 }
544 }
545
546 public function register_glossary_rest_fields() {
547 register_rest_field(
548 'glossaries',
549 'glossary_term_description',
550 [
551 'get_callback' => function ( $term ) {
552 return get_term_meta( $term['id'], 'glossary_term_description', true );
553 },
554 'update_callback' => null,
555 'schema' => [
556 'description' => __( 'Glossary Term Description', 'betterdocs' ),
557 'type' => 'string',
558 'context' => [ 'view', 'edit' ],
559 ],
560 ]
561 );
562 }
563
564 public function delete_glossaries( $params ) {
565 $term_id = $params->get_param( 'term_id' );
566 $delete = wp_delete_term( $term_id, 'glossaries' );
567
568 if ( is_wp_error( $delete ) ) {
569 return $delete;
570 } else {
571 return true;
572 }
573 }
574
575
576 public function insert_betterdocs_glossaries( $title, $description, $slug = '' ) {
577 $insert_term = wp_insert_term(
578 $title,
579 'glossaries',
580 [
581 'slug' => $slug,
582 'description' => $description
583 ]
584 );
585
586 if ( is_wp_error( $insert_term ) ) {
587 return $insert_term;
588 } else {
589 return true;
590 }
591 }
592
593 public function update_glossaries_order( $params ) {
594 $glossaries_order = $params->get_param( 'glossaries_order' );
595 $glossaries_order = json_decode( $glossaries_order, true );
596
597 foreach ( $glossaries_order as $order_data ) {
598 if ( (int) $order_data['current_position'] != (int) $order_data['updated_position'] ) {
599 update_term_meta( $order_data['id'], 'order', ( (int) $order_data['updated_position'] ) );
600 }
601 }
602 return true;
603 }
604
605 public function insert_betterdocs_faq( $post_title, $post_content, $term_id ) {
606 $post = wp_insert_post(
607 [
608 'post_type' => 'betterdocs_faq',
609 'post_title' => wp_strip_all_tags( $post_title ),
610 'post_content' => $post_content,
611 'post_status' => 'publish'
612 ]
613 );
614
615 if ( $term_id ) {
616 $set_terms = wp_set_object_terms( $post, $term_id, 'glossaries' );
617 if ( is_wp_error( $set_terms ) ) {
618 return $set_terms;
619 } else {
620 return $this->update_faq_order_on_insert( $term_id, $post );
621 }
622 } else {
623 return $post;
624 }
625 }
626
627 public function update_faq_order_on_insert( $term_id, $post ) {
628 $term_meta = get_term_meta( $term_id, '_betterdocs_faq_order' );
629 if ( ! empty( $term_meta ) ) {
630 $term_meta_arr = explode( ',', $term_meta[0] );
631 if ( ! in_array( $post, $term_meta_arr ) ) {
632 array_unshift( $term_meta_arr, $post );
633 $docs_ordering_data = filter_var_array( wp_unslash( $term_meta_arr ), FILTER_SANITIZE_NUMBER_INT );
634 return update_term_meta( $term_id, '_betterdocs_faq_order', implode( ',', $docs_ordering_data ) );
635 }
636 } else {
637 return update_term_meta( $term_id, '_betterdocs_faq_order', $post );
638 }
639 }
640
641 /**
642 * Update _betterdocs_faq_order meta when new post created
643 */
644
645 public function update_faq_order_by_glossary( $params ) {
646 $term_id = $params->get_param( 'term_id' );
647 $posts = $params->get_param( 'posts' );
648 return update_term_meta( $term_id, '_betterdocs_faq_order', $posts );
649 }
650
651 public function create_betterdocs_faq( $params ) {
652 $post_title = $params->get_param( 'post_title' );
653 $post_content = $params->get_param( 'post_content' );
654 $term_id = $params->get_param( 'term_id' );
655 return $this->insert_betterdocs_faq( $post_title, $post_content, $term_id );
656 }
657
658 public function update_betterdocs_faq( $params ) {
659 $post_id = $params->get_param( 'post_id' );
660 $post_title = $params->get_param( 'post_title' );
661 $post_content = $params->get_param( 'post_content' );
662 $status = $params->get_param( 'status' );
663 $term_id = $params->get_param( 'term_id' );
664 if ( $status ) {
665 $data = [
666 'post_type' => 'betterdocs_faq',
667 'ID' => $post_id,
668 'status' => $status
669 ];
670 } else {
671 $data = [
672 'post_type' => 'betterdocs_faq',
673 'ID' => $post_id,
674 'post_title' => $post_title,
675 'post_content' => $post_content
676 ];
677
678 if ( $term_id ) {
679 $data['tax_input'] = [
680 'betterdocs_glossaries' => $term_id
681 ];
682
683 $term_meta = get_term_meta( $term_id, '_betterdocs_faq_order' );
684 $term_meta_arr = explode( ',', $term_meta[0] );
685 if ( ! in_array( $post_id, $term_meta_arr ) ) {
686 array_unshift( $term_meta_arr, $post_id );
687 $docs_ordering_data = filter_var_array( wp_unslash( $term_meta_arr ), FILTER_SANITIZE_NUMBER_INT );
688 update_term_meta( $term_id, '_betterdocs_faq_order', implode( ',', $docs_ordering_data ) );
689 }
690 }
691 }
692
693 return wp_update_post( $data );
694 }
695
696 public function delete_betterdocs_faq( $params ) {
697 $post_id = $params->get_param( 'post_id' );
698 return wp_delete_post( $post_id );
699 }
700
701 public function faq_post_loop( $args ) {
702 $posts = [];
703 $query = new WP_Query( $args );
704 if ( $query->have_posts() ) :
705 while ( $query->have_posts() ) :
706 $query->the_post();
707 $posts[ get_the_ID() ]['title'] = get_the_title();
708 $posts[ get_the_ID() ]['content'] = get_the_content();
709 endwhile;
710 endif;
711
712 return $posts;
713 }
714
715 public function update_glossary_status( $params ) {
716 $term_id = $params->get_param( 'term_id' );
717 $status = $params->get_param( 'status' );
718
719 // Ensure status is a string ('0' or '1')
720 $status = $status ? '1' : '0';
721
722 $result = update_term_meta( $term_id, 'status', $status );
723
724 // Return success response with updated status
725 return array(
726 'success' => $result !== false,
727 'term_id' => $term_id,
728 'status' => $status,
729 'message' => $result !== false ? 'Status updated successfully' : 'Failed to update status'
730 );
731 }
732
733 public function fetch_faq_posts( $params ) {
734 $faq = [];
735 $type = $params->get_param( 'type' );
736
737 if ( $type == 'category' ) {
738 $term_args = [
739 'taxonomy' => 'glossaries',
740 'hide_empty' => false,
741 ];
742
743 // Add language filtering if multilingual plugin is active and we should apply filtering
744 $current_language = Helper::get_current_language();
745 if ( $current_language && Helper::is_multilingual_active() && Helper::should_apply_language_filtering() ) {
746 // For WPML and Polylang, use 'lang' parameter
747 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) || function_exists( 'pll_current_language' ) ) {
748 $term_args['lang'] = $current_language;
749 }
750 }
751
752 $taxonomy_objects = get_terms( $term_args );
753
754 if ( $taxonomy_objects && ! is_wp_error( $taxonomy_objects ) ) :
755 foreach ( $taxonomy_objects as $term ) :
756 $args = [
757 'post_type' => 'betterdocs_faq',
758 'post_status' => 'publish',
759 'post_per_page' => -1,
760 'tax_query' => [
761 [
762 'taxonomy' => 'glossaries',
763 'field' => 'term_id',
764 'terms' => $term->term_id
765 ]
766 ]
767 ];
768
769 $posts = $this->faq_post_loop( $args );
770
771 $faq[ $term->slug ] = [
772 (array) $term,
773 'posts' => $posts
774 ];
775 endforeach;
776 endif;
777 } else {
778 $args = [
779 'post_type' => 'betterdocs_faq',
780 'post_status' => 'publish',
781 'post_per_page' => -1
782 ];
783 $posts = $this->faq_post_loop( $args );
784 $faq['posts'] = $posts;
785 }
786
787 return $faq;
788 }
789
790
791 public function glossary_search( $request ) {
792
793 $title = $request['title'];
794 $lang = $request['lang'] ?? null;
795
796 // Perform the taxonomy search
797 $taxonomy_args = array(
798 'name__like' => $title,
799 'taxonomy' => 'glossaries',
800 'hide_empty' => false
801 );
802
803 // Add language filtering if multilingual plugin is active
804 $language_to_use = $lang ?: Helper::get_current_language();
805 if ( $language_to_use && Helper::is_multilingual_active() ) {
806 // For WPML and Polylang, use 'lang' parameter
807 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) || function_exists( 'pll_current_language' ) ) {
808 $taxonomy_args['lang'] = $language_to_use;
809 }
810 }
811
812 $taxonomies = get_terms( $taxonomy_args );
813
814 if ( ! empty( $taxonomies ) ) {
815 $result = array();
816 foreach ( $taxonomies as $taxonomy ) {
817 $result[] = array(
818 'id' => $taxonomy->term_id,
819 'count' => $taxonomy->count,
820 'description' => $taxonomy->description,
821 'name' => $taxonomy->name,
822 'slug' => $taxonomy->slug
823 // Add more fields as needed
824 );
825 }
826 // Return the taxonomy data
827 return $result;
828 } else {
829 // Taxonomy not found
830 return new WP_Error( 'taxonomy_not_found', 'Taxonomy not found.', array( 'status' => 404 ) );
831 }
832 }
833
834 public function disable_language_filtering_for_admin_rest( $args, $request ) {
835 // Check if this is an admin REST request for glossaries management
836 if ( is_user_logged_in() && current_user_can( 'edit_others_posts' ) ) {
837 // Check if language parameter is explicitly provided in the request
838 $lang_param = $request->get_param( 'lang' );
839
840 if ( $lang_param ) {
841 // If language is specified, use it for filtering
842 $args['lang'] = $lang_param;
843 } else {
844 // If no language specified, remove language filtering to show all
845 unset( $args['lang'] );
846 // Add a temporary filter to bypass language restrictions
847 add_filter( 'get_terms', array( $this, 'ensure_all_glossaries_in_admin' ), 10, 3 );
848 }
849 }
850
851 return $args;
852 }
853
854 public function ensure_all_glossaries_in_admin( $terms, $taxonomies, $args ) {
855 // Only apply to glossaries taxonomy in admin context
856 if ( in_array( 'glossaries', (array) $taxonomies ) && is_user_logged_in() && current_user_can( 'edit_others_posts' ) ) {
857 // Remove this filter to prevent infinite loops
858 remove_filter( 'get_terms', array( $this, 'ensure_all_glossaries_in_admin' ), 10 );
859
860 // Get all glossaries terms without language filtering
861 $all_args = $args;
862 unset( $all_args['lang'] );
863 $all_args['suppress_filters'] = true; // Bypass all filters including language ones
864
865 $all_terms = get_terms( $all_args );
866
867 // Re-add the filter for future calls
868 add_filter( 'get_terms', array( $this, 'ensure_all_glossaries_in_admin' ), 10, 3 );
869
870 return is_wp_error( $all_terms ) ? $terms : $all_terms;
871 }
872
873 return $terms;
874 }
875
876 /**
877 * Add meta fields to REST API response
878 */
879 public function add_meta_to_rest_response( $response, $term, $request ) {
880 // Ensure meta fields are properly included
881 $meta = get_term_meta( $term->term_id );
882
883 // Format meta data as expected by React component
884 $response->data['meta'] = array();
885
886 if ( isset( $meta['status'] ) ) {
887 $response->data['meta']['status'] = $meta['status'];
888 } else {
889 $response->data['meta']['status'] = array( '1' ); // Default to enabled
890 }
891
892 if ( isset( $meta['order'] ) ) {
893 $response->data['meta']['order'] = $meta['order'];
894 } else {
895 $response->data['meta']['order'] = array( '0' ); // Default order
896 }
897
898 if ( isset( $meta['glossary_term_description'] ) ) {
899 $response->data['meta']['glossary_term_description'] = $meta['glossary_term_description'];
900 } else {
901 $response->data['meta']['glossary_term_description'] = array( '' );
902 }
903
904 return $response;
905 }
906
907 public function glossaries_orderby_meta( $args, $request ) {
908 if ( $args['taxonomy'] === 'glossaries' ) {
909 $args['orderby'] = 'meta_value_num';
910 $args['meta_key'] = 'status';
911 }
912 return $args;
913 }
914
915 public function get_glossary_count( $request ) {
916 $options = get_option( 'store_glossary_count' );
917 return rest_ensure_response( $options );
918 }
919
920 /**
921 * Given a list of candidate glossary term names, return which ones already
922 * exist in the `glossaries` taxonomy. Used by the bulk Define-with-AI flow
923 * to surface conflicts before generation rather than at save time.
924 */
925 public function check_existing_glossaries( $request ) {
926 $terms = $request->get_param( 'terms' );
927 if ( ! is_array( $terms ) ) {
928 return rest_ensure_response( array( 'existing' => array() ) );
929 }
930
931 $existing = array();
932 $seen = array();
933
934 foreach ( $terms as $term ) {
935 if ( ! is_string( $term ) ) {
936 continue;
937 }
938 $trimmed = trim( $term );
939 if ( $trimmed === '' ) {
940 continue;
941 }
942 $key = strtolower( $trimmed );
943 if ( isset( $seen[ $key ] ) ) {
944 continue;
945 }
946 $seen[ $key ] = true;
947
948 if ( term_exists( $trimmed, 'glossaries' ) ) {
949 $existing[] = $trimmed;
950 }
951 }
952
953 return rest_ensure_response( array( 'existing' => $existing ) );
954 }
955 public function get_glossaries( $request ) {
956 $taxo = get_taxonomies(
957 array(
958 'name' => array(
959 'glossaries'
960 )
961 ),
962 'objects'
963 );
964
965 return rest_ensure_response( $taxo );
966 }
967 }
968