PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.2.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.2.0
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.0, at includes/REST/DocCategories.php

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