PluginProbe
Gutenberg / 23.1.0
Gutenberg v23.1.0
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.1.0, at lib/experimental/guidelines/class-gutenberg-guidelines-post-type.php

393 lines 12.0 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 'capability_type' => 'guideline',
122 'map_meta_cap' => true,
123 'capabilities' => array(
124 'read' => 'edit_posts',
125 'create_posts' => 'publish_posts',
126 'edit_posts' => 'edit_posts',
127 'publish_posts' => 'publish_posts',
128 'read_private_posts' => 'read_private_posts',
129 'edit_private_posts' => 'edit_private_posts',
130 'edit_published_posts' => 'edit_published_posts',
131 'delete_private_posts' => 'delete_private_posts',
132 'delete_published_posts' => 'delete_published_posts',
133 'delete_posts' => 'delete_posts',
134 'edit_others_posts' => 'edit_others_posts',
135 'delete_others_posts' => 'delete_others_posts',
136 ),
137 'supports' => array( 'title', 'editor', 'excerpt', 'author', 'revisions' ),
138 'hierarchical' => false,
139 'has_archive' => false,
140 'rewrite' => false,
141 'query_var' => false,
142 'can_export' => true,
143 )
144 );
145
146 register_taxonomy(
147 self::TAXONOMY,
148 self::POST_TYPE,
149 array(
150 'public' => false,
151 'publicly_queryable' => false,
152 'hierarchical' => true,
153 'labels' => array(
154 'name' => _x( 'Guideline Types', 'taxonomy general name', 'gutenberg' ),
155 'singular_name' => _x( 'Guideline Type', 'taxonomy singular name', 'gutenberg' ),
156 'add_new_item' => __( 'Add Guideline Type', 'gutenberg' ),
157 'add_or_remove_items' => __( 'Add or remove guideline types', 'gutenberg' ),
158 'back_to_items' => __( '&larr; Go to Guideline Types', 'gutenberg' ),
159 'edit_item' => __( 'Edit Guideline Type', 'gutenberg' ),
160 'item_link' => __( 'Guideline Type Link', 'gutenberg' ),
161 'item_link_description' => __( 'A link to a guideline type.', 'gutenberg' ),
162 'items_list' => __( 'Guideline Types list', 'gutenberg' ),
163 'items_list_navigation' => __( 'Guideline Types list navigation', 'gutenberg' ),
164 'new_item_name' => __( 'New Guideline Type Name', 'gutenberg' ),
165 'no_terms' => __( 'No guideline types', 'gutenberg' ),
166 'not_found' => __( 'No guideline types found.', 'gutenberg' ),
167 'search_items' => __( 'Search Guideline Types', 'gutenberg' ),
168 'update_item' => __( 'Update Guideline Type', 'gutenberg' ),
169 'view_item' => __( 'View Guideline Type', 'gutenberg' ),
170 ),
171 'capabilities' => array(
172 'manage_terms' => 'manage_categories',
173 'edit_terms' => 'edit_posts',
174 'delete_terms' => 'delete_categories',
175 'assign_terms' => 'edit_posts',
176 ),
177 'query_var' => false,
178 'rewrite' => false,
179 'show_ui' => true,
180 'show_admin_column' => true,
181 'show_in_nav_menus' => false,
182 'show_in_rest' => true,
183 )
184 );
185
186 add_action( 'save_post_' . self::POST_TYPE, '_wp_guidelines_ensure_default_type_term' );
187 add_filter( 'wp_insert_term_data', '_wp_guidelines_maybe_map_term_label', 10, 2 );
188 }
189
190 /**
191 * Resolves a taxonomy term by slug, creating it if it doesn't exist yet.
192 *
193 * @param string $slug Term slug.
194 * @param string $name Human-readable term name, used when creating.
195 * @return int|WP_Error Term ID on success, WP_Error on failure.
196 */
197 public static function get_or_create_term_id( string $slug, string $name ) {
198 $term = get_term_by( 'slug', $slug, self::TAXONOMY );
199 if ( $term ) {
200 return (int) $term->term_id;
201 }
202
203 $inserted = wp_insert_term(
204 $name,
205 self::TAXONOMY,
206 array( 'slug' => $slug )
207 );
208
209 if ( is_wp_error( $inserted ) ) {
210 return $inserted;
211 }
212
213 return (int) $inserted['term_id'];
214 }
215
216 /**
217 * Determines whether a guideline post belongs to the content singleton.
218 *
219 * Used by the /wp/v2/content-guidelines route to reject non-content-typed
220 * posts addressed by ID — those belong to the standard /wp/v2/guidelines
221 * collection.
222 *
223 * @param int $post_id Post ID.
224 * @return bool True if the post has the `content` term.
225 */
226 public static function is_content_guideline( $post_id ) {
227 $terms = get_the_terms( $post_id, self::TAXONOMY );
228 if ( is_wp_error( $terms ) || empty( $terms ) ) {
229 return false;
230 }
231
232 foreach ( $terms as $term ) {
233 if ( self::TERM_CONTENT === $term->slug ) {
234 return true;
235 }
236 }
237
238 return false;
239 }
240
241 /**
242 * Register post meta fields with revision support.
243 */
244 public static function register_post_meta(): void {
245 $meta_args = array(
246 'show_in_rest' => true,
247 'single' => true,
248 'type' => 'string',
249 'revisions_enabled' => true,
250 'auth_callback' => function (): bool {
251 return current_user_can( 'manage_options' );
252 },
253 'sanitize_callback' => 'sanitize_textarea_field',
254 );
255
256 // Register standard category meta.
257 foreach ( self::CATEGORY_META_KEYS as $category ) {
258 register_post_meta( self::POST_TYPE, '_guideline_' . $category, $meta_args );
259 }
260
261 // Register meta for content blocks.
262 foreach ( self::get_content_blocks() as $block_name ) {
263 register_post_meta( self::POST_TYPE, self::block_name_to_meta_key( $block_name ), $meta_args );
264 }
265 }
266
267 /**
268 * Get block names that have content role attributes.
269 *
270 * @return array Block names with content role.
271 */
272 public static function get_content_blocks(): array {
273 $content_blocks = array();
274 $registry = WP_Block_Type_Registry::get_instance();
275
276 foreach ( $registry->get_all_registered() as $block_type ) {
277 if ( self::block_has_content_role( $block_type ) ) {
278 $content_blocks[] = $block_type->name;
279 }
280 }
281
282 return $content_blocks;
283 }
284
285 /**
286 * Check if a block type has any attribute with content role.
287 *
288 * @param WP_Block_Type $block_type The block type to check.
289 * @return bool True if block has content role attribute.
290 */
291 private static function block_has_content_role( WP_Block_Type $block_type ): bool {
292 if ( empty( $block_type->attributes ) ) {
293 return false;
294 }
295
296 foreach ( $block_type->attributes as $attribute ) {
297 if ( isset( $attribute['role'] ) && 'content' === $attribute['role'] ) {
298 return true;
299 }
300 }
301
302 return false;
303 }
304
305 /**
306 * Convert a block name to a meta key.
307 *
308 * @param string $block_name The block name (e.g., 'core/paragraph').
309 * @return string The meta key (e.g., '_guideline_block_core_paragraph').
310 */
311 public static function block_name_to_meta_key( string $block_name ): string {
312 // Replace '/' with '_' to create a valid meta key.
313 $sanitized = str_replace( '/', '_', $block_name );
314 return self::BLOCK_META_PREFIX . $sanitized;
315 }
316
317 /**
318 * Convert a meta key back to a block name.
319 *
320 * @param string $meta_key The meta key (e.g., '_guideline_block_core_paragraph').
321 * @return string The block name (e.g., 'core/paragraph').
322 */
323 public static function meta_key_to_block_name( string $meta_key ): string {
324 // Remove prefix and convert first '_' back to '/'.
325 $without_prefix = str_replace( self::BLOCK_META_PREFIX, '', $meta_key );
326 // Replace first underscore with '/' (namespace separator).
327 return preg_replace( '/_/', '/', $without_prefix, 1 );
328 }
329
330 /**
331 * Check if a meta key is a block guideline meta key.
332 *
333 * @param string $meta_key The meta key to check.
334 * @return bool True if it's a block guideline meta key.
335 */
336 public static function is_block_meta_key( string $meta_key ): bool {
337 return strpos( $meta_key, self::BLOCK_META_PREFIX ) === 0;
338 }
339
340 /**
341 * Gets guideline categories from post meta.
342 *
343 * Shared between the post controller and revisions controller.
344 *
345 * @param int $post_id Post ID (can be a post or revision ID).
346 * @return array Guideline categories.
347 */
348 public static function get_guideline_categories_from_meta( int $post_id ): array {
349 $category_labels = array(
350 'copy' => __( 'Copy Guidelines', 'gutenberg' ),
351 'images' => __( 'Image Guidelines', 'gutenberg' ),
352 'site' => __( 'Site Context', 'gutenberg' ),
353 'additional' => __( 'Additional Guidelines', 'gutenberg' ),
354 );
355
356 $guideline_categories = array();
357
358 // Get standard categories.
359 foreach ( self::CATEGORY_META_KEYS as $category ) {
360 $meta_key = '_guideline_' . $category;
361 $value = get_post_meta( $post_id, $meta_key, true );
362
363 $guideline_categories[ $category ] = array(
364 'label' => $category_labels[ $category ],
365 'guidelines' => $value,
366 );
367 }
368
369 // Get block-specific guidelines from individual meta keys.
370 $all_meta = get_post_meta( $post_id );
371
372 $blocks = array();
373 foreach ( $all_meta as $meta_key => $meta_values ) {
374 if ( self::is_block_meta_key( $meta_key ) ) {
375 $block_name = self::meta_key_to_block_name( $meta_key );
376 $value = $meta_values[0] ?? '';
377
378 if ( ! empty( $value ) ) {
379 $blocks[ $block_name ] = array(
380 'guidelines' => $value,
381 );
382 }
383 }
384 }
385
386 if ( ! empty( $blocks ) ) {
387 $guideline_categories['blocks'] = $blocks;
388 }
389
390 return $guideline_categories;
391 }
392 }
393