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

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