PluginProbe
weDocs: AI Powered Knowledge Base, Docs, Documentation, Wiki & AI Chatbot / trunk
weDocs: AI Powered Knowledge Base, Docs, Documentation, Wiki & AI Chatbot vtrunk
2.5.0 2.4.1 2.4.0 2.3.1 2.3.0 2.2.5 2.2.6 2.2.7 2.2.2 2.2.3 2.2.4 1.3.0 1.3.1 1.3.2 1.3.3 1.4 1.4.1 1.5 1.6 1.6.1 1.6.2 1.6.3 1.7 1.7.1 1.7.2 All 54 releases
wedocs / includes / Post_Types.php

Post_Types.php in weDocs: AI Powered Knowledge Base, Docs, Documentation, Wiki & AI Chatbot trunk, at includes/Post_Types.php

415 lines 15.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WeDevs\WeDocs;
4
5 /**
6 * Post type class
7 */
8 class Post_Types {
9
10 /**
11 * The post type name.
12 *
13 * @var string
14 */
15 private $post_type = 'docs';
16
17 /**
18 * Initialize the class
19 */
20 public function __construct() {
21 add_action( 'init', [ $this, 'register_post_type' ] );
22 add_action( 'init', [ $this, 'register_taxonomy' ] );
23 add_action( 'init', [ $this, 'register_post_meta' ] );
24
25 // Propagate _is_vendor_doc meta to descendants.
26 add_action( 'rest_after_insert_docs', [ $this, 'inherit_vendor_doc_meta' ], 10, 2 );
27 // added_postmeta fires the first time the flag is written (new row);
28 // updated_postmeta fires on subsequent changes. Listen to both.
29 add_action( 'added_postmeta', [ $this, 'propagate_vendor_doc_meta' ], 10, 4 );
30 add_action( 'updated_postmeta', [ $this, 'propagate_vendor_doc_meta' ], 10, 4 );
31 add_action( 'init', [ $this, 'register_faq_post_type' ] );
32 add_action( 'init', [ $this, 'register_faq_taxonomy' ] );
33 add_action( 'init', [ $this, 'register_faq_meta' ] );
34 add_filter( 'get_terms', [ $this, 'sort_faq_groups_by_order' ], 10, 4 );
35 }
36
37 /**
38 * Register the post type.
39 *
40 * @return void
41 */
42 public function register_post_type() {
43 $labels = array(
44 'name' => _x( 'Docs', 'Post Type General Name', 'wedocs' ),
45 'singular_name' => _x( 'Doc', 'Post Type Singular Name', 'wedocs' ),
46 'menu_name' => __( 'Documentation', 'wedocs' ),
47 'parent_item_colon' => __( 'Parent Doc', 'wedocs' ),
48 'all_items' => __( 'All Documentations', 'wedocs' ),
49 'view_item' => __( 'View Documentation', 'wedocs' ),
50 'add_new_item' => __( 'Add Documentation', 'wedocs' ),
51 'add_new' => __( 'Add New', 'wedocs' ),
52 'edit_item' => __( 'Edit Documentation', 'wedocs' ),
53 'update_item' => __( 'Update Documentation', 'wedocs' ),
54 'search_items' => __( 'Search Documentation', 'wedocs' ),
55 'not_found' => __( 'Not documentation found', 'wedocs' ),
56 'not_found_in_trash' => __( 'Not found in Trash', 'wedocs' ),
57 );
58
59 $rewrite = array(
60 'slug' => 'docs',
61 'with_front' => true,
62 'pages' => true,
63 'feeds' => true,
64 );
65
66 $args = array(
67 'labels' => $labels,
68 'supports' => array( 'title', 'editor', 'thumbnail', 'revisions', 'page-attributes', 'comments', 'elementor', 'custom-fields'),
69 'hierarchical' => true,
70 'public' => true,
71 'show_ui' => true,
72 'show_in_menu' => false,
73 'show_in_nav_menus' => true,
74 'show_in_admin_bar' => true,
75 'menu_position' => 5,
76 'menu_icon' => 'dashicons-portfolio',
77 'can_export' => true,
78 'has_archive' => false,
79 'exclude_from_search' => false,
80 'publicly_queryable' => true,
81 'show_in_rest' => true,
82 'rewrite' => $rewrite,
83 'map_meta_cap' => true,
84 'capability_type' => array( 'doc', 'docs' ),
85 'taxonomies' => array( 'doc_tag' ),
86 );
87
88 register_post_type( $this->post_type, apply_filters( 'wedocs_post_type', $args ) );
89 }
90
91 /**
92 * Register doc tags taxonomy.
93 *
94 * @return void
95 */
96 public function register_taxonomy() {
97 $labels = [
98 'name' => _x( 'Tags', 'Taxonomy General Name', 'wedocs' ),
99 'singular_name' => _x( 'Tag', 'Taxonomy Singular Name', 'wedocs' ),
100 'menu_name' => __( 'Tags', 'wedocs' ),
101 'all_items' => __( 'All Tags', 'wedocs' ),
102 'parent_item' => __( 'Parent Tag', 'wedocs' ),
103 'parent_item_colon' => __( 'Parent Tag:', 'wedocs' ),
104 'new_item_name' => __( 'New Tag', 'wedocs' ),
105 'add_new_item' => __( 'Add New Item', 'wedocs' ),
106 'edit_item' => __( 'Edit Tag', 'wedocs' ),
107 'update_item' => __( 'Update Tag', 'wedocs' ),
108 'view_item' => __( 'View Tag', 'wedocs' ),
109 'separate_items_with_commas' => __( 'Separate items with commas', 'wedocs' ),
110 'add_or_remove_items' => __( 'Add or remove items', 'wedocs' ),
111 'choose_from_most_used' => __( 'Choose from the most used', 'wedocs' ),
112 'popular_items' => __( 'Popular Tags', 'wedocs' ),
113 'search_items' => __( 'Search Tags', 'wedocs' ),
114 'not_found' => __( 'Not Found', 'wedocs' ),
115 'no_terms' => __( 'No items', 'wedocs' ),
116 'items_list' => __( 'Tags list', 'wedocs' ),
117 'items_list_navigation' => __( 'Tags list navigation', 'wedocs' ),
118 ];
119
120 $rewrite = [
121 'slug' => 'doc-tag',
122 'with_front' => true,
123 'hierarchical' => false,
124 ];
125
126 $args = [
127 'labels' => $labels,
128 'hierarchical' => false,
129 'public' => true,
130 'show_ui' => true,
131 'show_admin_column' => true,
132 'show_in_nav_menus' => true,
133 'show_tagcloud' => true,
134 'show_in_rest' => true,
135 'rewrite' => $rewrite,
136 ];
137
138 register_taxonomy( 'doc_tag', [ 'docs' ], $args );
139 }
140
141 /**
142 * Register post meta fields for the docs post type.
143 *
144 * @since 2.2.7
145 *
146 * @return void
147 */
148 public function register_post_meta() {
149 register_post_meta( $this->post_type, '_is_vendor_doc', [
150 'show_in_rest' => true,
151 'single' => true,
152 'type' => 'string',
153 'default' => '0',
154 'auth_callback' => function () {
155 return current_user_can( 'edit_docs' );
156 },
157 ] );
158 }
159
160 /**
161 * Inherit _is_vendor_doc meta from root ancestor when a doc is created via REST.
162 *
163 * When a section or article is created under a vendor doc tree, it should
164 * automatically inherit the vendor doc flag from its root ancestor.
165 *
166 * @since 2.2.7
167 *
168 * @param \WP_Post $post Inserted or updated post object.
169 * @param \WP_REST_Request $request Request object.
170 *
171 * @return void
172 */
173 public function inherit_vendor_doc_meta( $post, $request ) {
174 if ( empty( $post->post_parent ) ) {
175 return;
176 }
177
178 $ancestors = get_post_ancestors( $post->ID );
179 $root_id = ! empty( $ancestors ) ? end( $ancestors ) : $post->post_parent;
180 $is_vendor = get_post_meta( $root_id, '_is_vendor_doc', true );
181
182 if ( '1' === $is_vendor ) {
183 update_post_meta( $post->ID, '_is_vendor_doc', '1' );
184 } else {
185 // Root is not a vendor doc: clear any stale flag left from a
186 // previous parent so reparented docs don't keep vendor visibility.
187 update_post_meta( $post->ID, '_is_vendor_doc', '0' );
188 }
189 }
190
191 /**
192 * Propagate _is_vendor_doc meta changes to all descendants.
193 *
194 * When a root doc is marked or unmarked as a vendor doc, all its
195 * sections and articles should receive the same meta value.
196 *
197 * @since 2.2.7
198 *
199 * @param int $meta_id ID of the metadata entry.
200 * @param int $post_id Post ID.
201 * @param string $meta_key Metadata key.
202 * @param mixed $meta_value Metadata value.
203 *
204 * @return void
205 */
206 public function propagate_vendor_doc_meta( $meta_id, $post_id, $meta_key, $meta_value ) {
207 if ( '_is_vendor_doc' !== $meta_key ) {
208 return;
209 }
210
211 if ( 'docs' !== get_post_type( $post_id ) ) {
212 return;
213 }
214
215 $descendants = wedocs_get_posts_children( $post_id, 'docs' );
216
217 if ( empty( $descendants ) ) {
218 return;
219 }
220
221 // Temporarily unhook to prevent recursive triggers.
222 remove_action( 'updated_postmeta', [ $this, 'propagate_vendor_doc_meta' ], 10 );
223
224 foreach ( $descendants as $descendant ) {
225 update_post_meta( $descendant->ID, '_is_vendor_doc', $meta_value );
226 }
227
228 add_action( 'updated_postmeta', [ $this, 'propagate_vendor_doc_meta' ], 10, 4 );
229 }
230
231 /**
232 * Register the FAQ post type.
233 *
234 * @since WEDOCS_SINCE
235 *
236 * @return void
237 */
238 public function register_faq_post_type() {
239 $labels = [
240 'name' => _x( 'FAQs', 'Post Type General Name', 'wedocs' ),
241 'singular_name' => _x( 'FAQ', 'Post Type Singular Name', 'wedocs' ),
242 'menu_name' => __( 'FAQs', 'wedocs' ),
243 'all_items' => __( 'All FAQs', 'wedocs' ),
244 'view_item' => __( 'View FAQ', 'wedocs' ),
245 'add_new_item' => __( 'Add FAQ', 'wedocs' ),
246 'add_new' => __( 'Add New', 'wedocs' ),
247 'edit_item' => __( 'Edit FAQ', 'wedocs' ),
248 'update_item' => __( 'Update FAQ', 'wedocs' ),
249 'search_items' => __( 'Search FAQs', 'wedocs' ),
250 'not_found' => __( 'No FAQs found', 'wedocs' ),
251 'not_found_in_trash' => __( 'Not found in Trash', 'wedocs' ),
252 ];
253
254 $args = [
255 'labels' => $labels,
256 'supports' => [ 'title', 'editor', 'page-attributes', 'custom-fields' ],
257 'hierarchical' => false,
258 'public' => false,
259 'show_ui' => false,
260 'show_in_menu' => false,
261 'show_in_rest' => true,
262 'rest_base' => 'wedocs-faqs',
263 'has_archive' => false,
264 'exclude_from_search' => true,
265 'publicly_queryable' => false,
266 'map_meta_cap' => true,
267 'capability_type' => [ 'doc', 'docs' ],
268 // The delete_*_docs caps are never granted to any role, and the
269 // user_has_cap filter in Capability only covers the docs post type
270 // on classic edit screens, so REST deletes would always 403.
271 // Deleting a FAQ needs the same access as creating one.
272 'capabilities' => [
273 'delete_posts' => 'edit_docs',
274 'delete_others_posts' => 'edit_docs',
275 'delete_published_posts' => 'edit_docs',
276 'delete_private_posts' => 'edit_docs',
277 ],
278 ];
279
280 register_post_type( 'wedocs_faq', apply_filters( 'wedocs_faq_post_type', $args ) );
281 }
282
283 /**
284 * Register the FAQ group taxonomy.
285 *
286 * @since WEDOCS_SINCE
287 *
288 * @return void
289 */
290 public function register_faq_taxonomy() {
291 $labels = [
292 'name' => _x( 'FAQ Groups', 'Taxonomy General Name', 'wedocs' ),
293 'singular_name' => _x( 'FAQ Group', 'Taxonomy Singular Name', 'wedocs' ),
294 'menu_name' => __( 'FAQ Groups', 'wedocs' ),
295 'all_items' => __( 'All FAQ Groups', 'wedocs' ),
296 'new_item_name' => __( 'New FAQ Group', 'wedocs' ),
297 'add_new_item' => __( 'Add New FAQ Group', 'wedocs' ),
298 'edit_item' => __( 'Edit FAQ Group', 'wedocs' ),
299 'update_item' => __( 'Update FAQ Group', 'wedocs' ),
300 'search_items' => __( 'Search FAQ Groups', 'wedocs' ),
301 'not_found' => __( 'Not Found', 'wedocs' ),
302 ];
303
304 $args = [
305 'labels' => $labels,
306 'hierarchical' => true,
307 'public' => false,
308 'show_ui' => false,
309 'show_in_rest' => true,
310 'rest_base' => 'wedocs-faq-groups',
311 'show_admin_column' => false,
312 // Mapped to the doc editing capability, which is granted to the
313 // administrator and editor roles only. Dedicated *_doc_terms caps
314 // are never granted to any role, so using them would 403 the REST routes.
315 'capabilities' => [
316 'manage_terms' => 'edit_docs',
317 'edit_terms' => 'edit_docs',
318 'delete_terms' => 'edit_docs',
319 'assign_terms' => 'edit_docs',
320 ],
321 ];
322
323 register_taxonomy( 'wedocs_faq_group', [ 'wedocs_faq' ], $args );
324 }
325
326 /**
327 * Register FAQ post meta fields for REST API exposure.
328 *
329 * @since WEDOCS_SINCE
330 *
331 * @return void
332 */
333 public function register_faq_meta() {
334 register_post_meta( 'wedocs_faq', '_faq_open_by_default', [
335 'type' => 'boolean',
336 'single' => true,
337 'default' => false,
338 'show_in_rest' => true,
339 'sanitize_callback' => 'rest_sanitize_boolean',
340 'auth_callback' => function () {
341 return current_user_can( 'edit_docs' );
342 },
343 ] );
344
345 register_term_meta( 'wedocs_faq_group', 'icon', [
346 'type' => 'integer',
347 'single' => true,
348 'default' => 0,
349 'show_in_rest' => true,
350 'sanitize_callback' => 'absint',
351 ] );
352
353 register_term_meta( 'wedocs_faq_group', 'order', [
354 'type' => 'integer',
355 'single' => true,
356 'default' => 0,
357 'show_in_rest' => true,
358 'sanitize_callback' => 'absint',
359 ] );
360
361 register_term_meta( 'wedocs_faq_group', 'status', [
362 'type' => 'boolean',
363 'single' => true,
364 'default' => true,
365 'show_in_rest' => true,
366 'sanitize_callback' => 'rest_sanitize_boolean',
367 ] );
368 }
369
370 /**
371 * Sort FAQ group terms by their order term meta after retrieval.
372 *
373 * Uses the get_terms filter instead of meta_key query arg because
374 * terms without an explicit order meta row would be excluded by WP_Term_Query.
375 *
376 * @since WEDOCS_SINCE
377 *
378 * @param array $terms Array of found terms.
379 * @param array|null $taxonomies Array of taxonomies.
380 * @param array $args Term query arguments.
381 * @param \WP_Term_Query $term_query The WP_Term_Query instance.
382 *
383 * @return array
384 */
385 public function sort_faq_groups_by_order( $terms, $taxonomies, $args, $term_query ) {
386 // Bail early for non-FAQ taxonomy queries to avoid overhead on every get_terms call.
387 if ( ! is_array( $taxonomies ) || ! in_array( 'wedocs_faq_group', $taxonomies, true ) ) {
388 return $terms;
389 }
390
391 if ( empty( $terms ) || is_wp_error( $terms ) ) {
392 return $terms;
393 }
394
395 // Only sort when terms are returned as objects (not counts, ids, etc.).
396 if ( ! isset( $terms[0] ) || ! is_object( $terms[0] ) ) {
397 return $terms;
398 }
399
400 usort( $terms, function ( $a, $b ) {
401 $order_a = (int) get_term_meta( $a->term_id, 'order', true );
402 $order_b = (int) get_term_meta( $b->term_id, 'order', true );
403
404 // Fall back to the term ID so groups sharing an order stay stable.
405 if ( $order_a === $order_b ) {
406 return $a->term_id <=> $b->term_id;
407 }
408
409 return $order_a <=> $order_b;
410 } );
411
412 return $terms;
413 }
414 }
415