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

327 lines 11.7 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', 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 }
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 array(
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' ] = 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 }
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 = array();
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 = array(
94 'hide_empty' => false,
95 'taxonomy' => 'doc_category',
96 'orderby' => 'betterdocs_order',
97 'order' => 'ASC'
98 );
99
100 if ( 0 != $per_page ) {
101 $default_args[ 'number' ] = $per_page;
102 }
103
104 if ( 0 != $page ) {
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' ] = 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 }
121
122 $terms = get_terms( $terms_query );
123 $response = array();
124
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 }
326 }
327