PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.2.6
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.2.6
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 3.5.2 All 199 releases
betterdocs / includes / REST / DocCategories.php

DocCategories.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.2.6, at includes/REST/DocCategories.php

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