PluginProbe
Gutenberg / 23.2.1
Gutenberg v23.2.1
23.9.1 23.9.0 23.8.0 23.7.2 23.7.1 23.7.0 23.6.1 23.6.2 23.6.0 23.5.3 23.5.2 23.5.1 23.5.0 23.4.0 23.3.2 23.3.1 23.3.0 23.2.0 23.2.1 23.2.2 23.1.1 23.1.0 23.0.1 12.6.0 7.4.0 All 402 releases
gutenberg / lib / experimental / guidelines / class-gutenberg-guidelines-post-type.php

class-gutenberg-guidelines-post-type.php in Gutenberg 23.2.1, at lib/experimental/guidelines/class-gutenberg-guidelines-post-type.php

396 lines 12.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Guidelines Post Type registration.
4 *
5 * @package gutenberg
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit;
10 }
11
12 /**
13 * Handles registration of the Guidelines custom post type.
14 */
15 class Gutenberg_Guidelines_Post_Type {
16
17 /**
18 * The post type name.
19 *
20 * @var string
21 */
22 const POST_TYPE = 'wp_guideline';
23
24 /**
25 * The taxonomy name for guideline types.
26 *
27 * @var string
28 */
29 const TAXONOMY = 'wp_guideline_type';
30
31 /**
32 * Taxonomy term slug used for site-wide content guidelines.
33 *
34 * @var string
35 */
36 const TERM_CONTENT = 'content';
37
38 /**
39 * The standard guideline category meta keys.
40 *
41 * @var array
42 */
43 const CATEGORY_META_KEYS = array(
44 'copy',
45 'images',
46 'site',
47 'additional',
48 );
49
50 /**
51 * All valid guideline category keys for filtering.
52 *
53 * Includes standard categories plus 'blocks'.
54 *
55 * @var array
56 */
57 const VALID_CATEGORIES = array(
58 'copy',
59 'images',
60 'site',
61 'additional',
62 'blocks',
63 );
64
65 /**
66 * Valid guideline statuses.
67 *
68 * @var array
69 */
70 const VALID_STATUSES = array(
71 'draft',
72 'publish',
73 );
74
75 /**
76 * Prefix for block-specific guideline meta keys.
77 *
78 * @var string
79 */
80 const BLOCK_META_PREFIX = '_guideline_block_';
81
82 /**
83 * Register the custom post type.
84 */
85 public static function register(): void {
86 if ( post_type_exists( self::POST_TYPE ) ) {
87 return;
88 }
89
90 register_post_type(
91 self::POST_TYPE,
92 array(
93 'labels' => array(
94 'name' => _x( 'Guidelines', 'post type general name', 'gutenberg' ),
95 'singular_name' => _x( 'Guideline', 'post type singular name', 'gutenberg' ),
96 'add_new' => __( 'Add Guideline', 'gutenberg' ),
97 'add_new_item' => __( 'Add Guideline', 'gutenberg' ),
98 'all_items' => __( 'All Guidelines', 'gutenberg' ),
99 'edit_item' => __( 'Edit Guideline', 'gutenberg' ),
100 'filter_items_list' => __( 'Filter guidelines list', 'gutenberg' ),
101 'item_published' => __( 'Guideline published.', 'gutenberg' ),
102 'item_published_privately' => __( 'Guideline published privately.', 'gutenberg' ),
103 'item_reverted_to_draft' => __( 'Guideline reverted to draft.', 'gutenberg' ),
104 'item_scheduled' => __( 'Guideline scheduled.', 'gutenberg' ),
105 'item_updated' => __( 'Guideline updated.', 'gutenberg' ),
106 'items_list' => __( 'Guidelines list', 'gutenberg' ),
107 'items_list_navigation' => __( 'Guidelines list navigation', 'gutenberg' ),
108 'new_item' => __( 'New Guideline', 'gutenberg' ),
109 'not_found' => __( 'No guidelines found.', 'gutenberg' ),
110 'not_found_in_trash' => __( 'No guidelines found in Trash.', 'gutenberg' ),
111 'search_items' => __( 'Search Guidelines', 'gutenberg' ),
112 'view_item' => __( 'View Guideline', 'gutenberg' ),
113 'view_items' => __( 'View Guidelines', 'gutenberg' ),
114 ),
115 'public' => false,
116 'publicly_queryable' => false,
117 'show_ui' => true,
118 'show_in_menu' => false,
119 'show_in_rest' => true,
120 'rest_base' => 'guidelines',
121
122 'rest_controller_class' => Gutenberg_Guidelines_REST_Controller::class,
123
124 'capability_type' => 'guideline',
125 'map_meta_cap' => true,
126 'capabilities' => array(
127 'read' => 'edit_posts',
128 'create_posts' => 'publish_posts',
129 'edit_posts' => 'edit_posts',
130 'publish_posts' => 'publish_posts',
131 'read_private_posts' => 'read_private_posts',
132 'edit_private_posts' => 'edit_private_posts',
133 'edit_published_posts' => 'edit_published_posts',
134 'delete_private_posts' => 'delete_private_posts',
135 'delete_published_posts' => 'delete_published_posts',
136 'delete_posts' => 'delete_posts',
137 'edit_others_posts' => 'edit_others_posts',
138 'delete_others_posts' => 'delete_others_posts',
139 ),
140 'supports' => array( 'title', 'editor', 'excerpt', 'author', 'revisions' ),
141 'hierarchical' => false,
142 'has_archive' => false,
143 'rewrite' => false,
144 'query_var' => false,
145 'can_export' => true,
146 )
147 );
148
149 register_taxonomy(
150 self::TAXONOMY,
151 self::POST_TYPE,
152 array(
153 'public' => false,
154 'publicly_queryable' => false,
155 'hierarchical' => true,
156 'labels' => array(
157 'name' => _x( 'Guideline Types', 'taxonomy general name', 'gutenberg' ),
158 'singular_name' => _x( 'Guideline Type', 'taxonomy singular name', 'gutenberg' ),
159 'add_new_item' => __( 'Add Guideline Type', 'gutenberg' ),
160 'add_or_remove_items' => __( 'Add or remove guideline types', 'gutenberg' ),
161 'back_to_items' => __( '&larr; Go to Guideline Types', 'gutenberg' ),
162 'edit_item' => __( 'Edit Guideline Type', 'gutenberg' ),
163 'item_link' => __( 'Guideline Type Link', 'gutenberg' ),
164 'item_link_description' => __( 'A link to a guideline type.', 'gutenberg' ),
165 'items_list' => __( 'Guideline Types list', 'gutenberg' ),
166 'items_list_navigation' => __( 'Guideline Types list navigation', 'gutenberg' ),
167 'new_item_name' => __( 'New Guideline Type Name', 'gutenberg' ),
168 'no_terms' => __( 'No guideline types', 'gutenberg' ),
169 'not_found' => __( 'No guideline types found.', 'gutenberg' ),
170 'search_items' => __( 'Search Guideline Types', 'gutenberg' ),
171 'update_item' => __( 'Update Guideline Type', 'gutenberg' ),
172 'view_item' => __( 'View Guideline Type', 'gutenberg' ),
173 ),
174 'capabilities' => array(
175 'manage_terms' => 'manage_categories',
176 'edit_terms' => 'edit_posts',
177 'delete_terms' => 'delete_categories',
178 'assign_terms' => 'edit_posts',
179 ),
180 'query_var' => false,
181 'rewrite' => false,
182 'show_ui' => true,
183 'show_admin_column' => true,
184 'show_in_nav_menus' => false,
185 'show_in_rest' => true,
186 )
187 );
188
189 add_action( 'save_post_' . self::POST_TYPE, '_wp_guidelines_ensure_default_type_term' );
190 add_filter( 'wp_insert_term_data', '_wp_guidelines_maybe_map_term_label', 10, 2 );
191 }
192
193 /**
194 * Resolves a taxonomy term by slug, creating it if it doesn't exist yet.
195 *
196 * @param string $slug Term slug.
197 * @param string $name Human-readable term name, used when creating.
198 * @return int|WP_Error Term ID on success, WP_Error on failure.
199 */
200 public static function get_or_create_term_id( string $slug, string $name ) {
201 $term = get_term_by( 'slug', $slug, self::TAXONOMY );
202 if ( $term ) {
203 return (int) $term->term_id;
204 }
205
206 $inserted = wp_insert_term(
207 $name,
208 self::TAXONOMY,
209 array( 'slug' => $slug )
210 );
211
212 if ( is_wp_error( $inserted ) ) {
213 return $inserted;
214 }
215
216 return (int) $inserted['term_id'];
217 }
218
219 /**
220 * Determines whether a guideline post belongs to the content singleton.
221 *
222 * Used by the /wp/v2/content-guidelines route to reject non-content-typed
223 * posts addressed by ID — those belong to the standard /wp/v2/guidelines
224 * collection.
225 *
226 * @param int $post_id Post ID.
227 * @return bool True if the post has the `content` term.
228 */
229 public static function is_content_guideline( $post_id ) {
230 $terms = get_the_terms( $post_id, self::TAXONOMY );
231 if ( is_wp_error( $terms ) || empty( $terms ) ) {
232 return false;
233 }
234
235 foreach ( $terms as $term ) {
236 if ( self::TERM_CONTENT === $term->slug ) {
237 return true;
238 }
239 }
240
241 return false;
242 }
243
244 /**
245 * Register post meta fields with revision support.
246 */
247 public static function register_post_meta(): void {
248 $meta_args = array(
249 'show_in_rest' => true,
250 'single' => true,
251 'type' => 'string',
252 'revisions_enabled' => true,
253 'auth_callback' => function (): bool {
254 return current_user_can( 'manage_options' );
255 },
256 'sanitize_callback' => 'sanitize_textarea_field',
257 );
258
259 // Register standard category meta.
260 foreach ( self::CATEGORY_META_KEYS as $category ) {
261 register_post_meta( self::POST_TYPE, '_guideline_' . $category, $meta_args );
262 }
263
264 // Register meta for content blocks.
265 foreach ( self::get_content_blocks() as $block_name ) {
266 register_post_meta( self::POST_TYPE, self::block_name_to_meta_key( $block_name ), $meta_args );
267 }
268 }
269
270 /**
271 * Get block names that have content role attributes.
272 *
273 * @return array Block names with content role.
274 */
275 public static function get_content_blocks(): array {
276 $content_blocks = array();
277 $registry = WP_Block_Type_Registry::get_instance();
278
279 foreach ( $registry->get_all_registered() as $block_type ) {
280 if ( self::block_has_content_role( $block_type ) ) {
281 $content_blocks[] = $block_type->name;
282 }
283 }
284
285 return $content_blocks;
286 }
287
288 /**
289 * Check if a block type has any attribute with content role.
290 *
291 * @param WP_Block_Type $block_type The block type to check.
292 * @return bool True if block has content role attribute.
293 */
294 private static function block_has_content_role( WP_Block_Type $block_type ): bool {
295 if ( empty( $block_type->attributes ) ) {
296 return false;
297 }
298
299 foreach ( $block_type->attributes as $attribute ) {
300 if ( isset( $attribute['role'] ) && 'content' === $attribute['role'] ) {
301 return true;
302 }
303 }
304
305 return false;
306 }
307
308 /**
309 * Convert a block name to a meta key.
310 *
311 * @param string $block_name The block name (e.g., 'core/paragraph').
312 * @return string The meta key (e.g., '_guideline_block_core_paragraph').
313 */
314 public static function block_name_to_meta_key( string $block_name ): string {
315 // Replace '/' with '_' to create a valid meta key.
316 $sanitized = str_replace( '/', '_', $block_name );
317 return self::BLOCK_META_PREFIX . $sanitized;
318 }
319
320 /**
321 * Convert a meta key back to a block name.
322 *
323 * @param string $meta_key The meta key (e.g., '_guideline_block_core_paragraph').
324 * @return string The block name (e.g., 'core/paragraph').
325 */
326 public static function meta_key_to_block_name( string $meta_key ): string {
327 // Remove prefix and convert first '_' back to '/'.
328 $without_prefix = str_replace( self::BLOCK_META_PREFIX, '', $meta_key );
329 // Replace first underscore with '/' (namespace separator).
330 return preg_replace( '/_/', '/', $without_prefix, 1 );
331 }
332
333 /**
334 * Check if a meta key is a block guideline meta key.
335 *
336 * @param string $meta_key The meta key to check.
337 * @return bool True if it's a block guideline meta key.
338 */
339 public static function is_block_meta_key( string $meta_key ): bool {
340 return strpos( $meta_key, self::BLOCK_META_PREFIX ) === 0;
341 }
342
343 /**
344 * Gets guideline categories from post meta.
345 *
346 * Shared between the post controller and revisions controller.
347 *
348 * @param int $post_id Post ID (can be a post or revision ID).
349 * @return array Guideline categories.
350 */
351 public static function get_guideline_categories_from_meta( int $post_id ): array {
352 $category_labels = array(
353 'copy' => __( 'Copy Guidelines', 'gutenberg' ),
354 'images' => __( 'Image Guidelines', 'gutenberg' ),
355 'site' => __( 'Site Context', 'gutenberg' ),
356 'additional' => __( 'Additional Guidelines', 'gutenberg' ),
357 );
358
359 $guideline_categories = array();
360
361 // Get standard categories.
362 foreach ( self::CATEGORY_META_KEYS as $category ) {
363 $meta_key = '_guideline_' . $category;
364 $value = get_post_meta( $post_id, $meta_key, true );
365
366 $guideline_categories[ $category ] = array(
367 'label' => $category_labels[ $category ],
368 'guidelines' => $value,
369 );
370 }
371
372 // Get block-specific guidelines from individual meta keys.
373 $all_meta = get_post_meta( $post_id );
374
375 $blocks = array();
376 foreach ( $all_meta as $meta_key => $meta_values ) {
377 if ( self::is_block_meta_key( $meta_key ) ) {
378 $block_name = self::meta_key_to_block_name( $meta_key );
379 $value = $meta_values[0] ?? '';
380
381 if ( ! empty( $value ) ) {
382 $blocks[ $block_name ] = array(
383 'guidelines' => $value,
384 );
385 }
386 }
387 }
388
389 if ( ! empty( $blocks ) ) {
390 $guideline_categories['blocks'] = $blocks;
391 }
392
393 return $guideline_categories;
394 }
395 }
396