| @@ -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 | +} | |