| @@ -5,11 +5,13 @@ | ||
| 5 | 5 | if ( ! defined( 'ABSPATH' ) ) { |
| 6 | 6 | exit; |
| 7 | 7 | } |
| 8 | 8 | |
| 9 | - | |
| 9 | +use WPDeveloper\BetterDocs\Core\Query; | |
| 10 | 10 | use WPDeveloper\BetterDocs\Utils\Helper; |
| 11 | +use WPDeveloper\BetterDocs\Core\Settings; | |
| 11 | 12 | use WPDeveloper\BetterDocs\Core\Shortcode; |
| 13 | +use WPDeveloper\BetterDocs\Admin\Customizer\Defaults; | |
| 12 | 14 | |
| 13 | 15 | class CategoryGrid extends Shortcode { |
| 14 | 16 | protected $layout_class = 'layout-1'; |
| 15 | 17 | |
| @@ -23,8 +25,15 @@ | ||
| 23 | 25 | 'icon' => 'show_icon', |
| 24 | 26 | 'post_counter' => 'show_count' |
| 25 | 27 | ]; |
| 26 | 28 | |
| 29 | + public function __construct( Settings $settings, Query $query, Helper $helper, Defaults $defaults ) { | |
| 30 | + parent::__construct( $settings, $query, $helper, $defaults ); | |
| 31 | + | |
| 32 | + add_action( 'wp_ajax_betterdocs_lazy_category_body', [ $this, 'ajax_lazy_category_body' ] ); | |
| 33 | + add_action( 'wp_ajax_nopriv_betterdocs_lazy_category_body', [ $this, 'ajax_lazy_category_body' ] ); | |
| 34 | + } | |
| 35 | + | |
| 27 | 36 | public function get_name() { |
| 28 | 37 | return 'betterdocs_category_grid'; |
| 29 | 38 | } |
| 30 | 39 | |
| @@ -70,8 +79,9 @@ | ||
| 70 | 79 | 'list_icon_url' => '', |
| 71 | 80 | 'list_icon_name' => 'list', |
| 72 | 81 | 'show_list_icon' => true, |
| 73 | 82 | 'sidebar_layout' => '', |
| 83 | + 'lazy_load' => false, | |
| 74 | 84 | 'wrapper_class' => [] |
| 75 | 85 | ]; |
| 76 | 86 | } |
| 77 | 87 | |
| @@ -148,9 +158,9 @@ | ||
| 148 | 158 | |
| 149 | 159 | $terms_query = $this->query->terms_query( |
| 150 | 160 | [ |
| 151 | 161 | 'taxonomy' => $this->attributes['taxonomy'], |
| 152 | - 'multiple_kb' => $this->attributes['multiple_knowledge_base'], | |
| 162 | + 'multiple_kb' => ( $this->attributes['multiple_knowledge_base'] && ! empty( $this->attributes['kb_slug'] ) ) ? true : false, | |
| 153 | 163 | 'kb_slug' => $this->attributes['kb_slug'], |
| 154 | 164 | 'terms' => $this->attributes['terms'], |
| 155 | 165 | 'order' => $this->attributes['terms_order'], |
| 156 | 166 | 'orderby' => $this->attributes['terms_orderby'], |
| @@ -227,8 +237,97 @@ | ||
| 227 | 237 | 'category_title_link' => $category_title_link, |
| 228 | 238 | 'layout_type' => $this->attributes['layout_type'] ?? '', |
| 229 | 239 | 'list_icon_url' => $this->attributes['list_icon_url'] ?? '', |
| 230 | 240 | 'show_list_icon' => $this->attributes['show_list_icon'] ?? true, |
| 231 | - 'sidebar_layout' => $this->attributes['sidebar_layout'] ?? '' | |
| 241 | + 'sidebar_layout' => $this->attributes['sidebar_layout'] ?? '', | |
| 242 | + 'lazy_load' => ! empty( $this->attributes['lazy_load'] ) | |
| 232 | 243 | ]; |
| 244 | + } | |
| 245 | + | |
| 246 | + /** | |
| 247 | + * AJAX handler for lazy-loaded category bodies. Returns the inner HTML of | |
| 248 | + * a single .betterdocs-body (docs list + nested subcategories) for one term. | |
| 249 | + * Reuses the same template (`template-parts/category-list`) that the full | |
| 250 | + * page render uses, which in turn hits the nested-categories fragment cache. | |
| 251 | + * | |
| 252 | + * Public read, intentionally no nonce — matches the SearchForm/SearchModal | |
| 253 | + * nopriv pattern and stays compatible with full-page caching (a per-request | |
| 254 | + * nonce would go stale on cached anonymous pages and silently break the | |
| 255 | + * lazy expand). This endpoint only ever returns data that is already public: | |
| 256 | + * WP_Query runs publish-only and Query::get_doc_ids_by_term applies the same | |
| 257 | + * capability filtering as the on-page render, so private/draft docs never | |
| 258 | + * leak. The response is the published doc titles for a term — the same set a | |
| 259 | + * visitor sees by navigating to that category — so there is nothing to gate | |
| 260 | + * beyond what the normal archive already exposes. | |
| 261 | + */ | |
| 262 | + public function ajax_lazy_category_body() { | |
| 263 | + $term_id = isset( $_POST['term_id'] ) ? (int) $_POST['term_id'] : 0; | |
| 264 | + $kb_slug = isset( $_POST['kb_slug'] ) ? sanitize_text_field( wp_unslash( $_POST['kb_slug'] ) ) : ''; | |
| 265 | + $multiple_kb = ! empty( $_POST['multiple_kb'] ) && ! empty( $kb_slug ); | |
| 266 | + // category_icon comes from the client when the body is inside a | |
| 267 | + // sidebar layout that uses folder icons (Sleek / layout-7). Allowed | |
| 268 | + // values match what nested-categories.php branches on. | |
| 269 | + $category_icon = ''; | |
| 270 | + if ( isset( $_POST['category_icon'] ) ) { | |
| 271 | + $raw = sanitize_text_field( wp_unslash( $_POST['category_icon'] ) ); | |
| 272 | + if ( in_array( $raw, array( 'folder', 'folder-open' ), true ) ) { | |
| 273 | + $category_icon = $raw; | |
| 274 | + } | |
| 275 | + } | |
| 276 | + | |
| 277 | + if ( $term_id <= 0 ) { | |
| 278 | + wp_send_json_error( [ 'message' => 'invalid term_id' ], 400 ); | |
| 279 | + } | |
| 280 | + | |
| 281 | + $term = get_term( $term_id, 'doc_category' ); | |
| 282 | + if ( ! $term instanceof \WP_Term ) { | |
| 283 | + wp_send_json_error( [ 'message' => 'term not found' ], 404 ); | |
| 284 | + } | |
| 285 | + | |
| 286 | + $docs_query = [ | |
| 287 | + 'orderby' => $this->settings->get( 'alphabetically_order_post', 'betterdocs_order' ), | |
| 288 | + 'order' => $this->settings->get( 'docs_order', 'ASC' ), | |
| 289 | + 'posts_per_page' => -1, | |
| 290 | + 'term_id' => $term->term_id, | |
| 291 | + 'term_slug' => $term->slug, | |
| 292 | + 'multiple_kb' => $multiple_kb, | |
| 293 | + 'kb_slug' => $kb_slug, | |
| 294 | + ]; | |
| 295 | + | |
| 296 | + $view_object = betterdocs()->views; | |
| 297 | + | |
| 298 | + ob_start(); | |
| 299 | + $view_object->get( | |
| 300 | + 'template-parts/category-list', | |
| 301 | + [ | |
| 302 | + 'view_object' => $view_object, | |
| 303 | + 'term' => $term, | |
| 304 | + 'query_args' => betterdocs()->query->docs_query_args( $docs_query ), | |
| 305 | + 'nested_subcategory' => (bool) $this->settings->get( 'archive_nested_subcategory' ), | |
| 306 | + 'nested_docs_query_args' => [ | |
| 307 | + 'multiple_kb' => $multiple_kb, | |
| 308 | + 'kb_slug' => $kb_slug, | |
| 309 | + 'posts_per_page' => -1, | |
| 310 | + ], | |
| 311 | + 'nested_terms_query' => [ | |
| 312 | + 'orderby' => $this->settings->get( 'terms_orderby', 'betterdocs_order' ), | |
| 313 | + 'order' => $this->settings->get( 'terms_order', 'ASC' ), | |
| 314 | + ], | |
| 315 | + 'show_list_icon' => true, | |
| 316 | + 'list_icon_name' => 'list', | |
| 317 | + 'list_icon_url' => '', | |
| 318 | + 'layout_type' => '', | |
| 319 | + 'widget_type' => 'category-grid', | |
| 320 | + 'sidebar_layout' => '', | |
| 321 | + 'category_icon' => $category_icon, | |
| 322 | + 'multiple_knowledge_base' => $multiple_kb, | |
| 323 | + 'kb_slug' => $kb_slug, | |
| 324 | + ] | |
| 325 | + ); | |
| 326 | + $html = ob_get_clean(); | |
| 327 | + | |
| 328 | + wp_send_json_success( [ | |
| 329 | + 'term_id' => $term->term_id, | |
| 330 | + 'html' => $html, | |
| 331 | + ] ); | |
| 233 | 332 | } |
| 234 | 333 | } |