PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 3.5.3
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v3.5.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 3.5.1 All 200 releases
← All changes | includes/Shortcodes/CategoryGrid.php +151 -299 4.9.23.5.3 View file →
@@ -1,333 +1,185 @@
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 - protected $layout_class = 'layout-1';
9 + protected $layout_class = 'layout-1';
17 10
18 - /**
19 - * A list of deprecated attributes.
20 - * @var array<string, string>
21 - */
22 - protected $deprecated_attributes = [
23 - 'category' => 'taxonomy',
24 - 'posts_per_grid' => 'posts_per_page',
25 - 'icon' => 'show_icon',
26 - 'post_counter' => 'show_count'
27 - ];
11 + /**
12 + * A list of deprecated attributes.
13 + * @var array<string, string>
14 + */
15 + protected $deprecated_attributes = [
16 + 'category' => 'taxonomy',
17 + 'posts_per_grid' => 'posts_per_page',
18 + 'icon' => 'show_icon',
19 + 'post_counter' => 'show_count'
20 + ];
28 21
29 - public function __construct( Settings $settings, Query $query, Helper $helper, Defaults $defaults ) {
30 - parent::__construct( $settings, $query, $helper, $defaults );
22 + public function get_name() {
23 + return 'betterdocs_category_grid';
24 + }
31 25
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 - }
26 + public function get_style_depends() {
27 + return ['betterdocs-category-grid'];
28 + }
35 29
36 - public function get_name() {
37 - return 'betterdocs_category_grid';
38 - }
30 + public function get_script_depends() {
31 + return ['betterdocs-category-grid'];
32 + }
39 33
40 - public function get_style_depends() {
41 - return [ 'betterdocs-category-grid' ];
42 - }
34 + /**
35 + * Summary of default_attributes
36 + * @return array
37 + */
38 + public function default_attributes() {
39 + return [
40 + 'sidebar_list' => false,
41 + 'taxonomy' => 'doc_category',
42 + 'show_icon' => true,
43 + 'masonry' => false,
44 + 'posts_per_page' => $this->settings->get( 'posts_number', 0 ),
45 + 'orderby' => $this->settings->get( 'alphabetically_order_post', 'betterdocs_order' ),
46 + 'order' => $this->settings->get( 'docs_order', 'ASC' ),
47 + 'show_count' => $this->settings->get( 'post_count' ),
48 + 'column' => $this->settings->get( 'column_number' ),
49 + 'nested_subcategory' => $this->settings->get( 'nested_subcategory' ),
50 + 'terms' => '',
51 + 'terms_orderby' => '',
52 + 'terms_order' => '',
53 + 'terms_include' => '',
54 + 'terms_exclude' => '',
55 + 'terms_offset' => '',
56 + 'kb_slug' => '',
57 + 'multiple_knowledge_base' => false,
58 + 'disable_customizer_style' => false,
59 + 'title_tag' => 'h2',
60 + 'category_title_link' => false
61 + ];
62 + }
43 63
44 - public function get_script_depends() {
45 - return [ 'betterdocs-category-grid' ];
46 - }
64 + public function generate_attributes() {
65 + $attributes = [
66 + 'class' => [
67 + 'betterdocs-category-grid-inner-wrapper',
68 + $this->layout_class
69 + ]
70 + ];
47 71
48 - /**
49 - * Summary of default_attributes
50 - * @return array
51 - */
52 - public function default_attributes() {
53 - return [
54 - 'sidebar_list' => false,
55 - 'taxonomy' => 'doc_category',
56 - 'show_icon' => true,
57 - 'category_icon' => '',
58 - 'masonry' => false,
59 - 'posts_per_page' => $this->settings->get( 'posts_number', 0 ),
60 - 'orderby' => $this->settings->get( 'alphabetically_order_post', 'betterdocs_order' ),
61 - 'order' => $this->settings->get( 'docs_order', 'ASC' ),
62 - 'show_count' => $this->settings->get( 'post_count' ),
63 - 'count_suffix' => '',
64 - 'count_suffix_singular' => '',
65 - 'column' => $this->settings->get( 'column_number' ),
66 - 'nested_subcategory' => $this->settings->get( 'nested_subcategory' ),
67 - 'terms' => '',
68 - 'terms_orderby' => '',
69 - 'terms_order' => '',
70 - 'terms_include' => '',
71 - 'terms_exclude' => '',
72 - 'terms_offset' => '',
73 - 'kb_slug' => '',
74 - 'multiple_knowledge_base' => false,
75 - 'disable_customizer_style' => false,
76 - 'title_tag' => 'h2',
77 - 'category_title_link' => false,
78 - 'layout_type' => '',
79 - 'list_icon_url' => '',
80 - 'list_icon_name' => 'list',
81 - 'show_list_icon' => true,
82 - 'sidebar_layout' => '',
83 - 'lazy_load' => false,
84 - 'wrapper_class' => []
85 - ];
86 - }
72 + $masonry = (bool) $this->settings->get( 'masonry_layout', false );
73 + if ( $this->has( 'masonry' ) ) {
74 + $masonry = (bool) $this->attributes['masonry'];
75 + }
87 76
88 - public function generate_attributes() {
89 - $attributes = [
90 - 'class' => [
91 - 'betterdocs-category-grid-inner-wrapper',
92 - $this->layout_class
93 - ]
94 - ];
77 + if ( ! is_singular( 'docs' ) && ! is_tax( 'doc_category' ) && ! is_tax( 'doc_tag' ) ) {
78 + if ( $this->attributes['sidebar_list'] == true ) {
79 + $attributes['class'][] = 'layout-flex';
80 + } elseif ( $masonry == true ) {
81 + wp_enqueue_script( 'masonry' );
82 + $attributes['class'][] = 'masonry';
83 + } else {
84 + $attributes['class'][] = 'layout-flex';
85 + }
86 + if ( $this->attributes['sidebar_list'] == true ) {
87 + $_column_val = 1;
88 + } elseif ( $this->isset( 'column' ) ) {
89 + $_column_val = $this->attributes['column'];
90 + } else {
91 + $_column_val = $this->settings->get( 'column_number' );
92 + }
95 93
96 - $masonry = (bool) $this->settings->get( 'masonry_layout', false );
97 - if ( $this->has( 'masonry' ) ) {
98 - $masonry = (bool) $this->attributes['masonry'];
99 - }
94 + $attributes['class'][] = 'docs-col-' . $_column_val;
95 + $attributes['data-column_desktop'] = esc_html( $_column_val );
96 + $attributes['style'] = "--column: $_column_val;";
100 97
101 - if ( ! is_singular( 'docs' ) && ! is_tax( 'doc_category' ) && ! is_tax( 'doc_tag' ) ) {
102 - if ( $this->attributes['sidebar_list'] == true ) {
103 - $attributes['class'][] = 'layout-flex';
104 - } elseif ( $masonry == true ) {
105 - wp_enqueue_script( 'masonry' );
106 - $attributes['class'][] = 'masonry';
107 - } else {
108 - $attributes['class'][] = 'layout-flex';
109 - }
110 - if ( $this->attributes['sidebar_list'] == true ) {
111 - $_column_val = 1;
112 - } elseif ( $this->isset( 'column' ) ) {
113 - $_column_val = $this->attributes['column'];
114 - } else {
115 - $_column_val = $this->settings->get( 'column_number' );
116 - }
98 + if ( $this->isset( 'disable_customizer_style', false ) ) {
99 + $attributes['class'][] = 'single-kb';
100 + }
101 + }
117 102
118 - $attributes['class'][] = 'docs-col-' . $_column_val;
119 - $attributes['data-column_desktop'] = esc_html( $_column_val );
120 - $attributes['style'] = "--column: $_column_val;";
103 + return $attributes;
104 + }
121 105
122 - if ( $this->isset( 'disable_customizer_style', false ) ) {
123 - $attributes['class'][] = 'single-kb';
124 - }
125 - }
106 + public function header_layout_sequence( $sequence, $layout, $widget_type, $args ) {
107 + return ['category_icon', 'category_title', 'category_counts', 'collapse_icon'];
108 + }
126 109
110 + public function render( $atts, $content = null ) {
111 + if ( (bool) $this->attributes['sidebar_list'] ) {
112 + add_filter( 'betterdocs_header_layout_sequence', [$this, 'header_layout_sequence'], 10, 4 );
113 + }
127 114
128 - return $attributes;
129 - }
115 + $this->views( 'layouts/base' );
130 116
131 - public function header_layout_sequence( $sequence, $layout, $widget_type, $args ) {
132 - return [ 'category_icon', 'category_title', 'category_counts', 'collapse_icon' ];
133 - }
117 + if ( (bool) $this->attributes['sidebar_list'] ) {
118 + remove_filter( 'betterdocs_header_layout_sequence', [$this, 'header_layout_sequence'], 10 );
119 + }
120 + }
134 121
135 - public function render( $atts, $content = null ) {
136 - if ( (bool) $this->attributes['sidebar_list'] ) {
137 - add_filter( 'betterdocs_header_layout_sequence', [ $this, 'header_layout_sequence' ], 10, 4 );
138 - }
122 + public function view_params() {
123 + $exploremore_btn = $this->settings->get( 'exploremore_btn' );
124 + $button_text = $this->settings->get( 'exploremore_btn_txt' );
125 + $category_title_link = isset( $this->attributes['category_title_link'] ) ? $this->attributes['category_title_link'] : '';
139 126
140 - $this->views( 'layouts/base' );
127 + $show_button = false;
128 + if ( $this->attributes['posts_per_page'] == -1 ) {
129 + $show_button = false;
130 + } elseif ( $exploremore_btn && ! is_singular( 'docs' ) && Helper::get_tax() != 'doc_category' && ! is_tax( 'doc_tag' ) ) {
131 + $show_button = true;
132 + }
141 133
142 - if ( (bool) $this->attributes['sidebar_list'] ) {
143 - remove_filter( 'betterdocs_header_layout_sequence', [ $this, 'header_layout_sequence' ], 10 );
144 - }
145 - }
134 + $terms_query = $this->query->terms_query( [
135 + 'taxonomy' => $this->attributes['taxonomy'],
136 + 'multiple_kb' => $this->attributes['multiple_knowledge_base'],
137 + 'kb_slug' => $this->attributes['kb_slug'],
138 + 'terms' => $this->attributes['terms'],
139 + 'order' => $this->attributes['terms_order'],
140 + 'orderby' => $this->attributes['terms_orderby'],
141 + 'nested_subcategory' => $this->attributes['nested_subcategory']
142 + ] );
146 143
147 - public function view_params() {
148 - $exploremore_btn = $this->settings->get( 'exploremore_btn' );
149 - $button_text = $this->settings->get( 'exploremore_btn_txt' );
150 - $category_title_link = isset( $this->attributes['category_title_link'] ) ? $this->attributes['category_title_link'] : '';
144 + if ( $this->attributes['terms_include'] ) {
145 + $terms_query['include'] = $this->attributes['terms_include'];
146 + }
151 147
152 - $show_button = false;
153 - if ( $this->attributes['posts_per_page'] == -1 ) {
154 - $show_button = false;
155 - } elseif ( $exploremore_btn && ! is_singular( 'docs' ) && Helper::get_tax() != 'doc_category' && ! is_tax( 'doc_tag' ) ) {
156 - $show_button = true;
157 - }
148 + if ( $this->attributes['terms_exclude'] ) {
149 + $terms_query['exclude'] = $this->attributes['terms_exclude'];
150 + }
158 151
159 - $terms_query = $this->query->terms_query(
160 - [
161 - 'taxonomy' => $this->attributes['taxonomy'],
162 - 'multiple_kb' => ( $this->attributes['multiple_knowledge_base'] && ! empty( $this->attributes['kb_slug'] ) ) ? true : false,
163 - 'kb_slug' => $this->attributes['kb_slug'],
164 - 'terms' => $this->attributes['terms'],
165 - 'order' => $this->attributes['terms_order'],
166 - 'orderby' => $this->attributes['terms_orderby'],
167 - 'nested_subcategory' => $this->attributes['nested_subcategory']
168 - ]
169 - );
152 + if ( $this->attributes['terms_offset'] ) {
153 + $terms_query['offset'] = (int) $this->attributes['terms_offset'];
154 + }
170 155
171 - if ( $this->attributes['terms_include'] ) {
172 - $terms_query['include'] = $this->attributes['terms_include'];
173 - }
156 + $inner_wrapper_attr = $this->generate_attributes();
174 157
175 - if ( $this->attributes['terms_exclude'] ) {
176 - $terms_query['exclude'] = $this->attributes['terms_exclude'];
177 - }
158 + $docs_query = [
159 + 'orderby' => $this->attributes['orderby'],
160 + 'order' => $this->attributes['order'],
161 + 'posts_per_page' => $this->attributes['posts_per_page']
162 + ];
178 163
179 - if ( $this->attributes['terms_offset'] ) {
180 - $terms_query['offset'] = (int) $this->attributes['terms_offset'];
181 - }
164 + return [
165 + 'wrapper_attr' => ['class' => ['betterdocs-category-grid-wrapper']],
166 + 'inner_wrapper_attr' => $inner_wrapper_attr,
167 + 'layout' => 'default',
168 + 'widget_type' => 'category-grid',
182 169
183 - $inner_wrapper_attr = $this->generate_attributes();
170 + 'terms_query_args' => $terms_query,
171 + 'docs_query_args' => $docs_query,
172 + 'nested_docs_query_args' => $docs_query,
184 173
185 - $docs_query = [
186 - 'orderby' => $this->attributes['orderby'],
187 - 'order' => $this->attributes['order'],
188 - 'posts_per_page' => $this->attributes['posts_per_page']
189 - ];
190 -
191 - /**
192 - * Add This Attribute When Using Outside Betterdocs Templates Only
193 - */
194 - if ( $this->attributes['multiple_knowledge_base'] && ( ! empty( $this->attributes['kb_slug'] ) ) && ( ! betterdocs()->helper->is_templates() ) ) {
195 - $inner_wrapper_attr['data-mkb-slug'] = $this->attributes['kb_slug'];
196 - }
197 -
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 - return [
220 - 'wrapper_attr' => [ 'class' => $wrapper_attr_classes ],
221 - 'inner_wrapper_attr' => $inner_wrapper_attr,
222 - 'layout' => 'default',
223 - 'widget_type' => 'category-grid',
224 - 'terms_query_args' => $terms_query,
225 - 'docs_query_args' => $docs_query,
226 - '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' ],
228 - 'show_header' => true,
229 - 'show_list' => true,
230 - 'show_title' => true,
231 - 'show_button' => $show_button,
232 - 'button_text' => $button_text,
233 - 'show_button_icon' => true,
234 - 'button_icon_position' => true,
235 - 'title_tag' => $this->attributes['title_tag'] ?? 'h2',
236 - 'button_icon' => true,
237 - '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'] )
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 - ] );
332 - }
174 + 'show_header' => true,
175 + 'show_list' => true,
176 + 'show_title' => true,
177 + 'show_button' => $show_button,
178 + 'button_text' => $button_text,
179 + 'show_button_icon' => true,
180 + 'button_icon_position' => true,
181 + 'button_icon' => true,
182 + 'category_title_link' => $category_title_link
183 + ];
184 + }
333 185 }