PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.2.6
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.2.6
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 / Utils / Helper.php

Helper.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.2.6, at includes/Utils/Helper.php

1,014 lines 34.9 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\Utils;
4
5 use function BetterLinksPro\Dependencies\GuzzleHttp\json_decode;
6 use function WPML\PHP\Logger\error;
7
8 class Helper extends Base {
9
10 public static function get_plugins( $plugin_basename = null ) {
11 if ( ! function_exists( 'get_plugins' ) ) {
12 include_once ABSPATH . 'wp-admin/includes/plugin.php';
13 }
14
15 $plugins = get_plugins();
16 return $plugin_basename == null ? $plugins : isset( $plugins[ $plugin_basename ] );
17 }
18
19 public static function is_plugin_active( $plugin_basename ) {
20 if ( ! function_exists( 'is_plugin_active' ) ) {
21 include_once ABSPATH . 'wp-admin/includes/plugin.php';
22 }
23
24 return is_plugin_active( $plugin_basename );
25 }
26
27 public static function get_tax( $tax = '' ) {
28 global $wp_query;
29
30 if ( is_tax( 'knowledge_base' ) ) {
31 $_taxes = $wp_query->tax_query->queried_terms;
32 if ( array_key_exists( 'doc_category', $_taxes ) ) {
33 $tax = 'doc_category';
34 } else {
35 $tax = 'knowledge_base';
36 }
37 } elseif ( is_tax( 'doc_category' ) ) {
38 $tax = 'doc_category';
39 } elseif ( is_tax( 'doc_tag' ) ) {
40 $tax = 'doc_tag';
41 }
42
43 return $tax;
44 }
45
46 public function is_templates() {
47 global $wp_query;
48 $slug = betterdocs()->settings->get( 'encyclopedia_root_slug', 'encyclopedia' );
49
50 $tax = $this->get_tax();
51 if ( is_post_type_archive( 'docs' ) || $tax === 'knowledge_base' || $tax === 'doc_category' || $tax === 'doc_tag' || is_singular( 'docs' ) || is_tax( 'glossaries' ) ) {
52 return true;
53 }
54
55 if ( isset( $wp_query->query['pagename'] ) && $wp_query->query['pagename'] === $slug ) {
56 return true;
57 }
58
59 return false;
60 }
61
62 public function is_el_templates() {
63 $_return_val = betterdocs()->editor->get( 'elementor' )->is_templates();
64
65 if ( $_return_val !== null ) {
66 return $_return_val;
67 }
68
69 $this->is_templates();
70 }
71
72 /**
73 * Which tab to show.
74 *
75 * 1. Drag and Drop UI
76 * 2. Post List UI
77 *
78 * * 1. dnd
79 * * 2. classic
80 *
81 * look into views/admin/docs-ui directory to know more.
82 *
83 * @return string
84 */
85 public static function admin_tab() {
86 $admin_ui = 'grid';
87 if ( isset( $_GET['mode'], $_GET['page'] ) && $_GET['page'] === 'betterdocs-admin' && ! empty( $_GET['mode'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Missing
88 $admin_ui = $_GET['mode'] === 'grid' ? 'grid' : 'list'; // phpcs:ignore WordPress.Security.NonceVerification.Missing
89 }
90
91 return $admin_ui;
92 }
93
94 public static function is_active( $prev, $current, $class = 'active' ) {
95 if ( $current == $prev ) {
96 return $class;
97 }
98
99 return '';
100 }
101
102 public function get_users( $args ) {
103 $cache_key = 'betterdocs_cache_admin_user_roles';
104 $users = betterdocs()->database->get_cache( $cache_key );
105
106 if ( false === $users ) {
107 $users = get_users( $args );
108 betterdocs()->database->set_cache( $cache_key, $users );
109 }
110
111 return $users;
112 }
113
114 /**
115 * Normalize Menu Array
116 * Menu creator helper
117 *
118 * @since 2.5.0
119 *
120 * @param string $title
121 * @param string $slug
122 * @param string $cap
123 * @param array $callback
124 *
125 * @return array
126 */
127 public static function normalize_menu( $title, $slug, $cap = 'edit_docs', $callback = null, $optional = [] ) {
128 $args = [
129 'page_title' => $title,
130 'menu_title' => $title,
131 'capability' => $cap,
132 'menu_slug' => $slug
133 ];
134
135 if ( $callback != null ) {
136 $args['callback'] = $callback;
137 }
138
139 return wp_parse_args( $optional, $args );
140 }
141
142 /**
143 * Check if the current theme is a block theme.
144 *
145 * @since x.x.x
146 * @return bool
147 */
148 public function current_theme_is_fse_theme() {
149 if ( function_exists( 'wp_is_block_theme' ) ) {
150 return (bool) wp_is_block_theme();
151 }
152 if ( function_exists( 'gutenberg_is_fse_theme' ) ) {
153 return (bool) gutenberg_is_fse_theme();
154 }
155
156 return false;
157 }
158
159 protected static function is_assoc_array( $array ) {
160 return array_keys( $array ) !== range( 0, count( $array ) - 1 );
161 }
162
163 public static function merge( &$array1, &$array2 ) {
164 $merged = $array1;
165
166 foreach ( $array2 as $key => &$value ) {
167 if ( is_array( $value ) && self::is_assoc_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
168 $merged[ $key ] = self::merge( $merged[ $key ], $value );
169 } elseif ( is_array( $value ) && isset( $merged[ $key ] ) && is_array( $merged[ $key ] ) ) {
170 $merged[ $key ] = array_merge( $merged[ $key ], $value );
171 } else {
172 $merged[ $key ] = $value;
173 }
174 }
175
176 return $merged;
177 }
178
179 public static function get_custom_excerpt( $content, $numOfWords ) {
180 $content = strip_shortcodes( $content );
181 $content = wp_strip_all_tags( $content );
182 $words = explode( ' ', $content );
183 $excerptWords = array_slice( $words, 0, $numOfWords );
184 $excerpt = implode( ' ', $excerptWords );
185 if ( count( $words ) > $numOfWords ) {
186 $excerpt .= '...';
187 }
188 return $excerpt;
189 }
190
191 /**
192 * Get current language from various multilingual plugins
193 *
194 * @return string|null Current language code
195 */
196 public static function get_current_language() {
197 $current_language = null;
198
199 // WPML Support
200 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
201 global $sitepress;
202 if ( $sitepress && $sitepress->is_setup_complete() ) {
203 $current_language = defined( 'ICL_LANGUAGE_CODE' ) ? ICL_LANGUAGE_CODE : $sitepress->get_current_language();
204 }
205 }
206 // Polylang Support
207 elseif ( function_exists( 'pll_current_language' ) ) {
208 $current_language = pll_current_language();
209 }
210 // qTranslate-X Support
211 elseif ( function_exists( 'qtranxf_getLanguage' ) ) {
212 $current_language = qtranxf_getLanguage();
213 }
214 // Weglot Support
215 elseif ( function_exists( 'weglot_get_current_language' ) ) {
216 $current_language = weglot_get_current_language();
217 }
218 // TranslatePress Support
219 elseif ( class_exists( 'TRP_Translate_Press' ) && function_exists( 'trp_get_current_language' ) ) {
220 $current_language = trp_get_current_language();
221 }
222
223 return $current_language;
224 }
225
226 /**
227 * Check if any multilingual plugin is active
228 *
229 * @return bool
230 */
231 public static function is_multilingual_active() {
232 return is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ||
233 function_exists( 'pll_current_language' ) ||
234 function_exists( 'qtranxf_getLanguage' ) ||
235 function_exists( 'weglot_get_current_language' ) ||
236 ( class_exists( 'TRP_Translate_Press' ) && function_exists( 'trp_get_current_language' ) );
237 }
238
239 /**
240 * Check if we should apply language filtering
241 * Only apply on frontend or when specifically requested
242 *
243 * @return bool
244 */
245 public static function should_apply_language_filtering() {
246 // Don't apply language filtering in admin context unless it's a frontend request
247 if ( is_admin() ) {
248 // Allow language filtering for REST API requests that are frontend-facing
249 if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
250 // Check if this is a frontend REST request (not admin)
251 $request_uri = $_SERVER['REQUEST_URI'] ?? '';
252 // Don't filter admin REST requests for glossaries management
253 if ( strpos( $request_uri, '/wp/v2/glossaries' ) !== false ) {
254 return false; // Don't filter admin glossaries management
255 }
256 }
257 return false; // Don't filter other admin requests
258 }
259
260 // Apply filtering on frontend
261 return true;
262 }
263
264 /**
265 * Get current admin language for multilingual sites
266 * This is specifically for admin context where we need to detect
267 * the language being used for editing terms/posts
268 *
269 * @return string|null Current admin language code
270 */
271 public static function get_current_admin_language() {
272 $current_language = null;
273
274 // WPML Support - Admin language detection
275 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
276 global $sitepress;
277 if ( $sitepress && $sitepress->is_setup_complete() ) {
278 // For term editing, check if we have a specific term language
279 if ( isset( $_GET['tag_ID'] ) && function_exists( 'wpml_get_language_information' ) ) {
280 $term_info = wpml_get_language_information( null, (int) $_GET['tag_ID'] );
281 if ( ! is_wp_error( $term_info ) && $term_info && isset( $term_info['language_code'] ) ) {
282 $current_language = $term_info['language_code'];
283 }
284
285 }
286
287 // Check for language parameter in URL
288 if ( ! $current_language && isset( $_GET['lang'] ) ) {
289 $current_language = sanitize_text_field( $_GET['lang'] );
290 }
291
292 // Fallback to admin language or current language
293 if ( ! $current_language ) {
294 $current_language = defined( 'ICL_LANGUAGE_CODE' ) ? ICL_LANGUAGE_CODE : $sitepress->get_current_language();
295 }
296 }
297 }
298 // Polylang Support - Admin language detection
299 elseif ( function_exists( 'pll_current_language' ) ) {
300 // For term editing, get language from term ID
301 if ( isset( $_GET['tag_ID'] ) && function_exists( 'pll_get_term_language' ) ) {
302 $term_lang = pll_get_term_language( (int) $_GET['tag_ID'] );
303 if ( $term_lang ) {
304 $current_language = $term_lang;
305 }
306 }
307
308 // Check for language parameter in URL
309 if ( ! $current_language && isset( $_GET['lang'] ) ) {
310 $current_language = sanitize_text_field( $_GET['lang'] );
311 }
312
313 // Fallback to current admin language
314 if ( ! $current_language ) {
315 $current_language = pll_current_language( 'slug' );
316 }
317 }
318 // Other multilingual plugins
319 elseif ( function_exists( 'qtranxf_getLanguage' ) ) {
320 $current_language = qtranxf_getLanguage();
321 }
322 elseif ( function_exists( 'weglot_get_current_language' ) ) {
323 $current_language = weglot_get_current_language();
324 }
325 elseif ( class_exists( 'TRP_Translate_Press' ) && function_exists( 'trp_get_current_language' ) ) {
326 $current_language = trp_get_current_language();
327 }
328
329 return $current_language;
330 }
331
332 /**
333 * Generate language-specific meta key for category ordering
334 * Always falls back to base key if language-specific key doesn't exist
335 *
336 * @param string $base_key The base meta key (e.g., 'doc_category_order')
337 * @param string|null $language Language code, if null will auto-detect
338 * @return string Language-specific meta key or base key as fallback
339 */
340 public static function get_language_specific_meta_key( $base_key, $language = null ) {
341 // If no multilingual plugin is active, return the base key
342 if ( ! self::is_multilingual_active() ) {
343 return $base_key;
344 }
345
346 // Get current admin language if not provided
347 if ( $language === null ) {
348 $language = self::get_current_admin_language();
349 }
350
351 // If no language detected, return base key for backward compatibility
352 if ( ! $language ) {
353 return $base_key;
354 }
355
356 // Always return base key for now - we'll handle fallback in the query functions
357 // This ensures compatibility without requiring migration
358 return $base_key;
359 }
360
361 /**
362 * Get the appropriate meta key with fallback logic
363 * This function checks if language-specific meta exists, if not falls back to base key
364 *
365 * @param string $base_key The base meta key
366 * @param int $term_id The term ID to check
367 * @param string|null $language Language code
368 * @return string The meta key to use
369 */
370 public static function get_meta_key_with_fallback( $base_key, $term_id = null, $language = null ) {
371 // If no multilingual plugin is active, return the base key
372 if ( ! self::is_multilingual_active() ) {
373 return $base_key;
374 }
375
376 // Get current admin language if not provided
377 if ( $language === null ) {
378 $language = self::get_current_admin_language();
379 }
380
381 // If no language detected, return base key
382 if ( ! $language ) {
383 return $base_key;
384 }
385
386 $lang_meta_key = $base_key . '_' . $language;
387
388 // If we have a specific term ID, check if language-specific meta exists
389 if ( $term_id ) {
390 $lang_value = get_term_meta( $term_id, $lang_meta_key, true );
391 if ( ! empty( $lang_value ) ) {
392 return $lang_meta_key;
393 }
394 // Fall back to base key if language-specific doesn't exist
395 return $base_key;
396 }
397
398 // For queries without specific term ID, we need to check if ANY terms have language-specific meta
399 global $wpdb;
400 $has_lang_meta = $wpdb->get_var( $wpdb->prepare(
401 "SELECT COUNT(*) FROM {$wpdb->termmeta} tm
402 INNER JOIN {$wpdb->term_taxonomy} tt ON tm.term_id = tt.term_id
403 WHERE tm.meta_key = %s AND tt.taxonomy = 'doc_category' AND tm.meta_value != ''",
404 $lang_meta_key
405 ) );
406
407 // If language-specific meta exists for some terms, use it (terms without it will have empty values)
408 // Otherwise, fall back to base key
409 return $has_lang_meta > 0 ? $lang_meta_key : $base_key;
410 }
411
412 /**
413 * Migrate existing category orders to language-specific meta keys
414 * This should be called when a multilingual plugin is activated
415 *
416 * @param string $base_key The base meta key (e.g., 'doc_category_order')
417 * @param string $taxonomy The taxonomy to migrate
418 * @return bool Success status
419 */
420 public static function migrate_category_orders_to_multilingual( $base_key = 'doc_category_order', $taxonomy = 'doc_category' ) {
421 // Only run if multilingual plugin is active
422 if ( ! self::is_multilingual_active() ) {
423 return false;
424 }
425
426 global $wpdb;
427
428 // Get all terms with the base meta key
429 $terms_with_order = $wpdb->get_results( $wpdb->prepare(
430 "SELECT tm.term_id, tm.meta_value, t.slug
431 FROM {$wpdb->termmeta} tm
432 INNER JOIN {$wpdb->terms} t ON tm.term_id = t.term_id
433 INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
434 WHERE tm.meta_key = %s AND tt.taxonomy = %s",
435 $base_key,
436 $taxonomy
437 ) );
438
439 if ( empty( $terms_with_order ) ) {
440 return true; // Nothing to migrate
441 }
442
443 // Get available languages
444 $languages = self::get_available_languages();
445
446 if ( empty( $languages ) ) {
447 return false; // No languages found
448 }
449
450 // Migrate orders for each language
451 foreach ( $languages as $language ) {
452 $language_meta_key = $base_key . '_' . $language;
453
454 foreach ( $terms_with_order as $term_data ) {
455 // Check if language-specific meta already exists
456 $existing_value = get_term_meta( $term_data->term_id, $language_meta_key, true );
457
458 if ( empty( $existing_value ) ) {
459 // Copy the base order to language-specific key
460 update_term_meta( $term_data->term_id, $language_meta_key, $term_data->meta_value );
461 }
462 }
463 }
464
465 return true;
466 }
467
468 /**
469 * Migrate existing document orders to language-specific meta keys
470 * This should be called when a multilingual plugin is activated
471 *
472 * @param string $base_key The base meta key (e.g., '_docs_order')
473 * @param string $taxonomy The taxonomy to migrate
474 * @return bool Success status
475 */
476 public static function migrate_docs_orders_to_multilingual( $base_key = '_docs_order', $taxonomy = 'doc_category' ) {
477 // Only run if multilingual plugin is active
478 if ( ! self::is_multilingual_active() ) {
479 return false;
480 }
481
482 global $wpdb;
483
484 // Get all terms with the base meta key for document ordering
485 $terms_with_docs_order = $wpdb->get_results( $wpdb->prepare(
486 "SELECT tm.term_id, tm.meta_value, t.slug
487 FROM {$wpdb->termmeta} tm
488 INNER JOIN {$wpdb->terms} t ON tm.term_id = t.term_id
489 INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
490 WHERE tm.meta_key = %s AND tt.taxonomy = %s AND tm.meta_value != ''",
491 $base_key,
492 $taxonomy
493 ) );
494
495 if ( empty( $terms_with_docs_order ) ) {
496 return true; // Nothing to migrate
497 }
498
499 // Get available languages
500 $languages = self::get_available_languages();
501
502 if ( empty( $languages ) ) {
503 return false; // No languages found
504 }
505
506 // Migrate document orders for each language
507 foreach ( $languages as $language ) {
508 $language_meta_key = $base_key . '_' . $language;
509
510 foreach ( $terms_with_docs_order as $term_data ) {
511 // Check if language-specific meta already exists
512 $existing_value = get_term_meta( $term_data->term_id, $language_meta_key, true );
513
514 if ( empty( $existing_value ) ) {
515 // Copy the base document order to language-specific key
516 update_term_meta( $term_data->term_id, $language_meta_key, $term_data->meta_value );
517 }
518 }
519 }
520
521 return true;
522 }
523
524 /**
525 * Migrate both category and document orders to multilingual format
526 * This is a convenience method that runs both migrations
527 *
528 * @return bool Success status
529 */
530 public static function migrate_all_orders_to_multilingual() {
531 $category_result = self::migrate_category_orders_to_multilingual();
532 $docs_result = self::migrate_docs_orders_to_multilingual();
533
534 return $category_result && $docs_result;
535 }
536
537 /**
538 * Get available languages from multilingual plugins
539 *
540 * @return array Array of language codes
541 */
542 public static function get_available_languages() {
543 $languages = [];
544
545 // WPML Support
546 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
547 global $sitepress;
548 if ( $sitepress && $sitepress->is_setup_complete() ) {
549 $active_languages = $sitepress->get_active_languages();
550 if ( is_array( $active_languages ) ) {
551 $languages = array_keys( $active_languages );
552 }
553 }
554 }
555 // Polylang Support
556 elseif ( function_exists( 'pll_languages_list' ) ) {
557 $languages = pll_languages_list();
558 }
559
560 return $languages;
561 }
562
563 public static function get_current_letter_docs( $current_letter, $limit = '' ) {
564 global $wpdb;
565
566 // Check if the encyclopedia_prefix parameter is set
567
568 $encyclopeia_suorce = betterdocs()->settings->get( 'encyclopedia_source', 'docs' );
569 $enable_glossaries = betterdocs()->settings->get( 'enable_glossaries', false );
570 $encyclopedia_root_slug = betterdocs()->settings->get( 'encyclopedia_root_slug', 'encyclopdia' );
571
572 // if($enable_glossaries && $encyclopeia_suorce === 'glossaries'){
573 if ( $enable_glossaries && $encyclopeia_suorce === 'glossaries' ) {
574 $lang_join = '';
575 $lang_where = '';
576
577 // Add language filtering if multilingual plugin is active and we should apply filtering
578 $current_language = self::get_current_language();
579 if ( $current_language && self::is_multilingual_active() && self::should_apply_language_filtering() ) {
580 // For WPML, use icl_translations table
581 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
582 $lang_join = " LEFT JOIN {$wpdb->prefix}icl_translations icl_t ON icl_t.element_id = t.term_id AND icl_t.element_type = 'tax_glossaries'";
583 $lang_where = " AND (icl_t.language_code = '$current_language' OR icl_t.language_code IS NULL)";
584 }
585 // For Polylang, use term_relationships with language taxonomy
586 elseif ( function_exists( 'pll_current_language' ) ) {
587 $lang_join = " LEFT JOIN {$wpdb->term_relationships} tr ON t.term_id = tr.object_id LEFT JOIN {$wpdb->term_taxonomy} tt_lang ON tr.term_taxonomy_id = tt_lang.term_taxonomy_id AND tt_lang.taxonomy = 'language' LEFT JOIN {$wpdb->terms} t_lang ON tt_lang.term_id = t_lang.term_id";
588 $lang_where = " AND (t_lang.slug = '$current_language' OR t_lang.slug IS NULL)";
589 }
590 }
591
592 $query = "
593 SELECT
594 t.term_id,
595 t.name AS post_title,
596 t.slug as slug,
597 '' AS post_excerpt,
598 CONCAT('" . get_home_url() . "/$encyclopedia_root_slug/', t.slug) AS permalink,
599 tt.description AS post_content,
600 JSON_OBJECT(
601 'status', COALESCE(MAX(CASE WHEN m.meta_key = 'status' THEN m.meta_value END), ''),
602 'glossary_term_description', COALESCE(MAX(CASE WHEN m.meta_key = 'glossary_term_description' THEN m.meta_value END), '')
603 ) AS meta_data
604 FROM
605 {$wpdb->terms} t
606 INNER JOIN
607 {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
608 LEFT JOIN
609 {$wpdb->termmeta} m ON t.term_id = m.term_id
610 $lang_join
611 WHERE
612 tt.taxonomy = 'glossaries'
613 AND
614 SUBSTRING(t.name, 1, 1) = %s
615 $lang_where
616 GROUP BY
617 t.term_id
618 ORDER BY
619 t.name ASC
620 $limit
621 ";
622 } else {
623 $lang_join = '';
624 $lang_where = '';
625
626 // Add language filtering for docs if multilingual plugin is active and we should apply filtering
627 $current_language = self::get_current_language();
628 if ( $current_language && self::is_multilingual_active() && self::should_apply_language_filtering() ) {
629 // For WPML, use icl_translations table
630 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
631 $lang_join = " LEFT JOIN {$wpdb->prefix}icl_translations icl_t ON icl_t.element_id = {$wpdb->posts}.ID AND icl_t.element_type = 'post_docs'";
632 $lang_where = " AND (icl_t.language_code = '$current_language' OR icl_t.language_code IS NULL)";
633 }
634 // For Polylang, use term_relationships with language taxonomy
635 elseif ( function_exists( 'pll_current_language' ) ) {
636 $lang_join = " LEFT JOIN {$wpdb->term_relationships} tr ON {$wpdb->posts}.ID = tr.object_id LEFT JOIN {$wpdb->term_taxonomy} tt_lang ON tr.term_taxonomy_id = tt_lang.term_taxonomy_id AND tt_lang.taxonomy = 'language' LEFT JOIN {$wpdb->terms} t_lang ON tt_lang.term_id = t_lang.term_id";
637 $lang_where = " AND (t_lang.slug = '$current_language' OR t_lang.slug IS NULL)";
638 }
639 }
640
641 $query = "
642 SELECT ID, post_title, post_excerpt, guid, post_content
643 FROM {$wpdb->posts}
644 $lang_join
645 WHERE post_type = 'docs'
646 AND post_status = 'publish'
647 AND SUBSTRING(post_title, 1, 1) = %s
648 $lang_where
649 ORDER BY post_date DESC
650 $limit
651 ";
652 }
653
654 $current_letter_docs = $wpdb->get_results( $wpdb->prepare( $query, $current_letter ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
655
656 return $current_letter_docs;
657 }
658
659 public static function docs_sort_by_letter( $limit = 10 ) {
660 global $wpdb;
661 $enable_non_latin = betterdocs()->settings->get( 'encyclopedia_enable_non_latin' );
662 $script = betterdocs()->settings->get( 'encyclopedia_non_latin_option' );
663 $letters = Helper::get_character_range( $enable_non_latin, $script );
664
665 $docs_by_letter = [];
666 $encyclopeia_suorce = betterdocs()->settings->get( 'encyclopedia_source', 'docs' );
667 $enable_glossaries = betterdocs()->settings->get( 'enable_glossaries', false );
668
669 foreach ( $letters as $letter ) {
670 $posts = self::get_current_letter_docs( $letter, "LIMIT $limit" );
671
672 if ( is_array( $posts ) && ! empty( $posts ) ) {
673 foreach ( $posts as $post ) {
674 $description = isset($post['meta_data']) ? \json_decode( $post['meta_data'], true ) : '';
675 $glossary_term_description = $description['glossary_term_description'] ?? '';
676
677 // Remove any <p> tags or other unwanted HTML tags
678 $glossary_term_description = strip_tags( $glossary_term_description );
679 $post_excerpt = strip_tags( $post['post_excerpt'] ?? '' );
680
681 // Prepare post data
682 if ( $enable_glossaries && $encyclopeia_suorce === 'glossaries' ) {
683 // For glossaries
684 $post_data = [
685 'id' => $post['term_id'] ?? '',
686 'post_title' => $post['post_title'] ?? '',
687 'post_excerpt' => ! empty( $post_excerpt )
688 ? $post_excerpt
689 : ( ! empty( $glossary_term_description )
690 ? self::get_custom_excerpt( $glossary_term_description, 15 )
691 : self::get_custom_excerpt( strip_tags( $post['post_content'] ?? '' ), 15 ) ),
692 'permalink' => isset( $post['slug'] ) ? get_term_link( $post['slug'], 'glossaries' ) : ''
693 ];
694 } else {
695 // For docs
696 $post_data = [
697 'id' => $post['ID'] ?? '',
698 'post_title' => $post['post_title'] ?? '',
699 'post_excerpt' => ! empty( $post_excerpt )
700 ? $post_excerpt
701 : self::get_custom_excerpt( strip_tags( $post['post_content'] ?? '' ), 15 ),
702 'permalink' => isset( $post['ID'] ) ? get_the_permalink( $post['ID'] ) : ''
703 ];
704 }
705
706 $docs_by_letter[$letter][] = $post_data;
707 }
708 }
709 }
710
711 return $docs_by_letter;
712 }
713
714 public static function get_glossaries() {
715 global $wpdb;
716
717 $lang_join = '';
718 $lang_where = '';
719
720 // Add language filtering if multilingual plugin is active and we should apply filtering
721 $current_language = self::get_current_language();
722 if ( $current_language && self::is_multilingual_active() && self::should_apply_language_filtering() ) {
723 // For WPML, use icl_translations table
724 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
725 $lang_join = " LEFT JOIN {$wpdb->prefix}icl_translations icl_t ON icl_t.element_id = t.term_id AND icl_t.element_type = 'tax_glossaries'";
726 $lang_where = " AND (icl_t.language_code = '$current_language' OR icl_t.language_code IS NULL)";
727 }
728 // For Polylang, use term_relationships with language taxonomy
729 elseif ( function_exists( 'pll_current_language' ) ) {
730 $lang_join = " LEFT JOIN {$wpdb->term_relationships} tr ON t.term_id = tr.object_id LEFT JOIN {$wpdb->term_taxonomy} tt_lang ON tr.term_taxonomy_id = tt_lang.term_taxonomy_id AND tt_lang.taxonomy = 'language' LEFT JOIN {$wpdb->terms} t_lang ON tt_lang.term_id = t_lang.term_id";
731 $lang_where = " AND (t_lang.slug = '$current_language' OR t_lang.slug IS NULL)";
732 }
733 }
734
735 $query = "
736 SELECT t.name
737 FROM {$wpdb->terms} t
738 INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
739 $lang_join
740 WHERE tt.taxonomy = 'glossaries'
741 $lang_where
742 ORDER BY t.name ASC
743 ";
744
745 $glossaries = $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
746
747 return $glossaries;
748 }
749
750 /**
751 * Determine live search template layout, when live search is not selected from customizer(this will work when live search template is not selected from customizer)
752 *
753 * @param string $layout
754 * @return string $layout
755 */
756 public static function determine_search_layout( $layout ) {
757 if ( $layout ) {
758 return $layout;
759 }
760
761 $search_layout = betterdocs()->customizer->defaults->get( 'betterdocs_search_layout_select' );
762 $docs_layout = betterdocs()->customizer->defaults->get( 'betterdocs_docs_layout_select' );
763 $archive_page_layout = betterdocs()->customizer->defaults->get( 'betterdocs_archive_layout_select' );
764 $single_layout = betterdocs()->customizer->defaults->get( 'betterdocs_single_layout_select' );
765
766 if ( is_post_type_archive( 'docs' ) ) {
767 if ( $docs_layout != "layout-7" && ! $search_layout ) {
768 $layout = 'layout-1';
769 } else if ( $docs_layout == 'layout-7' && ! $search_layout ) {
770 $layout = 'layout-2';
771 }
772 } else if ( is_tax( 'doc_tag' ) && ! $search_layout ) {
773 $layout = 'layout-1';
774 } else if ( is_tax( 'doc_category' ) ) {
775 if ( $archive_page_layout != 'layout-7' && $archive_page_layout != 'layout-8' && ! $search_layout ) {
776 $layout = 'layout-1';
777 } else if ( ( $archive_page_layout == 'layout-7' && ! $search_layout ) || ( $archive_page_layout == 'layout-8' && ! $search_layout ) ) {
778 $layout = 'layout-2';
779 }
780 } else if ( is_singular( 'docs' ) ) {
781 if ( $single_layout != 'layout-8' && $single_layout != 'layout-9' && ! $search_layout ) {
782 $layout = 'layout-1';
783 } else if ( ( $single_layout == 'layout-8' && ! $search_layout ) || ( $single_layout == 'layout-9' && ! $search_layout ) ) {
784 $layout = 'layout-2';
785 }
786 }
787
788 return $layout;
789 }
790 public static function mb_ord_fallback( $char ) {
791 $code = unpack( 'N', mb_convert_encoding( $char, 'UCS-4BE', 'UTF-8' ) );
792 return $code[1];
793 }
794
795 public static function mb_chr_fallback( $code ) {
796 return mb_convert_encoding( pack( 'N', $code ), 'UTF-8', 'UCS-4BE' );
797 }
798
799 public static function unicodeRange( $start, $end ) {
800 $range = [];
801 for ( $i = self::mb_ord_fallback( $start ); $i <= self::mb_ord_fallback( $end ); $i++ ) {
802 $range[] = self::mb_chr_fallback( $i );
803 }
804 return $range;
805 }
806
807 public static function get_character_range( $enable_non_latin, $script ) {
808 if ( $enable_non_latin ) {
809 switch ( $script ) {
810 case 'arabic':
811 return self::unicodeRange( 'ء', 'ي' );
812 case 'cyrillic':
813 return self::unicodeRange( 'А', 'Я' );
814 case 'hebrew':
815 return self::unicodeRange( 'א', 'ת' );
816 case 'greek':
817 return self::unicodeRange( 'Α', 'Ω' );
818 default:
819 return range( 'A', 'Z' );
820 }
821 }
822
823 return range( 'A', 'Z' );
824 }
825
826 public static function get_the_top_most_parent( $term_id ) {
827 while ( $term_id != 0 ) {
828 $parent_id = wp_get_term_taxonomy_parent_id( $term_id, 'doc_category' );
829
830 if ( $parent_id == 0 ) {
831 break;
832 }
833
834 $term_id = $parent_id;
835 }
836 return $term_id;
837 }
838
839 public static function get_highest_docs_term() {
840 $terms = get_terms( [
841 'taxonomy' => 'doc_category', // Change to your desired taxonomy
842 'hide_empty' => true, // Only show terms with posts
843 'orderby' => 'count', // Order by post count
844 'order' => 'DESC', // Descending order
845 'number' => 1 // Get only the top term
846 ] );
847 return isset( $terms[0] ) ? $terms[0] : [];
848 }
849
850 public static function delete_specific_faq_posts_by_faq_category( $term_id ) {
851 $args = [
852 'post_type' => 'betterdocs_faq',
853 'posts_per_page' => -1,
854 'tax_query' => [
855 [
856 'taxonomy' => 'betterdocs_faq_category',
857 'field' => 'id',
858 'terms' => $term_id,
859 'operator' => 'IN'
860 ]
861 ],
862 'fields' => 'ids'
863 ];
864
865 $query = new \WP_Query( $args );
866
867 if ( $query->have_posts() ) {
868 foreach ( $query->posts as $doc_id ) {
869 wp_delete_post( $doc_id, true );
870 }
871 }
872 }
873
874 /**
875 * Function To Normalize Repeater Field For Quick Builder
876 *
877 * @param array $fields
878 * @param array $include_field_keys
879 *
880 * @return array
881 */
882 public static function normalize_repeater_field( $fields, $include_field_keys = [] ) {
883 if( empty( $include_field_keys ) ) {
884 return $fields;
885 }
886
887 $normalized_fields = [];
888
889 foreach( $fields as $field ) {
890 foreach( $include_field_keys as $field_key ) {
891 if( ! isset( $normalized_fields[$field_key] ) ) {
892 $normalized_fields[$field_key] = isset( $field[$field_key] ) && ! empty( $field[$field_key] ) ? $field[$field_key] : [];
893 } else {
894 array_push( $normalized_fields[$field_key], ...( isset( $field[$field_key] ) && ! empty( $field[$field_key] ) ? $field[$field_key] : [] ) );
895 $normalized_fields[$field_key] = array_unique( $normalized_fields[$field_key] );
896 }
897 }
898 }
899
900 return $normalized_fields;
901 }
902
903 public static function get_local_plugin_data( $basename = '' ) {
904 if ( empty( $basename ) ) {
905 return false;
906 }
907
908 if ( !function_exists( 'get_plugins' ) ) {
909 include_once ABSPATH . 'wp-admin/includes/plugin.php';
910 }
911
912 $plugins = get_plugins();
913
914 if ( !isset( $plugins[ $basename ] ) ) {
915 return false;
916 }
917
918 return $plugins[ $basename ];
919 }
920
921 /**
922 * Get default file icon based on programming language
923 *
924 * @param string $language Programming language identifier
925 * @return string Emoji icon for the language
926 */
927 public static function get_file_icon_by_language( $language ) {
928 $icons = [
929 'javascript' => '📄',
930 'typescript' => '📘',
931 'jsx' => '⚛️',
932 'tsx' => '⚛️',
933 'html' => '🌐',
934 'css' => '🎨',
935 'scss' => '🎨',
936 'sass' => '🎨',
937 'less' => '🎨',
938 'php' => '🐘',
939 'python' => '🐍',
940 'java' => '',
941 'csharp' => '🔷',
942 'cpp' => '⚙️',
943 'c' => '⚙️',
944 'ruby' => '💎',
945 'go' => '🐹',
946 'rust' => '🦀',
947 'swift' => '🦉',
948 'kotlin' => '🎯',
949 'sql' => '🗃️',
950 'json' => '📋',
951 'yaml' => '📋',
952 'xml' => '📄',
953 'markdown' => '📝',
954 'bash' => '💻',
955 'shell' => '💻',
956 'powershell' => '💻',
957 'dockerfile' => '🐳',
958 ];
959
960 return isset( $icons[$language] ) ? $icons[$language] : '📄';
961 }
962
963 /**
964 * Check if AI Chatbot is enabled
965 *
966 * @return bool
967 */
968 public function is_ai_chatbot_enabled() {
969 $chatbot_active = is_plugin_active( 'betterdocs-ai-chatbot/betterdocs-ai-chatbot.php' );
970 $chatbot_license_valid = get_option( 'betterdocs_chatbot_software__license_status' ) === 'valid';
971 $chatbot_enabled = betterdocs()->settings->get( 'enable_ai_chatbot', false );
972
973 // AI Search Suggestions are enabled if all conditions are met
974 return $chatbot_active && $chatbot_license_valid && $chatbot_enabled;
975 }
976
977 /**
978 * Check if tags are enabled and post has tags
979 *
980 * @return bool
981 */
982 public function is_tag_enabled() {
983 global $post;
984 $product_terms = wp_get_object_terms( $post->ID, 'doc_tag' );
985 $enable_tags = betterdocs()->settings->get( 'enable_tags', false );
986 return ! empty( $product_terms ) && $enable_tags;
987 }
988
989 /**
990 * Check if AI Search Suggestions are enabled
991 *
992 * @return bool
993 */
994 public function is_ai_search_suggestions_enabled() {
995 $ai_search_suggestions_active = is_plugin_active( 'betterdocs-ai-search-suggestions/betterdocs-ai-search-suggestions.php' );
996 $ai_search_suggestions_license_valid = get_option( 'betterdocs_ai_search_suggestions_software__license_status' ) === 'valid';
997 $ai_search_suggestions_enabled = betterdocs()->settings->get( 'enable_ai_powered_search', false );
998
999 return $ai_search_suggestions_active && $ai_search_suggestions_license_valid && $ai_search_suggestions_enabled;
1000 }
1001
1002 /**
1003 * Get the maximum order value from the 'doc_category_order' term meta
1004 *
1005 * @return int
1006 */
1007 public static function get_max_doc_category_order_from_term_meta() {
1008 global $wpdb;
1009 $sql = $wpdb->prepare( "SELECT MAX(CAST(meta_value AS UNSIGNED)) AS max FROM {$wpdb->prefix}termmeta WHERE meta_key = %s ", 'doc_category_order');
1010 $result = $wpdb->get_var($sql);
1011 return $result;
1012 }
1013 }
1014