Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a1388d4
Add experimental Content Guidelines feature
aagam-shah Jan 26, 2026
dcf729c
Content Guidelines: Add revision support and code cleanup
aagam-shah Jan 27, 2026
9c85705
Content Guidelines: Add two-column layout and revision pagination
aagam-shah Jan 27, 2026
6d67108
Content Guidelines: Use table layout for revision history
aagam-shah Jan 27, 2026
4c948b9
Content Guidelines: Use Update button for published state
aagam-shah Jan 27, 2026
31ac241
Content Guidelines: Fix JSON encoding for special characters
aagam-shah Jan 27, 2026
a5749cb
Content Guidelines: Simplify and refactor code for clarity
aagam-shah Jan 27, 2026
135be67
Content Guidelines: Add selector for block-specific guidelines
aagam-shah Jan 29, 2026
f5579f5
Content Guidelines: Update REST API paths to use 'wp/v2' namespace
aagam-shah Jan 29, 2026
678620f
Content Guidelines: Refactor and enhance admin page components
aagam-shah Feb 2, 2026
d90b91f
Content Guidelines: Use native WordPress admin styles
aagam-shah Feb 2, 2026
64bf8fe
Content Guidelines: Redesign configured blocks list and use design to…
aagam-shah Feb 2, 2026
5c34adc
Content Guidelines: Enable WordPress native export
aagam-shah Feb 2, 2026
2ec55b0
Content Guidelines: Redesign block guidelines add/edit flow
aagam-shah Feb 2, 2026
533db15
Merge upstream/trunk into ai-content-guidelines-mvp
aagam-shah Feb 2, 2026
87203df
Content Guidelines: code review fixes
aagam-shah Feb 3, 2026
40276d6
Content Guidelines: always publish on save, bypass draft status
aagam-shah Feb 3, 2026
3fc0fc6
Content Guidelines: fix PHP class file naming and lint warnings
aagam-shah Feb 3, 2026
e727dcd
Enhance Content Guidelines REST Controller with sanitization and vali…
aagam-shah Feb 3, 2026
6178244
Content Guidelines: add maxLength to guideline textareas
aagam-shah Feb 3, 2026
0a49c83
Refactor sanitization in Gutenberg Content Guidelines REST Controller
aagam-shah Feb 3, 2026
a7fe73b
Content Guidelines: use post meta with revisions instead of JSON blob
aagam-shah Feb 11, 2026
0002050
Content Guidelines: register block meta at rest_api_init with content…
aagam-shah Feb 12, 2026
5f0e594
Content Guidelines: Remove UI, add block query parameter
aagam-shah Feb 13, 2026
f0e4534
Content Guidelines: Extend core REST controllers, use __experimental …
aagam-shah Feb 13, 2026
18992a5
Content Guidelines: Gate meta queries behind rest_is_field_included c…
aagam-shah Feb 13, 2026
fd3dcb3
Content Guidelines: Rename 'other' category to 'additional'
aagam-shah Feb 13, 2026
20c6e16
Content Guidelines: Fix private property access in revisions controller
aagam-shah Feb 13, 2026
6c482fd
Content Guidelines: Extract valid categories and statuses to shared c…
aagam-shah Feb 13, 2026
be8682e
Content Guidelines: Revert unrelated package.json and package-lock.js…
aagam-shah Feb 13, 2026
8bb77a4
Merge remote-tracking branch 'upstream/trunk' into ai-content-guideli…
aagam-shah Feb 13, 2026
6cd5ea7
Content Guidelines: Remove __experimental REST API namespace
aagam-shah Feb 16, 2026
2cd50d6
Content Guidelines: Address PR review feedback
aagam-shah Feb 16, 2026
1238a38
Content Guidelines: Add REST controller PHP unit tests
aagam-shah Feb 16, 2026
91ca879
Content Guidelines: Fix phpcs warnings in bootstrap and rest controller
aagam-shah Feb 16, 2026
9fbf058
Merge remote-tracking branch 'upstream/trunk' into ai-content-guideli…
aagam-shah Feb 18, 2026
aaaf17f
Merge branch 'trunk' into ai-content-guidelines-mvp
aagam-shah Feb 26, 2026
d260d42
Content Guidelines: Use prepare_date_response() for date formatting
aagam-shah Feb 26, 2026
d5033bc
Content Guidelines: Add test for unauthenticated read access
aagam-shah Feb 26, 2026
e9baf0f
Content Guidelines: Replace visual separators with @covers annotation…
aagam-shah Feb 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,260 @@
<?php
/**
* Content Guidelines Post Type registration.
*
* @package gutenberg
*/

if ( ! defined( 'ABSPATH' ) ) {
exit;
}

/**
* Handles registration of the Content Guidelines custom post type.
*/
class Gutenberg_Content_Guidelines_Post_Type {

/**
* The post type name.
*
* @var string
*/
const POST_TYPE = 'wp_content_guideline';

/**
* The standard guideline category meta keys.
*
* @var array
*/
const CATEGORY_META_KEYS = array(
'copy',
'images',
'site',
'additional',
);

/**
* All valid guideline category keys for filtering.
*
* Includes standard categories plus 'blocks'.
*
* @var array
*/
const VALID_CATEGORIES = array(
'copy',
'images',
'site',
'additional',
'blocks',
);

/**
* Valid guideline statuses.
*
* @var array
*/
const VALID_STATUSES = array(
'draft',
'publish',
);

/**
* Prefix for block-specific guideline meta keys.
*
* @var string
*/
const BLOCK_META_PREFIX = '_content_guideline_block_';

/**
* Register the custom post type.
*/
public static function register() {
$args = array(
'labels' => array(
'name' => __( 'Content Guidelines', 'gutenberg' ),
'singular_name' => __( 'Content Guidelines', 'gutenberg' ),
),
'public' => false,
'publicly_queryable' => false,
'show_ui' => false,
'show_in_menu' => false,
'show_in_rest' => true,
'rest_base' => 'content-guidelines',
'rest_controller_class' => 'Gutenberg_Content_Guidelines_REST_Controller',
'revisions_rest_controller_class' => 'Gutenberg_Content_Guidelines_Revisions_Controller',
'capability_type' => 'post',
'capabilities' => array(
'read' => 'edit_posts',
'create_posts' => 'manage_options',
'edit_posts' => 'manage_options',
'edit_published_posts' => 'manage_options',
'delete_posts' => 'manage_options',
'delete_published_posts' => 'manage_options',
'edit_others_posts' => 'manage_options',
'delete_others_posts' => 'manage_options',
'publish_posts' => 'manage_options',
),
'map_meta_cap' => true,
'supports' => array( 'revisions' ),
'hierarchical' => false,
'has_archive' => false,
'rewrite' => false,
'query_var' => false,
'can_export' => true,
);

register_post_type( self::POST_TYPE, $args );
}

/**
* Register post meta fields with revision support.
*/
public static function register_post_meta() {
$meta_args = array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
'revisions_enabled' => true,
'auth_callback' => function () {
return current_user_can( 'manage_options' );
},
'sanitize_callback' => 'sanitize_textarea_field',
);

// Register standard category meta.
foreach ( self::CATEGORY_META_KEYS as $category ) {
register_post_meta( self::POST_TYPE, '_content_guideline_' . $category, $meta_args );
}
Comment thread
aagam-shah marked this conversation as resolved.

// Register meta for content blocks.
foreach ( self::get_content_blocks() as $block_name ) {
register_post_meta( self::POST_TYPE, self::block_name_to_meta_key( $block_name ), $meta_args );
}
}

/**
* Get block names that have content role attributes.
*
* @return array Block names with content role.
*/
public static function get_content_blocks() {
$content_blocks = array();
$registry = WP_Block_Type_Registry::get_instance();

foreach ( $registry->get_all_registered() as $block_type ) {
if ( self::block_has_content_role( $block_type ) ) {
$content_blocks[] = $block_type->name;
}
}

return $content_blocks;
}

/**
* Check if a block type has any attribute with content role.
*
* @param WP_Block_Type $block_type The block type to check.
* @return bool True if block has content role attribute.
*/
private static function block_has_content_role( $block_type ) {
if ( empty( $block_type->attributes ) ) {
return false;
}

foreach ( $block_type->attributes as $attribute ) {
if ( isset( $attribute['role'] ) && 'content' === $attribute['role'] ) {
return true;
}
}

return false;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea for a follow up: check if you need to test whether block supports contentRole, e..g, ! empty( $block_type->supports['contentRole']

See isContentBlock() and the relevant docs.

It might not matter so much now, but later it it might avoid bugs if the definition of “content blocks” is the same in the backend and in the editor.


/**
* Convert a block name to a meta key.
*
* @param string $block_name The block name (e.g., 'core/paragraph').
* @return string The meta key (e.g., '_content_guideline_block_core_paragraph').
*/
public static function block_name_to_meta_key( $block_name ) {
// Replace '/' with '_' to create a valid meta key.
$sanitized = str_replace( '/', '_', $block_name );
return self::BLOCK_META_PREFIX . $sanitized;
}

/**
* Convert a meta key back to a block name.
*
* @param string $meta_key The meta key (e.g., '_content_guideline_block_core_paragraph').
* @return string The block name (e.g., 'core/paragraph').
*/
public static function meta_key_to_block_name( $meta_key ) {
// Remove prefix and convert first '_' back to '/'.
$without_prefix = str_replace( self::BLOCK_META_PREFIX, '', $meta_key );
// Replace first underscore with '/' (namespace separator).
return preg_replace( '/_/', '/', $without_prefix, 1 );
}

/**
* Check if a meta key is a block guideline meta key.
*
* @param string $meta_key The meta key to check.
* @return bool True if it's a block guideline meta key.
*/
public static function is_block_meta_key( $meta_key ) {
return strpos( $meta_key, self::BLOCK_META_PREFIX ) === 0;
}

/**
* Gets guideline categories from post meta.
*
* Shared between the post controller and revisions controller.
*
* @param int $post_id Post ID (can be a post or revision ID).
* @return array Guideline categories.
*/
public static function get_guideline_categories_from_meta( $post_id ) {
$category_labels = array(
'copy' => __( 'Copy Guidelines', 'gutenberg' ),
'images' => __( 'Image Guidelines', 'gutenberg' ),
'site' => __( 'Site Context', 'gutenberg' ),
'additional' => __( 'Additional Guidelines', 'gutenberg' ),
);

$guideline_categories = array();

// Get standard categories.
foreach ( self::CATEGORY_META_KEYS as $category ) {
$meta_key = '_content_guideline_' . $category;
$value = get_post_meta( $post_id, $meta_key, true );

$guideline_categories[ $category ] = array(
'label' => $category_labels[ $category ],
'guidelines' => $value,
);
}

// Get block-specific guidelines from individual meta keys.
$all_meta = get_post_meta( $post_id );

$blocks = array();
foreach ( $all_meta as $meta_key => $meta_values ) {
if ( self::is_block_meta_key( $meta_key ) ) {
$block_name = self::meta_key_to_block_name( $meta_key );

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also idea for follow up.

I was wondering if it'd help to also filter by get_content_blocks here as well. Not sure.

I'm thinking to get blocks that currently exist in the environment, and ignore stale meta entires from blocks no longer available due to a theme switch or plugin deactivation.

$value = isset( $meta_values[0] ) ? $meta_values[0] : '';

if ( ! empty( $value ) ) {
Comment thread
aagam-shah marked this conversation as resolved.
$blocks[ $block_name ] = array(
'guidelines' => $value,
);
}
}
}

if ( ! empty( $blocks ) ) {
$guideline_categories['blocks'] = $blocks;
}

return $guideline_categories;
}
}
Loading
Loading