PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.9.3
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.9.3
4.9.3 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 All 201 releases
← All changes | views/template-parts/nested-categories.php +89 -19 4.6.2 → 4.9.3 View file →
@@ -32,8 +32,55 @@
32 32 if ( empty( $_nested_categories ) ) {
33 33 return;
34 34 }
35 35
36 +$_nested_term_ids = wp_list_pluck( $_nested_categories, 'term_id' );
37 +if ( ! empty( $_nested_term_ids ) ) {
38 + update_termmeta_cache( $_nested_term_ids );
39 +}
40 +
41 +// Fragment cache: cache the rendered HTML of the entire nested subtree at the
42 +// outermost invocation. The active-branch highlighting is baked into the
43 +// rendered HTML server-side (see the $classes / inline-style computation
44 +// below), so the cache key must vary by the current page's identity as well as
45 +// the top-level term, caps, and kb (see the key composition further down).
46 +// (`static` at file scope doesn't persist across the recursive include, so
47 +// the depth tracker has to live on a global.)
48 +global $bd_nested_depth;
49 +if ( ! isset( $bd_nested_depth ) ) {
50 + $bd_nested_depth = 0;
51 +}
52 +$bd_is_outermost = ( $bd_nested_depth === 0 );
53 +$bd_cache_key = '';
54 +
55 +if ( $bd_is_outermost ) {
56 + $bd_can_priv = current_user_can( 'read_private_docs' ) ? 1 : 0;
57 + $bd_multi_kb = isset( $multiple_knowledge_base ) && $multiple_knowledge_base ? 1 : 0;
58 + $bd_kb_slug = isset( $kb_slug ) ? $kb_slug : '';
59 + $bd_cat_icon = isset( $category_icon ) ? (string) $category_icon : '';
60 + // Active-state highlighting is now baked into the rendered HTML (see
61 + // $classes / inline style computation below). That means the cache key
62 + // must also vary by the current page's identity, otherwise a fragment
63 + // rendered while viewing page A would be served unchanged to page B with
64 + // the wrong .active branch.
65 + $bd_queried = (int) get_queried_object_id();
66 + $bd_is_single = is_singular( 'docs' ) ? 1 : 0;
67 + $bd_version = betterdocs()->database->get_cache_version( 'betterdocs_term_counts' );
68 + $bd_icon_disc = md5( wp_json_encode( isset( $list_icon_name ) ? $list_icon_name : "" ) );
69 + $bd_show_icon = ( isset( $show_list_icon ) && $show_list_icon === false ) ? 0 : 1;
70 + $bd_order_disc = md5( wp_json_encode( array( isset( $nested_terms_query ) ? $nested_terms_query : array(), isset( $nested_docs_query_args ) ? $nested_docs_query_args : array() ) ) );
71 + $bd_cache_key = 'bd_nested_frag_' . md5( "v{$bd_version}_term{$term_id}_m{$bd_multi_kb}_k{$bd_kb_slug}_p{$bd_can_priv}_i{$bd_cat_icon}_q{$bd_queried}_s{$bd_is_single}_ic{$bd_icon_disc}_si{$bd_show_icon}_o{$bd_order_disc}" );
72 +
73 + $bd_cached = get_transient( $bd_cache_key );
74 + if ( false !== $bd_cached ) {
75 + echo $bd_cached; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
76 + return;
77 + }
78 +
79 + ob_start();
80 +}
81 +$bd_nested_depth++;
82 +
36 83 // Ensure $layout_type is set
37 84 if ( ! isset( $layout_type ) ) {
38 85 $layout_type = '';
39 86 }
@@ -46,14 +93,8 @@
46 93 )
47 94 );
48 95 }
49 96
50 -$_page_id = null;
51 -$_category_ids = [];
52 -$_is_single = false;
53 -$_is_doc_category = false;
54 -$_current_doc_category = null;
55 -
56 97 // Check if list icon should be shown
57 98 $_show_list_icon = true;
58 99 if ( isset( $show_list_icon ) && $show_list_icon === false ) {
59 100 $_show_list_icon = false;
@@ -60,8 +101,19 @@
60 101 }
61 102
62 103 $_icon = $_show_list_icon ? betterdocs()->template_helper->icon( isset( $list_icon_name ) ? $list_icon_name : 'list' ) : '';
63 104
105 +// Active-branch detection (mirrors master). Used to set .active class +
106 +// display:block on each nested-category-list <ul> that's in the user's
107 +// current branch, so the parent's body opens with the right state on
108 +// initial paint and Sleek's CSS (.betterdocs-current-category /
109 +// .betterdocs-nested-category-list.active) gets to apply its styling.
110 +$_page_id = null;
111 +$_category_ids = [];
112 +$_is_single = false;
113 +$_is_doc_category = false;
114 +$_current_doc_category = null;
115 +
64 116 if ( is_single() ) {
65 117 $_is_single = true;
66 118 $_page_id = get_the_ID();
67 119 $_category_ids = wp_get_post_terms( $_page_id, 'doc_category', [ 'fields' => 'ids' ] );
@@ -94,9 +146,14 @@
94 146
95 147 $_nested_docs_args = apply_filters( 'betterdocs_nested_docs_args', $nested_docs_query_args );
96 148
97 149 foreach ( $_nested_categories as $_nested_category ) :
98 - $classes = $_is_single && in_array( $_nested_category->term_id, $_category_ids ) || ( $_is_doc_category && in_array( $_nested_category->term_id, $_category_ids ) ) ? 'betterdocs-nested-category-list betterdocs-current-category active' : 'betterdocs-nested-category-list';
150 + $_is_in_active_branch = ( $_is_single && in_array( $_nested_category->term_id, $_category_ids ) )
151 + || ( $_is_doc_category && in_array( $_nested_category->term_id, $_category_ids ) );
152 + $_ul_classes = $_is_in_active_branch
153 + ? 'betterdocs-nested-category-list betterdocs-current-category active'
154 + : 'betterdocs-nested-category-list';
155 + $_ul_style = $_is_in_active_branch ? 'display:block;' : 'display:none;';
99 156
100 157 $_counts = betterdocs()->query->get_docs_count(
101 158 $_nested_category,
102 159 $nested_subcategory,
@@ -110,9 +167,9 @@
110 167 continue;
111 168 }
112 169
113 170 ?>
114 - <li class="betterdocs-nested-category-wrapper">
171 + <li class="betterdocs-nested-category-wrapper" data-bd-term-id="<?php echo (int) $_nested_category->term_id; ?>">
115 172 <span class="betterdocs-nested-category-title">
116 173 <?php
117 174 if ( isset( $category_icon ) && $category_icon == 'folder' ) {
118 175 betterdocs()->template_helper->icon( 'folder', true );
@@ -120,16 +177,12 @@
120 177 } else {
121 178 betterdocs()->template_helper->icon( 'arrow-right', true );
122 179 betterdocs()->template_helper->icon( 'arrow-down', true );
123 180 }
124 - /**
125 - * Icons
126 - */
127 -
128 181 ?>
129 182 <a href="#"><?php echo esc_html( $_nested_category->name ); ?></a>
130 183 </span>
131 - <ul class="<?php echo esc_attr( $classes ); ?>" style="<?php echo $_is_single && in_array( $_nested_category->term_id, $_category_ids ) || ( $_is_doc_category && in_array( $_nested_category->term_id, $_category_ids ) ) ? 'display:block;' : 'display:none;'; ?>">
184 + <ul class="<?php echo esc_attr( $_ul_classes ); ?>" style="<?php echo esc_attr( $_ul_style ); ?>">
132 185 <?php
133 186 $_nested_docs_args['term_id'] = $_nested_category->term_id;
134 187 $_nested_docs_args['term_slug'] = $_nested_category->slug;
135 188
@@ -139,22 +192,23 @@
139 192
140 193 if ( $_docs_query->have_posts() ) {
141 194 while ( $_docs_query->have_posts() ) :
142 195 $_docs_query->the_post();
143 - $_attributes = [
144 - 'href' => esc_url( get_the_permalink() )
196 + $_attrs = [
197 + 'href' => esc_url( get_the_permalink() ),
198 + 'data-bd-doc-id' => (string) get_the_ID(),
145 199 ];
146 200 if ( $_page_id === get_the_ID() && Helper::get_tax() != 'doc_category' ) {
147 - $_attributes['class'] = 'active';
201 + $_attrs['class'] = 'active';
148 202 }
203 + $_link_attributes = betterdocs()->template_helper->get_html_attributes( $_attrs );
149 204
150 - $_link_attributes = betterdocs()->template_helper->get_html_attributes( $_attributes );
151 -
152 205 echo wp_sprintf(
153 206 '<li>%s<a %s>%s</a></li>',
154 207 $_icon, //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
155 208 $_link_attributes, //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
156 - betterdocs()->template_helper->kses( get_the_title() ) //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
209 + // Same per-item filter as category-list.php (API method badges etc.).
210 + apply_filters( 'betterdocs_docs_list_item_title', betterdocs()->template_helper->kses( get_the_title() ), get_the_ID() ) //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
157 211 );
158 212 endwhile;
159 213 }
160 214
@@ -175,4 +229,20 @@
175 229 </ul>
176 230 </li>
177 231 <?php
178 232 endforeach;
233 +
234 +$bd_nested_depth--;
235 +
236 +if ( $bd_is_outermost ) {
237 + $bd_html = ob_get_clean();
238 + // Never cache an empty render. An empty buffer means every child term was
239 + // skipped ( $_counts <= 0 ), which is usually a transient data state — stale
240 + // counts mid-import, docs not yet published. The read guard treats '' as a hit
241 + // ( '' !== false ), so caching it would pin a blank nested block for 6 hours
242 + // after the data is already correct. Re-rendering once per request while empty
243 + // is what lets it self-heal. (#165)
244 + if ( '' !== $bd_html ) {
245 + set_transient( $bd_cache_key, $bd_html, HOUR_IN_SECONDS * 6 );
246 + }
247 + echo $bd_html; //phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
248 +}