PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.8.9
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.8.9
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/Shortcodes/CategoryGrid.php +8 -138 4.8.23.8.9 View file →
@@ -1,17 +1,10 @@
1 1 <?php
2 -// phpcs:disable WordPressVIPMinimum.Performance.WPQueryParams.PostNotIn_exclude -- shortcode exposes user-driven exclusion attribute.
2 +
3 3 namespace WPDeveloper\BetterDocs\Shortcodes;
4 4
5 -if ( ! defined( 'ABSPATH' ) ) {
6 - exit;
7 -}
8 -
9 -use WPDeveloper\BetterDocs\Core\Query;
10 5 use WPDeveloper\BetterDocs\Utils\Helper;
11 -use WPDeveloper\BetterDocs\Core\Settings;
12 6 use WPDeveloper\BetterDocs\Core\Shortcode;
13 -use WPDeveloper\BetterDocs\Admin\Customizer\Defaults;
14 7
15 8 class CategoryGrid extends Shortcode {
16 9 protected $layout_class = 'layout-1';
17 10
@@ -25,15 +18,8 @@
25 18 'icon' => 'show_icon',
26 19 'post_counter' => 'show_count'
27 20 ];
28 21
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 -
36 22 public function get_name() {
37 23 return 'betterdocs_category_grid';
38 24 }
39 25
@@ -77,12 +63,9 @@
77 63 'category_title_link' => false,
78 64 'layout_type' => '',
79 65 'list_icon_url' => '',
80 66 'list_icon_name' => 'list',
81 - 'show_list_icon' => true,
82 - 'sidebar_layout' => '',
83 - 'lazy_load' => false,
84 - 'wrapper_class' => []
67 + 'sidebar_layout' => ''
85 68 ];
86 69 }
87 70
88 71 public function generate_attributes() {
@@ -123,9 +106,8 @@
123 106 $attributes['class'][] = 'single-kb';
124 107 }
125 108 }
126 109
127 -
128 110 return $attributes;
129 111 }
130 112
131 113 public function header_layout_sequence( $sequence, $layout, $widget_type, $args ) {
@@ -158,9 +140,9 @@
158 140
159 141 $terms_query = $this->query->terms_query(
160 142 [
161 143 'taxonomy' => $this->attributes['taxonomy'],
162 - 'multiple_kb' => ( $this->attributes['multiple_knowledge_base'] && ! empty( $this->attributes['kb_slug'] ) ) ? true : false,
144 + 'multiple_kb' => $this->attributes['multiple_knowledge_base'],
163 145 'kb_slug' => $this->attributes['kb_slug'],
164 146 'terms' => $this->attributes['terms'],
165 147 'order' => $this->attributes['terms_order'],
166 148 'orderby' => $this->attributes['terms_orderby'],
@@ -194,31 +176,10 @@
194 176 if ( $this->attributes['multiple_knowledge_base'] && ( ! empty( $this->attributes['kb_slug'] ) ) && ( ! betterdocs()->helper->is_templates() ) ) {
195 177 $inner_wrapper_attr['data-mkb-slug'] = $this->attributes['kb_slug'];
196 178 }
197 179
198 - // Prepare wrapper attributes with custom classes
199 - $wrapper_attr_classes = [ 'betterdocs-category-grid-wrapper' ];
200 -
201 - // Add custom wrapper classes if provided
202 - if ( $this->isset( 'wrapper_class' ) ) {
203 - $wrapper_classes = $this->attributes['wrapper_class'];
204 -
205 - // Handle both array and string formats
206 - if ( is_string( $wrapper_classes ) ) {
207 - // Split by comma or space and clean up
208 - $wrapper_classes = preg_split( '/[,\s]+/', $wrapper_classes );
209 - $wrapper_classes = array_filter( array_map( 'trim', $wrapper_classes ) );
210 - } elseif ( is_array( $wrapper_classes ) ) {
211 - $wrapper_classes = array_filter( $wrapper_classes );
212 - }
213 -
214 - if ( ! empty( $wrapper_classes ) ) {
215 - $wrapper_attr_classes = array_merge( $wrapper_attr_classes, $wrapper_classes );
216 - }
217 - }
218 -
219 180 return [
220 - 'wrapper_attr' => [ 'class' => $wrapper_attr_classes ],
181 + 'wrapper_attr' => [ 'class' => [ 'betterdocs-category-grid-wrapper' ] ],
221 182 'inner_wrapper_attr' => $inner_wrapper_attr,
222 183 'layout' => 'default',
223 184 'widget_type' => 'category-grid',
224 185 'terms_query_args' => $terms_query,
@@ -223,9 +184,9 @@
223 184 'widget_type' => 'category-grid',
224 185 'terms_query_args' => $terms_query,
225 186 'docs_query_args' => $docs_query,
226 187 'nested_docs_query_args' => $docs_query,
227 - 'list_icon_name' => ($this->attributes['list_icon_name'] ?? 'list') == 'list' ? 'list' : [ 'value' => $this->attributes['list_icon_name'] ?? 'list' ],
188 + 'list_icon_name' => $this->attributes['list_icon_name'] == 'list' ? 'list' : [ 'value' => $this->attributes['list_icon_name'] ],
228 189 'show_header' => true,
229 190 'show_list' => true,
230 191 'show_title' => true,
231 192 'show_button' => $show_button,
@@ -231,103 +192,12 @@
231 192 'show_button' => $show_button,
232 193 'button_text' => $button_text,
233 194 'show_button_icon' => true,
234 195 'button_icon_position' => true,
235 - 'title_tag' => $this->attributes['title_tag'] ?? 'h2',
236 196 'button_icon' => true,
237 197 'category_title_link' => $category_title_link,
238 - 'layout_type' => $this->attributes['layout_type'] ?? '',
239 - 'list_icon_url' => $this->attributes['list_icon_url'] ?? '',
240 - 'show_list_icon' => $this->attributes['show_list_icon'] ?? true,
241 - 'sidebar_layout' => $this->attributes['sidebar_layout'] ?? '',
242 - 'lazy_load' => ! empty( $this->attributes['lazy_load'] )
198 + 'layout_type' => $this->attributes['layout_type'],
199 + 'list_icon_url' => $this->attributes['list_icon_url'],
200 + 'sidebar_layout' => $this->attributes['sidebar_layout']
243 201 ];
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 - ] );
332 202 }
333 203 }