PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.2
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.2
4.9.2 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 All 200 releases
← All changes | includes/Editors/Elementor.php +68 -13 4.5.24.9.2 View file →
@@ -27,8 +27,9 @@
27 27 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Basic\FAQ;
28 28 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Reactions;
29 29 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Navigation;
30 30 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\CodeSnippet;
31 +use WPDeveloper\BetterDocs\Editors\Elementor\Widget\CodeSnippetTab;
31 32 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\ArchiveList;
32 33 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\PrintButton;
33 34 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\Breadcrumbs;
34 35 use WPDeveloper\BetterDocs\Editors\Elementor\Widget\ReadingTime;
@@ -204,9 +205,11 @@
204 205 } else if ( $wb->get_name() !== 'betterdocs-sidebar' ) {
205 206 $wb->add_control(
206 207 'box_per_page',
207 208 [
208 - 'label' => __( $wb->get_name() == 'betterdocs-tab-view-list' ? 'Tabs Per Page' : 'Box Per Page', 'betterdocs' ),
209 + 'label' => $wb->get_name() == 'betterdocs-tab-view-list'
210 + ? __( 'Tabs Per Page', 'betterdocs' )
211 + : __( 'Box Per Page', 'betterdocs' ),
209 212 'type' => Controls_Manager::NUMBER,
210 213 'default' => '8'
211 214 ]
212 215 );
@@ -405,8 +408,21 @@
405 408 'nested_subcategory' => 'true'
406 409 ]
407 410 ]
408 411 );
412 +
413 + $wb->add_control(
414 + 'lazy_load',
415 + [
416 + 'label' => __( 'Lazy Load Docs & Subcategories', 'betterdocs' ),
417 + 'description' => __( 'Render only the active category upfront; fetch the rest on click. Recommended for large knowledge bases when this widget is used as a sidebar.', 'betterdocs' ),
418 + 'type' => Controls_Manager::SWITCHER,
419 + 'label_on' => __( 'Yes', 'betterdocs' ),
420 + 'label_off' => __( 'No', 'betterdocs' ),
421 + 'return_value' => 'true',
422 + 'default' => ''
423 + ]
424 + );
409 425 }
410 426
411 427 if ( $wb->get_name() === 'betterdocs-category-box' ) {
412 428 $wb->add_control(
@@ -454,8 +470,21 @@
454 470 'return_value' => 'true',
455 471 'default' => ( $this->settings->get( 'archive_nested_subcategory' ) == 1 ) ? 'true' : ''
456 472 ]
457 473 );
474 +
475 + $wb->add_control(
476 + 'lazy_load',
477 + [
478 + 'label' => __( 'Lazy Load Docs & Subcategories', 'betterdocs' ),
479 + 'description' => __( 'Render only the active category upfront; fetch the rest on click (or on scroll for the Memphis sidebar layout).', 'betterdocs' ),
480 + 'type' => Controls_Manager::SWITCHER,
481 + 'label_on' => __( 'Yes', 'betterdocs' ),
482 + 'label_off' => __( 'No', 'betterdocs' ),
483 + 'return_value' => 'true',
484 + 'default' => ( $this->settings->get( 'archive_lazy_load_descendants' ) == 1 ) ? 'true' : ''
485 + ]
486 + );
458 487 }
459 488
460 489 if ( $wb->get_name() === 'betterdocs-tab-view-list' ) {
461 490 $wb->add_control(
@@ -593,17 +622,25 @@
593 622 return '';
594 623 }
595 624
596 625 public function get_kb_terms( $prettify = false, $term_id = true ) {
597 - $args = [
598 - 'taxonomy' => 'knowledge_base',
599 - 'hide_empty' => true,
600 - 'parent' => 0
601 - ];
626 + // This list populates the widget "Knowledge Bases" selector — an admin-side
627 + // config picker, not a front-end listing. It must show EVERY KB regardless of
628 + // whether it currently has content or is access-restricted for the editing
629 + // user's role. Previously it ran through terms_query()/get_terms(), so on a
630 + // regular page the Pro access-control terms_clauses filter + hide_empty pruned
631 + // the list to a subset, while the theme-builder context showed them all.
632 + // hide_empty:false keeps empty KBs; suppress_filters:true signals AccessControl
633 + // (get_terms_args) to skip its role-based terms_clauses filter, so the picker is
634 + // consistent everywhere.
635 + $terms = get_terms( [
636 + 'taxonomy' => 'knowledge_base',
637 + 'hide_empty' => false,
638 + 'parent' => 0,
639 + 'orderby' => 'name',
640 + 'suppress_filters' => true,
641 + ] );
602 642
603 - $terms_query_args = betterdocs()->query->terms_query( $args );
604 - $terms = betterdocs()->query->get_terms( $terms_query_args );
605 -
606 643 if ( is_wp_error( $terms ) ) {
607 644 return [];
608 645 }
609 646
@@ -610,9 +647,11 @@
610 647 if ( $prettify ) {
611 648 $pretty_taxonomies = [];
612 649
613 650 foreach ( $terms as $term ) {
614 - $pretty_taxonomies[$term_id ? $term->term_id : $term->slug] = $term->name;
651 + // Decode HTML entities (e.g. "&" → "&") so the select2 control
652 + // shows the real KB name instead of the escaped entity.
653 + $pretty_taxonomies[$term_id ? $term->term_id : $term->slug] = wp_specialchars_decode( $term->name, ENT_QUOTES );
615 654 }
616 655
617 656 return $pretty_taxonomies;
618 657 }
@@ -659,9 +698,10 @@
659 698 'betterdocs-elementor-category-grid' => CategoryGrid::class,
660 699 'betterdocs-elementor-category-box' => CategoryBox::class,
661 700 'betterdocs-elementor-category-slate' => CategorySlateLayout::class,
662 701 'betterdocs-faq-widget' => FAQ::class,
663 - 'betterdocs-code-snippet' => CodeSnippet::class
702 + 'betterdocs-code-snippet' => CodeSnippet::class,
703 + 'betterdocs-code-snippet-tab' => CodeSnippetTab::class
664 704 ];
665 705
666 706 return $widget_arr;
667 707 }
@@ -722,8 +762,9 @@
722 762 $assets->register( 'betterdocs-el-category-box', 'elementor/css/category-box.css' );
723 763 $assets->register( 'betterdocs-el-articles-list', 'elementor/css/articles-list.css' );
724 764 $assets->register( 'betterdocs-el-navigation', 'elementor/css/navigation.css' );
725 765 $assets->register( 'betterdocs-code-snippet', 'public/css/code-snippet.css' );
766 + $assets->register( 'betterdocs-code-snippet-tab', 'public/css/code-snippet-tab.css' );
726 767
727 768 /**
728 769 * Widget's JS
729 770 */
@@ -728,14 +769,22 @@
728 769 * Widget's JS
729 770 */
730 771 $assets->register( 'betterdocs-el-category-grid', 'elementor/js/category-grid.js', ['jquery', 'betterdocs-category-toggler'] );
731 772 $assets->register( 'betterdocs-code-snippet', 'public/js/code-snippet.js', ['jquery'] );
773 + $assets->register( 'betterdocs-code-snippet-tab', 'public/js/code-snippet-tab.js', ['jquery'] );
732 774
733 775 /**
734 776 * Localize This In Order To Know If This Block Is Arriving From Betterdocs Templates Or Not
735 777 */
736 778 betterdocs()->assets->localize( 'betterdocs-el-category-grid', 'betterdocsCategoryGridConfig', [
737 - 'is_betterdocs_templates' => betterdocs()->helper->is_templates() ? true : false
779 + 'is_betterdocs_templates' => betterdocs()->helper->is_templates() ? true : false,
780 + // Must also carry the lazy-load endpoint. wp_localize_script concatenates
781 + // every `var betterdocsCategoryGridConfig = …` for this object name and the
782 + // last assignment wins; shipping only is_betterdocs_templates here clobbered
783 + // Scripts.php's full config on Elementor pages, leaving ajax_url undefined so
784 + // loadLazyBody() bailed and sidebar lazy-load never fired.
785 + 'ajax_url' => admin_url( 'admin-ajax.php' ),
786 + 'lazy_load_action' => 'betterdocs_lazy_category_body',
738 787 ] );
739 788
740 789 if ( betterdocs()->helper->is_el_templates() == true ) {
741 790 $assets->enqueue( 'betterdocs-elementor-editor', 'elementor/css/betterdocs-el-edit.css' );
@@ -945,13 +994,19 @@
945 994 return array_merge( $templates, $local_templates );
946 995 }
947 996
948 997 public function modified_ajax_action( $ajax ) {
998 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Elementor's own ajax handler verifies nonce upstream.
949 999 if ( ! isset( $_REQUEST['actions'] ) ) {
950 1000 return;
951 1001 }
952 1002
953 - $actions = json_decode( stripslashes( $_REQUEST['actions'] ), true );
1003 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended,WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- Elementor's ajax handler verifies nonce; raw JSON is structure-validated below.
1004 + $raw_actions = wp_unslash( $_REQUEST['actions'] );
1005 + $actions = json_decode( $raw_actions, true );
1006 + if ( ! is_array( $actions ) ) {
1007 + return;
1008 + }
954 1009 $data = false;
955 1010
956 1011 foreach ( $actions as $id => $action_data ) {
957 1012 if ( ! isset( $action_data['get_template_data'] ) ) {