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/REST/DocCategories.php +123 -296 4.4.13.8.9 View file →
@@ -1,326 +1,153 @@
1 1 <?php
2 2
3 3 namespace WPDeveloper\BetterDocs\REST;
4 4
5 -use stdClass;
6 5 use WPDeveloper\BetterDocs\Core\BaseAPI;
7 6
8 7 class DocCategories extends BaseAPI {
9 - public function permission_check(): bool {
10 - return true;
11 - // return current_user_can( 'edit_docs' );
12 - }
8 + public function permission_check(): bool {
9 + return current_user_can( 'edit_docs' );
10 + }
13 11
14 - public function register() {
15 - $this->get( 'doc-categories', array( $this, 'get_response' ), array(
16 - 'password' => array(
17 - 'description' => __( 'The password for password-protected docs.' ),
18 - 'type' => 'string'
19 - )
20 - ) );
21 - $this->get( 'doc-categories-kb', array( $this, 'doc_categories_kb_response' ) );
22 - }
12 + public function register() {
13 + $this->get( 'doc-categories', [ $this, 'get_response' ] );
14 + }
23 15
24 - public function doc_categories_kb_response( $request ) {
25 - $mkb = ! empty( $request->get_param( 'knowledge_base' ) ) ? get_term( $request->get_param( 'knowledge_base' ), 'knowledge_base' ) : '';
26 - $mkb = ! empty( $mkb ) ? $mkb->slug : '';
27 - $suppress_filters = ! empty( $request->get_param( 'suppress_filters' ) ) ? $request->get_param( 'suppress_filters' ) : '';
16 + public function get_response( $request ) {
17 + global $wpdb;
28 18
29 - $terms_query = betterdocs()->query->terms_query(
30 - array(
31 - 'parent' => 0,
32 - 'hide_empty' => true,
33 - 'taxonomy' => 'doc_category',
34 - 'orderby' => 'betterdocs_order',
35 - 'order' => 'ASC'
36 - )
37 - );
19 + $mkb = $request->get_param( 'knowledge_base' );
38 20
39 - if ( ! empty( $suppress_filters ) ) {
40 - $terms_query[ 'suppress_filters' ] = $suppress_filters;
41 - }
21 + $terms_query = betterdocs()->query->terms_query(
22 + [
23 + 'hide_empty' => false,
24 + 'taxonomy' => 'doc_category',
25 + 'orderby' => 'meta_value_num',
26 + 'meta_key' => 'doc_category_order',
27 + 'order' => 'ASC'
28 + ]
29 + );
42 30
43 - if ( ! empty( $mkb ) ) {
44 - $terms_query[ 'meta_query' ] = array(
45 - 'relation' => 'AND',
46 - array(
47 - 'key' => 'doc_category_knowledge_base',
48 - 'value' => $mkb,
49 - 'compare' => 'LIKE'
50 - )
51 - );
52 - $terms_query[ 'order' ] = 'ASC';
53 - }
31 + if ( ! empty( $mkb ) ) {
32 + $terms_query['meta_query'] = [
33 + 'relation' => 'AND',
34 + [
35 + 'key' => 'doc_category_knowledge_base',
36 + 'value' => $mkb,
37 + 'compare' => 'LIKE'
38 + ]
39 + ];
40 + $terms_query['order'] = 'ASC';
41 + }
54 42
55 - $terms = get_terms( $terms_query );
43 + $terms = get_terms( $terms_query );
44 + $response = [];
56 45
57 - $terms = $this->convert_terms_to_array_of_std_objects( $terms );
46 + foreach ( $terms as $term ) {
47 + $original_args = [
48 + 'post_type' => 'docs',
49 + 'posts_per_page' => '-1',
50 + 'post_status' => 'any',
51 + 'term_id' => $term->term_id,
52 + 'term_slug' => $term->slug,
53 + 'nested_subcategory' => false,
54 + 'orderby' => 'betterdocs_order'
55 + ];
58 56
59 - return $terms;
60 - }
57 + if ( ! empty( $mkb ) ) {
58 + $original_args['multiple_kb'] = true;
59 + $original_args['kb_slug'] = $mkb;
60 + }
61 61
62 - private function convert_terms_to_array_of_std_objects( $payload ) {
63 - $terms = array();
62 + $query_args = betterdocs()->query->docs_query_args( $original_args );
64 63
65 - foreach ( $payload as $term ) {
66 - $object = new stdClass();
64 + $posts = betterdocs()->query->get_posts( $query_args, true );
65 + $response[ $term->term_id ] = [];
67 66
68 - $object->term_id = $term->term_id;
69 - $object->name = $term->name;
70 - $object->slug = $term->slug;
71 - $object->term_group = $term->term_group;
72 - $object->term_taxonomy_id = $term->term_taxonomy_id;
73 - $object->taxonomy = $term->taxonomy;
74 - $object->description = $term->description;
75 - $object->parent = $term->parent;
76 - $object->count = $term->count;
77 - $object->filter = $term->filter;
78 - $object->meta = $term->meta;
67 + if ( ! $posts->have_posts() ) {
68 + wp_reset_query();
69 + }
70 + while ( $posts->have_posts() ) :
71 + $posts->the_post();
72 + $data = $this->get_doc_data( get_the_ID() );
73 + array_push( $response[ $term->term_id ], $data );
74 + endwhile;
79 75
80 - array_push( $terms, $object );
81 - }
76 + wp_reset_postdata();
77 + wp_reset_query();
78 + }
82 79
83 - return $terms;
84 - }
80 + /**
81 + * Uncategories Docs
82 + */
83 + $_post__not_in_query = $wpdb->prepare(
84 + "SELECT ID as post_id from $wpdb->posts WHERE post_type = %s AND post_status != 'trash' AND post_status != 'auto-draft' AND ID NOT IN ( SELECT object_id as post_id FROM $wpdb->term_relationships WHERE term_taxonomy_id IN ( SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s ) )",
85 + 'docs',
86 + 'doc_category'
87 + );
85 88
86 - public function get_response( $request ) {
87 - global $wpdb;
89 + $_post__not_in = $wpdb->get_col( $_post__not_in_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
88 90
89 - $mkb = $request->get_param( 'knowledge_base' );
90 - $per_page = $request->get_param( 'per_page' );
91 - $page = $request->get_param( 'page' );
91 + if ( ! empty( $_post__not_in ) ) {
92 + $uncategorized_docs = [];
93 + $_uncategorized_docs_query = new \WP_Query(
94 + [
95 + 'post_type' => 'docs',
96 + 'post_status' => 'any',
97 + 'post__in' => $_post__not_in
98 + ]
99 + );
92 100
93 - $default_args = array(
94 - 'hide_empty' => false,
95 - 'taxonomy' => 'doc_category',
96 - 'orderby' => 'betterdocs_order',
97 - 'order' => 'ASC'
98 - );
101 + if ( ! $_uncategorized_docs_query->have_posts() ) {
102 + wp_reset_query();
103 + }
104 + while ( $_uncategorized_docs_query->have_posts() ) :
105 + $_uncategorized_docs_query->the_post();
106 + $data = $this->get_doc_data( get_the_ID() );
107 + array_push( $uncategorized_docs, $data );
108 + endwhile;
99 109
100 - if ( 0 != $per_page ) {
101 - $default_args[ 'number' ] = $per_page;
102 - }
110 + wp_reset_postdata();
111 + wp_reset_query();
103 112
104 - if ( 0 != $page ) {
105 - $default_args[ 'offset' ] = ( $page * $per_page ) - $per_page;
106 - }
113 + $response['uncategorized'] = $uncategorized_docs;
114 + }
107 115
108 - $terms_query = betterdocs()->query->terms_query( $default_args );
116 + return $response;
117 + }
109 118
110 - if ( ! empty( $mkb ) ) {
111 - $terms_query[ 'meta_query' ] = array(
112 - 'relation' => 'AND',
113 - array(
114 - 'key' => 'doc_category_knowledge_base',
115 - 'value' => $mkb,
116 - 'compare' => 'LIKE'
117 - )
118 - );
119 - $terms_query[ 'order' ] = 'ASC';
120 - }
119 + /**
120 + * Get Doc Data Based On Doc ID
121 + *
122 + * @return void
123 + */
124 + public function get_doc_data( $id ) {
125 + $post_data = get_post( $id );
126 + $data = [
127 + 'author' => (int) $post_data->post_author,
128 + 'author_info' => [
129 + 'name' => get_the_author_meta( 'display_name', $post_data->post_author ),
130 + 'author_nicename' => get_the_author_meta( 'nicename', $post_data->post_author ),
131 + 'author_url' => get_author_posts_url( $post_data->post_author )
132 + ],
133 + 'unique_id' => uniqid( 'doc' ),
134 + 'id' => $post_data->ID,
135 + 'title' => $post_data->post_title,
136 + 'slug' => get_post_field( 'post_name', $id ),
137 + 'link' => get_permalink( $id ),
138 + 'status' => get_post_status(),
139 + 'date' => $post_data->post_date,
140 + 'date_gmt' => $post_data->post_date_gmt,
141 + 'doc_category' => wp_get_post_terms( $id, 'doc_category', [ 'fields' => 'ids' ] ),
142 + 'doc_tag' => wp_get_post_terms( $id, 'doc_tag', [ 'fields' => 'ids' ] ),
143 + 'password' => $post_data->post_password,
144 + 'comment_status' => $post_data->comment_status
145 + ];
121 146
122 - $terms = get_terms( $terms_query );
123 - $response = array();
147 + if ( taxonomy_exists( 'knowledge_base' ) ) {
148 + $data['knowledge_base'] = wp_get_post_terms( $id, 'knowledge_base', [ 'fields' => 'ids' ] );
149 + }
124 150
125 - // Determine allowed post statuses based on user permissions
126 - $post_status = array( 'publish' );
127 - if ( current_user_can( 'read_private_docs' ) ) {
128 - $post_status[] = 'private';
129 - }
130 - // Admin users with edit_docs capability should see all post statuses
131 - if ( current_user_can( 'edit_docs' ) ) {
132 - $post_status = array( 'publish', 'draft', 'pending', 'private', 'future' );
133 - }
134 -
135 - foreach ( $terms as $term ) {
136 - $original_args = array(
137 - 'post_type' => 'docs',
138 - 'posts_per_page' => '-1',
139 - 'post_status' => $post_status,
140 - 'term_id' => $term->term_id,
141 - 'term_slug' => $term->slug,
142 - 'nested_subcategory' => false,
143 - 'orderby' => 'betterdocs_order'
144 - );
145 -
146 - // Exclude password-protected posts unless user has permission
147 - if ( ! current_user_can( 'edit_posts' ) ) {
148 - $original_args[ 'has_password' ] = false;
149 - }
150 -
151 - if ( ! empty( $mkb ) ) {
152 - $original_args[ 'multiple_kb' ] = true;
153 - $original_args[ 'kb_slug' ] = $mkb;
154 - }
155 -
156 - $query_args = betterdocs()->query->docs_query_args( $original_args );
157 -
158 - $posts = betterdocs()->query->get_posts( $query_args, true );
159 - $response[ $term->term_id ] = array();
160 -
161 - if ( ! $posts->have_posts() ) {
162 - wp_reset_query();
163 - }
164 - while ( $posts->have_posts() ):
165 - $posts->the_post();
166 - $post_obj = get_post( get_the_ID() );
167 -
168 - // Double-check password protection for individual posts
169 - if ( ! empty( $post_obj->post_password ) ) {
170 - $can_access = $this->can_access_password_content( $post_obj, $request );
171 - if ( ! $can_access ) {
172 - continue; // Skip this post
173 - }
174 - }
175 -
176 - $data = $this->get_doc_data( get_the_ID(), $request );
177 - array_push( $response[ $term->term_id ], $data );
178 - endwhile;
179 -
180 - wp_reset_postdata();
181 - wp_reset_query();
182 - }
183 -
184 - /**
185 - * Uncategories Docs
186 - */
187 - // Build secure query for uncategorized docs with proper post status filtering
188 - $post_status_placeholders = implode( ',', array_fill( 0, count( $post_status ), '%s' ) );
189 - $_post__not_in_query = $wpdb->prepare(
190 - "SELECT ID as post_id from $wpdb->posts WHERE post_type = %s AND post_status IN ($post_status_placeholders) AND post_status != 'trash' AND post_status != 'auto-draft' AND ID NOT IN ( SELECT object_id as post_id FROM $wpdb->term_relationships WHERE term_taxonomy_id IN ( SELECT term_taxonomy_id FROM $wpdb->term_taxonomy WHERE taxonomy = %s ) )",
191 - array_merge( array( 'docs' ), $post_status, array( 'doc_category' ) )
192 - );
193 -
194 - $_post__not_in = $wpdb->get_col( $_post__not_in_query ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
195 -
196 - if ( ! empty( $_post__not_in ) ) {
197 - $uncategorized_docs = array();
198 - $uncategorized_query_args = array(
199 - 'post_type' => 'docs',
200 - 'post_status' => $post_status,
201 - 'post__in' => $_post__not_in
202 - );
203 -
204 - // Exclude password-protected posts unless user has permission
205 - if ( ! current_user_can( 'edit_posts' ) ) {
206 - $uncategorized_query_args[ 'has_password' ] = false;
207 - }
208 -
209 - $_uncategorized_docs_query = new \WP_Query( $uncategorized_query_args );
210 -
211 - if ( ! $_uncategorized_docs_query->have_posts() ) {
212 - wp_reset_query();
213 - }
214 - while ( $_uncategorized_docs_query->have_posts() ):
215 - $_uncategorized_docs_query->the_post();
216 - $post_obj = get_post( get_the_ID() );
217 -
218 - // Double-check password protection for individual posts
219 - if ( ! empty( $post_obj->post_password ) ) {
220 - $can_access = $this->can_access_password_content( $post_obj, $request );
221 - if ( ! $can_access ) {
222 - continue; // Skip this post
223 - }
224 - }
225 -
226 - $data = $this->get_doc_data( get_the_ID(), $request );
227 - array_push( $uncategorized_docs, $data );
228 - endwhile;
229 -
230 - wp_reset_postdata();
231 - wp_reset_query();
232 -
233 - $response[ 'uncategorized' ] = $uncategorized_docs;
234 - }
235 -
236 - unset( $terms_query[ 'offset' ] );
237 - unset( $terms_query[ 'number' ] );
238 - $total_terms = wp_count_terms( $terms_query );
239 -
240 - return array(
241 - 'data' => $response,
242 - 'total_terms' => $total_terms
243 - );
244 - }
245 -
246 - /**
247 - * Get Doc Data Based On Doc ID
248 - *
249 - * @param int $id Post ID
250 - * @param WP_REST_Request $request REST request object
251 - * @return array
252 - */
253 - public function get_doc_data( $id, $request = null ) {
254 - $post_data = get_post( $id );
255 - $data = array(
256 - 'author' => (int) $post_data->post_author,
257 - 'author_info' => array(
258 - 'name' => get_the_author_meta( 'display_name', $post_data->post_author ),
259 - 'author_nicename' => get_the_author_meta( 'nicename', $post_data->post_author ),
260 - 'author_url' => get_author_posts_url( $post_data->post_author )
261 - ),
262 - 'unique_id' => uniqid( 'doc' ),
263 - 'id' => $post_data->ID,
264 - 'title' => $post_data->post_title,
265 - 'slug' => get_post_field( 'post_name', $id ),
266 - 'link' => get_permalink( $id ),
267 - 'status' => get_post_status(),
268 - 'date' => $post_data->post_date,
269 - 'date_gmt' => $post_data->post_date_gmt,
270 - 'doc_category' => wp_get_post_terms( $id, 'doc_category', array( 'fields' => 'ids' ) ),
271 - 'doc_tag' => wp_get_post_terms( $id, 'doc_tag', array( 'fields' => 'ids' ) ),
272 - 'comment_status' => $post_data->comment_status
273 - );
274 -
275 - // Only expose password to users with edit permissions
276 - if ( current_user_can( 'edit_docs' ) ) {
277 - $data[ 'password' ] = $post_data->post_password;
278 - }
279 -
280 - // Add password protection indicator
281 - if ( ! empty( $post_data->post_password ) ) {
282 - $data[ 'password_protected' ] = true;
283 - } else {
284 - $data[ 'password_protected' ] = false;
285 - }
286 -
287 - if ( taxonomy_exists( 'knowledge_base' ) ) {
288 - $data[ 'knowledge_base' ] = wp_get_post_terms( $id, 'knowledge_base', array( 'fields' => 'ids' ) );
289 - }
290 -
291 - return $data;
292 - }
293 -
294 - /**
295 - * Checks if the user can access password-protected content.
296 - *
297 - * This method determines whether we need to override the regular password
298 - * check in core with a filter.
299 - *
300 - * @param WP_Post $post Post to check against.
301 - * @param WP_REST_Request $request Request data to check.
302 - * @return bool True if the user can access password-protected content, otherwise false.
303 - */
304 - public function can_access_password_content( $post, $request ) {
305 - if ( empty( $post->post_password ) ) {
306 - // No filter required.
307 - return true;
308 - }
309 -
310 - /*
311 - * Users always get access to password protected content if they have
312 - * the `edit_post` meta capability.
313 - */
314 - if ( current_user_can( 'edit_post', $post->ID ) ) {
315 - return true;
316 - }
317 -
318 - // No password provided in request, no auth.
319 - if ( empty( $request ) || empty( $request[ 'password' ] ) ) {
320 - return false;
321 - }
322 -
323 - // Double-check the request password.
324 - return hash_equals( $post->post_password, $request[ 'password' ] );
325 - }
151 + return $data;
152 + }
326 153 }