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

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

1,017 lines 35.0 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 = 0 ) {
564 global $wpdb;
565
566 $limit = absint( $limit );
567 $limit_sql = $limit > 0 ? $wpdb->prepare( 'LIMIT %d', $limit ) : '';
568
569 // Check if the encyclopedia_prefix parameter is set
570
571 $encyclopeia_suorce = betterdocs()->settings->get( 'encyclopedia_source', 'docs' );
572 $enable_glossaries = betterdocs()->settings->get( 'enable_glossaries', false );
573 $encyclopedia_root_slug = betterdocs()->settings->get( 'encyclopedia_root_slug', 'encyclopdia' );
574
575 // if($enable_glossaries && $encyclopeia_suorce === 'glossaries'){
576 if ( $enable_glossaries && $encyclopeia_suorce === 'glossaries' ) {
577 $lang_join = '';
578 $lang_where = '';
579
580 // Add language filtering if multilingual plugin is active and we should apply filtering
581 $current_language = self::get_current_language();
582 if ( $current_language && self::is_multilingual_active() && self::should_apply_language_filtering() ) {
583 // For WPML, use icl_translations table
584 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
585 $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'";
586 $lang_where = " AND (icl_t.language_code = '$current_language' OR icl_t.language_code IS NULL)";
587 }
588 // For Polylang, use term_relationships with language taxonomy
589 elseif ( function_exists( 'pll_current_language' ) ) {
590 $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";
591 $lang_where = " AND (t_lang.slug = '$current_language' OR t_lang.slug IS NULL)";
592 }
593 }
594
595 $query = "
596 SELECT
597 t.term_id,
598 t.name AS post_title,
599 t.slug as slug,
600 '' AS post_excerpt,
601 CONCAT('" . get_home_url() . "/$encyclopedia_root_slug/', t.slug) AS permalink,
602 tt.description AS post_content,
603 JSON_OBJECT(
604 'status', COALESCE(MAX(CASE WHEN m.meta_key = 'status' THEN m.meta_value END), ''),
605 'glossary_term_description', COALESCE(MAX(CASE WHEN m.meta_key = 'glossary_term_description' THEN m.meta_value END), '')
606 ) AS meta_data
607 FROM
608 {$wpdb->terms} t
609 INNER JOIN
610 {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
611 LEFT JOIN
612 {$wpdb->termmeta} m ON t.term_id = m.term_id
613 $lang_join
614 WHERE
615 tt.taxonomy = 'glossaries'
616 AND
617 SUBSTRING(t.name, 1, 1) = %s
618 $lang_where
619 GROUP BY
620 t.term_id
621 ORDER BY
622 t.name ASC
623 $limit_sql
624 ";
625 } else {
626 $lang_join = '';
627 $lang_where = '';
628
629 // Add language filtering for docs if multilingual plugin is active and we should apply filtering
630 $current_language = self::get_current_language();
631 if ( $current_language && self::is_multilingual_active() && self::should_apply_language_filtering() ) {
632 // For WPML, use icl_translations table
633 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
634 $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'";
635 $lang_where = " AND (icl_t.language_code = '$current_language' OR icl_t.language_code IS NULL)";
636 }
637 // For Polylang, use term_relationships with language taxonomy
638 elseif ( function_exists( 'pll_current_language' ) ) {
639 $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";
640 $lang_where = " AND (t_lang.slug = '$current_language' OR t_lang.slug IS NULL)";
641 }
642 }
643
644 $query = "
645 SELECT ID, post_title, post_excerpt, guid, post_content
646 FROM {$wpdb->posts}
647 $lang_join
648 WHERE post_type = 'docs'
649 AND post_status = 'publish'
650 AND SUBSTRING(post_title, 1, 1) = %s
651 $lang_where
652 ORDER BY post_date DESC
653 $limit_sql
654 ";
655 }
656
657 $current_letter_docs = $wpdb->get_results( $wpdb->prepare( $query, $current_letter ), ARRAY_A ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
658
659 return $current_letter_docs;
660 }
661
662 public static function docs_sort_by_letter( $limit = 10 ) {
663 global $wpdb;
664 $enable_non_latin = betterdocs()->settings->get( 'encyclopedia_enable_non_latin' );
665 $script = betterdocs()->settings->get( 'encyclopedia_non_latin_option' );
666 $letters = Helper::get_character_range( $enable_non_latin, $script );
667
668 $docs_by_letter = [];
669 $encyclopeia_suorce = betterdocs()->settings->get( 'encyclopedia_source', 'docs' );
670 $enable_glossaries = betterdocs()->settings->get( 'enable_glossaries', false );
671
672 foreach ( $letters as $letter ) {
673 $posts = self::get_current_letter_docs( $letter, $limit );
674
675 if ( is_array( $posts ) && ! empty( $posts ) ) {
676 foreach ( $posts as $post ) {
677 $description = isset($post['meta_data']) ? \json_decode( $post['meta_data'], true ) : '';
678 $glossary_term_description = $description['glossary_term_description'] ?? '';
679
680 // Remove any <p> tags or other unwanted HTML tags
681 $glossary_term_description = strip_tags( $glossary_term_description );
682 $post_excerpt = strip_tags( $post['post_excerpt'] ?? '' );
683
684 // Prepare post data
685 if ( $enable_glossaries && $encyclopeia_suorce === 'glossaries' ) {
686 // For glossaries
687 $post_data = [
688 'id' => $post['term_id'] ?? '',
689 'post_title' => $post['post_title'] ?? '',
690 'post_excerpt' => ! empty( $post_excerpt )
691 ? $post_excerpt
692 : ( ! empty( $glossary_term_description )
693 ? self::get_custom_excerpt( $glossary_term_description, 15 )
694 : self::get_custom_excerpt( strip_tags( $post['post_content'] ?? '' ), 15 ) ),
695 'permalink' => isset( $post['slug'] ) ? get_term_link( $post['slug'], 'glossaries' ) : ''
696 ];
697 } else {
698 // For docs
699 $post_data = [
700 'id' => $post['ID'] ?? '',
701 'post_title' => $post['post_title'] ?? '',
702 'post_excerpt' => ! empty( $post_excerpt )
703 ? $post_excerpt
704 : self::get_custom_excerpt( strip_tags( $post['post_content'] ?? '' ), 15 ),
705 'permalink' => isset( $post['ID'] ) ? get_the_permalink( $post['ID'] ) : ''
706 ];
707 }
708
709 $docs_by_letter[$letter][] = $post_data;
710 }
711 }
712 }
713
714 return $docs_by_letter;
715 }
716
717 public static function get_glossaries() {
718 global $wpdb;
719
720 $lang_join = '';
721 $lang_where = '';
722
723 // Add language filtering if multilingual plugin is active and we should apply filtering
724 $current_language = self::get_current_language();
725 if ( $current_language && self::is_multilingual_active() && self::should_apply_language_filtering() ) {
726 // For WPML, use icl_translations table
727 if ( is_plugin_active( 'sitepress-multilingual-cms/sitepress.php' ) ) {
728 $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'";
729 $lang_where = " AND (icl_t.language_code = '$current_language' OR icl_t.language_code IS NULL)";
730 }
731 // For Polylang, use term_relationships with language taxonomy
732 elseif ( function_exists( 'pll_current_language' ) ) {
733 $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";
734 $lang_where = " AND (t_lang.slug = '$current_language' OR t_lang.slug IS NULL)";
735 }
736 }
737
738 $query = "
739 SELECT t.name
740 FROM {$wpdb->terms} t
741 INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id
742 $lang_join
743 WHERE tt.taxonomy = 'glossaries'
744 $lang_where
745 ORDER BY t.name ASC
746 ";
747
748 $glossaries = $wpdb->get_col( $query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
749
750 return $glossaries;
751 }
752
753 /**
754 * 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)
755 *
756 * @param string $layout
757 * @return string $layout
758 */
759 public static function determine_search_layout( $layout ) {
760 if ( $layout ) {
761 return $layout;
762 }
763
764 $search_layout = betterdocs()->customizer->defaults->get( 'betterdocs_search_layout_select' );
765 $docs_layout = betterdocs()->customizer->defaults->get( 'betterdocs_docs_layout_select' );
766 $archive_page_layout = betterdocs()->customizer->defaults->get( 'betterdocs_archive_layout_select' );
767 $single_layout = betterdocs()->customizer->defaults->get( 'betterdocs_single_layout_select' );
768
769 if ( is_post_type_archive( 'docs' ) ) {
770 if ( $docs_layout != "layout-7" && ! $search_layout ) {
771 $layout = 'layout-1';
772 } else if ( $docs_layout == 'layout-7' && ! $search_layout ) {
773 $layout = 'layout-2';
774 }
775 } else if ( is_tax( 'doc_tag' ) && ! $search_layout ) {
776 $layout = 'layout-1';
777 } else if ( is_tax( 'doc_category' ) ) {
778 if ( $archive_page_layout != 'layout-7' && $archive_page_layout != 'layout-8' && ! $search_layout ) {
779 $layout = 'layout-1';
780 } else if ( ( $archive_page_layout == 'layout-7' && ! $search_layout ) || ( $archive_page_layout == 'layout-8' && ! $search_layout ) ) {
781 $layout = 'layout-2';
782 }
783 } else if ( is_singular( 'docs' ) ) {
784 if ( $single_layout != 'layout-8' && $single_layout != 'layout-9' && ! $search_layout ) {
785 $layout = 'layout-1';
786 } else if ( ( $single_layout == 'layout-8' && ! $search_layout ) || ( $single_layout == 'layout-9' && ! $search_layout ) ) {
787 $layout = 'layout-2';
788 }
789 }
790
791 return $layout;
792 }
793 public static function mb_ord_fallback( $char ) {
794 $code = unpack( 'N', mb_convert_encoding( $char, 'UCS-4BE', 'UTF-8' ) );
795 return $code[1];
796 }
797
798 public static function mb_chr_fallback( $code ) {
799 return mb_convert_encoding( pack( 'N', $code ), 'UTF-8', 'UCS-4BE' );
800 }
801
802 public static function unicodeRange( $start, $end ) {
803 $range = [];
804 for ( $i = self::mb_ord_fallback( $start ); $i <= self::mb_ord_fallback( $end ); $i++ ) {
805 $range[] = self::mb_chr_fallback( $i );
806 }
807 return $range;
808 }
809
810 public static function get_character_range( $enable_non_latin, $script ) {
811 if ( $enable_non_latin ) {
812 switch ( $script ) {
813 case 'arabic':
814 return self::unicodeRange( 'ء', 'ي' );
815 case 'cyrillic':
816 return self::unicodeRange( 'А', 'Я' );
817 case 'hebrew':
818 return self::unicodeRange( 'א', 'ת' );
819 case 'greek':
820 return self::unicodeRange( 'Α', 'Ω' );
821 default:
822 return range( 'A', 'Z' );
823 }
824 }
825
826 return range( 'A', 'Z' );
827 }
828
829 public static function get_the_top_most_parent( $term_id ) {
830 while ( $term_id != 0 ) {
831 $parent_id = wp_get_term_taxonomy_parent_id( $term_id, 'doc_category' );
832
833 if ( $parent_id == 0 ) {
834 break;
835 }
836
837 $term_id = $parent_id;
838 }
839 return $term_id;
840 }
841
842 public static function get_highest_docs_term() {
843 $terms = get_terms( [
844 'taxonomy' => 'doc_category', // Change to your desired taxonomy
845 'hide_empty' => true, // Only show terms with posts
846 'orderby' => 'count', // Order by post count
847 'order' => 'DESC', // Descending order
848 'number' => 1 // Get only the top term
849 ] );
850 return isset( $terms[0] ) ? $terms[0] : [];
851 }
852
853 public static function delete_specific_faq_posts_by_faq_category( $term_id ) {
854 $args = [
855 'post_type' => 'betterdocs_faq',
856 'posts_per_page' => -1,
857 'tax_query' => [
858 [
859 'taxonomy' => 'betterdocs_faq_category',
860 'field' => 'id',
861 'terms' => $term_id,
862 'operator' => 'IN'
863 ]
864 ],
865 'fields' => 'ids'
866 ];
867
868 $query = new \WP_Query( $args );
869
870 if ( $query->have_posts() ) {
871 foreach ( $query->posts as $doc_id ) {
872 wp_delete_post( $doc_id, true );
873 }
874 }
875 }
876
877 /**
878 * Function To Normalize Repeater Field For Quick Builder
879 *
880 * @param array $fields
881 * @param array $include_field_keys
882 *
883 * @return array
884 */
885 public static function normalize_repeater_field( $fields, $include_field_keys = [] ) {
886 if( empty( $include_field_keys ) ) {
887 return $fields;
888 }
889
890 $normalized_fields = [];
891
892 foreach( $fields as $field ) {
893 foreach( $include_field_keys as $field_key ) {
894 if( ! isset( $normalized_fields[$field_key] ) ) {
895 $normalized_fields[$field_key] = isset( $field[$field_key] ) && ! empty( $field[$field_key] ) ? $field[$field_key] : [];
896 } else {
897 array_push( $normalized_fields[$field_key], ...( isset( $field[$field_key] ) && ! empty( $field[$field_key] ) ? $field[$field_key] : [] ) );
898 $normalized_fields[$field_key] = array_unique( $normalized_fields[$field_key] );
899 }
900 }
901 }
902
903 return $normalized_fields;
904 }
905
906 public static function get_local_plugin_data( $basename = '' ) {
907 if ( empty( $basename ) ) {
908 return false;
909 }
910
911 if ( !function_exists( 'get_plugins' ) ) {
912 include_once ABSPATH . 'wp-admin/includes/plugin.php';
913 }
914
915 $plugins = get_plugins();
916
917 if ( !isset( $plugins[ $basename ] ) ) {
918 return false;
919 }
920
921 return $plugins[ $basename ];
922 }
923
924 /**
925 * Get default file icon based on programming language
926 *
927 * @param string $language Programming language identifier
928 * @return string Emoji icon for the language
929 */
930 public static function get_file_icon_by_language( $language ) {
931 $icons = [
932 'javascript' => '📄',
933 'typescript' => '📘',
934 'jsx' => '⚛️',
935 'tsx' => '⚛️',
936 'html' => '🌐',
937 'css' => '🎨',
938 'scss' => '🎨',
939 'sass' => '🎨',
940 'less' => '🎨',
941 'php' => '🐘',
942 'python' => '🐍',
943 'java' => '',
944 'csharp' => '🔷',
945 'cpp' => '⚙️',
946 'c' => '⚙️',
947 'ruby' => '💎',
948 'go' => '🐹',
949 'rust' => '🦀',
950 'swift' => '🦉',
951 'kotlin' => '🎯',
952 'sql' => '🗃️',
953 'json' => '📋',
954 'yaml' => '📋',
955 'xml' => '📄',
956 'markdown' => '📝',
957 'bash' => '💻',
958 'shell' => '💻',
959 'powershell' => '💻',
960 'dockerfile' => '🐳',
961 ];
962
963 return isset( $icons[$language] ) ? $icons[$language] : '📄';
964 }
965
966 /**
967 * Check if AI Chatbot is enabled
968 *
969 * @return bool
970 */
971 public function is_ai_chatbot_enabled() {
972 $chatbot_active = is_plugin_active( 'betterdocs-ai-chatbot/betterdocs-ai-chatbot.php' );
973 $chatbot_license_valid = get_option( 'betterdocs_chatbot_software__license_status' ) === 'valid';
974 $chatbot_enabled = betterdocs()->settings->get( 'enable_ai_chatbot', false );
975
976 // AI Search Suggestions are enabled if all conditions are met
977 return $chatbot_active && $chatbot_license_valid && $chatbot_enabled;
978 }
979
980 /**
981 * Check if tags are enabled and post has tags
982 *
983 * @return bool
984 */
985 public function is_tag_enabled() {
986 global $post;
987 $product_terms = wp_get_object_terms( $post->ID, 'doc_tag' );
988 $enable_tags = betterdocs()->settings->get( 'enable_tags', false );
989 return ! empty( $product_terms ) && $enable_tags;
990 }
991
992 /**
993 * Check if AI Search Suggestions are enabled
994 *
995 * @return bool
996 */
997 public function is_ai_search_suggestions_enabled() {
998 $ai_search_suggestions_active = is_plugin_active( 'betterdocs-ai-search-suggestions/betterdocs-ai-search-suggestions.php' );
999 $ai_search_suggestions_license_valid = get_option( 'betterdocs_ai_search_suggestions_software__license_status' ) === 'valid';
1000 $ai_search_suggestions_enabled = betterdocs()->settings->get( 'enable_ai_powered_search', false );
1001
1002 return $ai_search_suggestions_active && $ai_search_suggestions_license_valid && $ai_search_suggestions_enabled;
1003 }
1004
1005 /**
1006 * Get the maximum order value from the 'doc_category_order' term meta
1007 *
1008 * @return int
1009 */
1010 public static function get_max_doc_category_order_from_term_meta() {
1011 global $wpdb;
1012 $sql = $wpdb->prepare( "SELECT MAX(CAST(meta_value AS UNSIGNED)) AS max FROM {$wpdb->prefix}termmeta WHERE meta_key = %s ", 'doc_category_order');
1013 $result = $wpdb->get_var($sql);
1014 return $result;
1015 }
1016 }
1017