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

367 lines 9.8 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 * Neutral default term slug for manually-created guidelines whose type
40 * hasn't been chosen yet. The REST controller that owns the content
41 * singleton always writes its own term explicitly.
42 *
43 * @var string
44 */
45 const TERM_ARTIFACT = 'artifact';
46
47 /**
48 * The standard guideline category meta keys.
49 *
50 * @var array
51 */
52 const CATEGORY_META_KEYS = array(
53 'copy',
54 'images',
55 'site',
56 'additional',
57 );
58
59 /**
60 * All valid guideline category keys for filtering.
61 *
62 * Includes standard categories plus 'blocks'.
63 *
64 * @var array
65 */
66 const VALID_CATEGORIES = array(
67 'copy',
68 'images',
69 'site',
70 'additional',
71 'blocks',
72 );
73
74 /**
75 * Valid guideline statuses.
76 *
77 * @var array
78 */
79 const VALID_STATUSES = array(
80 'draft',
81 'publish',
82 );
83
84 /**
85 * Prefix for block-specific guideline meta keys.
86 *
87 * @var string
88 */
89 const BLOCK_META_PREFIX = '_guideline_block_';
90
91 /**
92 * Register the custom post type.
93 */
94 public static function register() {
95 if ( post_type_exists( self::POST_TYPE ) ) {
96 return;
97 }
98
99 $args = array(
100 'labels' => array(
101 'name' => __( 'Guidelines', 'gutenberg' ),
102 'singular_name' => __( 'Guidelines', 'gutenberg' ),
103 ),
104 'public' => false,
105 'publicly_queryable' => false,
106 'show_ui' => true,
107 'show_in_menu' => false,
108 'show_in_rest' => true,
109 'rest_base' => 'guidelines',
110 'rest_controller_class' => 'Gutenberg_Guidelines_REST_Controller',
111 'revisions_rest_controller_class' => 'Gutenberg_Guidelines_Revisions_Controller',
112 'capability_type' => 'post',
113 'capabilities' => array(
114 'read' => 'edit_posts',
115 'create_posts' => 'manage_options',
116 'edit_posts' => 'manage_options',
117 'edit_published_posts' => 'manage_options',
118 'delete_posts' => 'manage_options',
119 'delete_published_posts' => 'manage_options',
120 'edit_others_posts' => 'manage_options',
121 'delete_others_posts' => 'manage_options',
122 'publish_posts' => 'manage_options',
123 ),
124 'map_meta_cap' => true,
125 'supports' => array( 'title', 'editor', 'excerpt', 'author', 'revisions' ),
126 'hierarchical' => false,
127 'has_archive' => false,
128 'rewrite' => false,
129 'query_var' => false,
130 'can_export' => true,
131 );
132
133 register_post_type( self::POST_TYPE, $args );
134
135 register_taxonomy(
136 self::TAXONOMY,
137 self::POST_TYPE,
138 array(
139 'public' => false,
140 'publicly_queryable' => false,
141 'hierarchical' => true,
142 'labels' => array(
143 'name' => __( 'Guideline Types', 'gutenberg' ),
144 'singular_name' => __( 'Guideline Type', 'gutenberg' ),
145 ),
146 'query_var' => false,
147 'rewrite' => false,
148 'show_ui' => true,
149 'show_admin_column' => true,
150 'show_in_nav_menus' => false,
151 'show_in_rest' => true,
152 )
153 );
154
155 add_action( 'save_post_' . self::POST_TYPE, array( __CLASS__, 'ensure_default_type_term' ) );
156 }
157
158 /**
159 * Ensures a guideline post always has a type term.
160 *
161 * Assigns the `artifact` fallback when the post was saved without one.
162 * The REST controller sets `content` explicitly for the singleton, so
163 * this only applies to posts inserted through other paths.
164 *
165 * The taxonomy intentionally does not use `default_term` at registration
166 * time: on multisite installations with many sites, that triggers cache
167 * clearing work across sites even for taxonomies with zero posts.
168 *
169 * @param int $post_id Post ID.
170 */
171 public static function ensure_default_type_term( $post_id ) {
172 if ( wp_is_post_revision( $post_id ) ) {
173 return;
174 }
175
176 $terms = get_the_terms( $post_id, self::TAXONOMY );
177 if ( is_wp_error( $terms ) || ! empty( $terms ) ) {
178 return;
179 }
180
181 $term_id = self::get_or_create_term_id( self::TERM_ARTIFACT, __( 'Artifact', 'gutenberg' ) );
182 if ( is_wp_error( $term_id ) ) {
183 return;
184 }
185
186 wp_set_object_terms( $post_id, array( $term_id ), self::TAXONOMY );
187 }
188
189 /**
190 * Resolves a taxonomy term by slug, creating it if it doesn't exist yet.
191 *
192 * @param string $slug Term slug.
193 * @param string $name Human-readable term name, used when creating.
194 * @return int|WP_Error Term ID on success, WP_Error on failure.
195 */
196 public static function get_or_create_term_id( $slug, $name ) {
197 $term = get_term_by( 'slug', $slug, self::TAXONOMY );
198 if ( $term ) {
199 return (int) $term->term_id;
200 }
201
202 $inserted = wp_insert_term(
203 $name,
204 self::TAXONOMY,
205 array( 'slug' => $slug )
206 );
207
208 if ( is_wp_error( $inserted ) ) {
209 return $inserted;
210 }
211
212 return (int) $inserted['term_id'];
213 }
214
215 /**
216 * Register post meta fields with revision support.
217 */
218 public static function register_post_meta() {
219 $meta_args = array(
220 'show_in_rest' => true,
221 'single' => true,
222 'type' => 'string',
223 'revisions_enabled' => true,
224 'auth_callback' => function () {
225 return current_user_can( 'manage_options' );
226 },
227 'sanitize_callback' => 'sanitize_textarea_field',
228 );
229
230 // Register standard category meta.
231 foreach ( self::CATEGORY_META_KEYS as $category ) {
232 register_post_meta( self::POST_TYPE, '_guideline_' . $category, $meta_args );
233 }
234
235 // Register meta for content blocks.
236 foreach ( self::get_content_blocks() as $block_name ) {
237 register_post_meta( self::POST_TYPE, self::block_name_to_meta_key( $block_name ), $meta_args );
238 }
239 }
240
241 /**
242 * Get block names that have content role attributes.
243 *
244 * @return array Block names with content role.
245 */
246 public static function get_content_blocks() {
247 $content_blocks = array();
248 $registry = WP_Block_Type_Registry::get_instance();
249
250 foreach ( $registry->get_all_registered() as $block_type ) {
251 if ( self::block_has_content_role( $block_type ) ) {
252 $content_blocks[] = $block_type->name;
253 }
254 }
255
256 return $content_blocks;
257 }
258
259 /**
260 * Check if a block type has any attribute with content role.
261 *
262 * @param WP_Block_Type $block_type The block type to check.
263 * @return bool True if block has content role attribute.
264 */
265 private static function block_has_content_role( $block_type ) {
266 if ( empty( $block_type->attributes ) ) {
267 return false;
268 }
269
270 foreach ( $block_type->attributes as $attribute ) {
271 if ( isset( $attribute['role'] ) && 'content' === $attribute['role'] ) {
272 return true;
273 }
274 }
275
276 return false;
277 }
278
279 /**
280 * Convert a block name to a meta key.
281 *
282 * @param string $block_name The block name (e.g., 'core/paragraph').
283 * @return string The meta key (e.g., '_guideline_block_core_paragraph').
284 */
285 public static function block_name_to_meta_key( $block_name ) {
286 // Replace '/' with '_' to create a valid meta key.
287 $sanitized = str_replace( '/', '_', $block_name );
288 return self::BLOCK_META_PREFIX . $sanitized;
289 }
290
291 /**
292 * Convert a meta key back to a block name.
293 *
294 * @param string $meta_key The meta key (e.g., '_guideline_block_core_paragraph').
295 * @return string The block name (e.g., 'core/paragraph').
296 */
297 public static function meta_key_to_block_name( $meta_key ) {
298 // Remove prefix and convert first '_' back to '/'.
299 $without_prefix = str_replace( self::BLOCK_META_PREFIX, '', $meta_key );
300 // Replace first underscore with '/' (namespace separator).
301 return preg_replace( '/_/', '/', $without_prefix, 1 );
302 }
303
304 /**
305 * Check if a meta key is a block guideline meta key.
306 *
307 * @param string $meta_key The meta key to check.
308 * @return bool True if it's a block guideline meta key.
309 */
310 public static function is_block_meta_key( $meta_key ) {
311 return strpos( $meta_key, self::BLOCK_META_PREFIX ) === 0;
312 }
313
314 /**
315 * Gets guideline categories from post meta.
316 *
317 * Shared between the post controller and revisions controller.
318 *
319 * @param int $post_id Post ID (can be a post or revision ID).
320 * @return array Guideline categories.
321 */
322 public static function get_guideline_categories_from_meta( $post_id ) {
323 $category_labels = array(
324 'copy' => __( 'Copy Guidelines', 'gutenberg' ),
325 'images' => __( 'Image Guidelines', 'gutenberg' ),
326 'site' => __( 'Site Context', 'gutenberg' ),
327 'additional' => __( 'Additional Guidelines', 'gutenberg' ),
328 );
329
330 $guideline_categories = array();
331
332 // Get standard categories.
333 foreach ( self::CATEGORY_META_KEYS as $category ) {
334 $meta_key = '_guideline_' . $category;
335 $value = get_post_meta( $post_id, $meta_key, true );
336
337 $guideline_categories[ $category ] = array(
338 'label' => $category_labels[ $category ],
339 'guidelines' => $value,
340 );
341 }
342
343 // Get block-specific guidelines from individual meta keys.
344 $all_meta = get_post_meta( $post_id );
345
346 $blocks = array();
347 foreach ( $all_meta as $meta_key => $meta_values ) {
348 if ( self::is_block_meta_key( $meta_key ) ) {
349 $block_name = self::meta_key_to_block_name( $meta_key );
350 $value = $meta_values[0] ?? '';
351
352 if ( ! empty( $value ) ) {
353 $blocks[ $block_name ] = array(
354 'guidelines' => $value,
355 );
356 }
357 }
358 }
359
360 if ( ! empty( $blocks ) ) {
361 $guideline_categories['blocks'] = $blocks;
362 }
363
364 return $guideline_categories;
365 }
366 }
367