From 17678a4b2fa16f85668c8fce6f0686ea4947fc4e Mon Sep 17 00:00:00 2001 From: Jorge Date: Fri, 17 Jun 2022 15:31:44 +0100 Subject: [PATCH 1/9] Add: Ability to use creation patterns for other post types besides page --- ...tenberg-rest-block-patterns-controller.php | 224 ++++++++++++++++++ lib/compat/wordpress-6.1/rest-api.php | 16 +- lib/load.php | 1 + .../components/start-page-options/index.js | 60 +++-- 4 files changed, 268 insertions(+), 33 deletions(-) create mode 100644 lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php new file mode 100644 index 0000000000000..98fbdd9d7d1d5 --- /dev/null +++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php @@ -0,0 +1,224 @@ +namespace = 'wp/v2'; + $this->rest_base = 'block-patterns/patterns'; + } + + /** + * Registers the routes for the objects of the controller. + * + * @since 6.0.0 + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ), + true + ); + } + + /** + * Checks whether a given request has permission to read block patterns. + * + * @since 6.0.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_items_permissions_check( $request ) { + if ( current_user_can( 'edit_posts' ) ) { + return true; + } + + foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { + if ( current_user_can( $post_type->cap->edit_posts ) ) { + return true; + } + } + + return new WP_Error( + 'rest_cannot_view', + __( 'Sorry, you are not allowed to view the registered block patterns.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + /** + * Retrieves all block patterns. + * + * @since 6.0.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + if ( ! $this->remote_patterns_loaded ) { + // Load block patterns from w.org. + _load_remote_block_patterns(); // Patterns with the `core` keyword. + _load_remote_featured_patterns(); // Patterns in the `featured` category. + _register_remote_theme_patterns(); // Patterns requested by current theme. + + $this->remote_patterns_loaded = true; + } + + $response = array(); + $patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered(); + foreach ( $patterns as $pattern ) { + $prepared_pattern = $this->prepare_item_for_response( $pattern, $request ); + $response[] = $this->prepare_response_for_collection( $prepared_pattern ); + } + return rest_ensure_response( $response ); + } + + /** + * Prepare a raw block pattern before it gets output in a REST API response. + * + * @since 6.0.0 + * + * @param array $item Raw pattern as registered, before any changes. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $keys = array( + 'name' => 'name', + 'title' => 'title', + 'description' => 'description', + 'viewportWidth' => 'viewport_width', + 'blockTypes' => 'block_types', + 'startContentPostTypes' => 'start_content_post_types', + 'categories' => 'categories', + 'keywords' => 'keywords', + 'content' => 'content', + 'inserter' => 'inserter', + ); + $data = array(); + foreach ( $keys as $item_key => $rest_key ) { + if ( isset( $item[ $item_key ] ) && rest_is_field_included( $rest_key, $fields ) ) { + $data[ $rest_key ] = $item[ $item_key ]; + } + } + + $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; + $data = $this->add_additional_fields_to_object( $data, $request ); + $data = $this->filter_response_by_context( $data, $context ); + return rest_ensure_response( $data ); + } + + /** + * Retrieves the block pattern schema, conforming to JSON Schema. + * + * @since 6.0.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'block-pattern', + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'description' => __( 'The pattern name.' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'title' => array( + 'description' => __( 'The pattern title, in human readable format.' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'description' => array( + 'description' => __( 'The pattern detailed description.' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'viewport_width' => array( + 'description' => __( 'The pattern viewport width for inserter preview.' ), + 'type' => 'number', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'block_types' => array( + 'description' => __( 'Block types that the pattern is intended to be used with.' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'start_content_post_types' => array( + 'description' => __( 'Post types where the pattern is intended to be used as the starting content.' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'categories' => array( + 'description' => __( 'The pattern category slugs.' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'keywords' => array( + 'description' => __( 'The pattern keywords.' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'content' => array( + 'description' => __( 'The pattern content.' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'inserter' => array( + 'description' => __( 'Determines whether the pattern is visible in inserter.' ), + 'type' => 'boolean', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } +} diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php index 30c68981c4f11..3c03fe207a27d 100644 --- a/lib/compat/wordpress-6.1/rest-api.php +++ b/lib/compat/wordpress-6.1/rest-api.php @@ -20,17 +20,11 @@ function gutenberg_update_templates_template_parts_rest_controller( $args, $post } add_filter( 'register_post_type_args', 'gutenberg_update_templates_template_parts_rest_controller', 10, 2 ); - /** - * Add the post type's `icon`(menu_icon) in the response. - * When we backport this change we will need to add the - * `icon` to WP_REST_Post_Types_Controller schema. - * - * @param WP_REST_Response $response The response object. - * @param WP_Post_Type $post_type The original post type object. + * Registers the block patterns REST API routes. */ -function gutenberg_update_post_types_rest_response( $response, $post_type ) { - $response->data['icon'] = $post_type->menu_icon; - return $response; +function gutenberg_register_gutenberg_rest_block_patterns() { + $block_patterns = new Gutenberg_REST_Block_Patterns_Controller(); + $block_patterns->register_routes(); } -add_filter( 'rest_prepare_post_type', 'gutenberg_update_post_types_rest_response', 10, 2 ); +add_action( 'rest_api_init', 'gutenberg_register_gutenberg_rest_block_patterns', 100 ); diff --git a/lib/load.php b/lib/load.php index f7cad4597b692..74431fa51d69c 100644 --- a/lib/load.php +++ b/lib/load.php @@ -64,6 +64,7 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/compat/wordpress-6.0/rest-api.php'; // WordPress 6.1 compat. + require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php'; require_once __DIR__ . '/compat/wordpress-6.1/class-gutenberg-rest-templates-controller.php'; require_once __DIR__ . '/compat/wordpress-6.1/rest-api.php'; diff --git a/packages/edit-post/src/components/start-page-options/index.js b/packages/edit-post/src/components/start-page-options/index.js index 3fc8740ca95b4..0f571beaabf56 100644 --- a/packages/edit-post/src/components/start-page-options/index.js +++ b/packages/edit-post/src/components/start-page-options/index.js @@ -3,7 +3,7 @@ */ import { Modal } from '@wordpress/components'; import { __ } from '@wordpress/i18n'; -import { useState, useEffect } from '@wordpress/element'; +import { useState, useEffect, useMemo } from '@wordpress/element'; import { store as blockEditorStore, __experimentalBlockPatternsList as BlockPatternsList, @@ -17,15 +17,32 @@ import { store as editorStore } from '@wordpress/editor'; */ import { store as editPostStore } from '../../store'; -function PatternSelection( { onChoosePattern } ) { - const { blockPatterns } = useSelect( ( select ) => { - const { __experimentalGetPatternsByBlockTypes } = - select( blockEditorStore ); +function useStartPatterns() { + const { blockPatterns, postType } = useSelect( ( select ) => { + const { __experimentalGetPatternsByBlockTypes } = select( + blockEditorStore + ); + const { getCurrentPostType } = select( editorStore ); return { - blockPatterns: - __experimentalGetPatternsByBlockTypes( 'core/post-content' ), + blockPatterns: __experimentalGetPatternsByBlockTypes( + 'core/post-content' + ), + postType: getCurrentPostType(), }; }, [] ); + return useMemo( () => { + return blockPatterns.filter( ( pattern ) => { + return ( + ( postType === 'page' && ! pattern.start_content_post_types ) || + ( Array.isArray( pattern.start_content_post_types ) && + pattern.start_content_post_types.includes( postType ) ) + ); + } ); + }, [ postType, blockPatterns ] ); +} + +function PatternSelection( { onChoosePattern } ) { + const blockPatterns = useStartPatterns(); const shownBlockPatterns = useAsyncList( blockPatterns ); const { resetEditorBlocks } = useDispatch( editorStore ); return ( @@ -50,31 +67,30 @@ export default function StartPageOptions() { const [ modalState, setModalState ] = useState( START_PAGE_MODAL_STATES.INITIAL ); + const blockPatterns = useStartPatterns(); + const hasStartPattern = blockPatterns.length > 0; const shouldOpenModel = useSelect( ( select ) => { - if ( modalState !== START_PAGE_MODAL_STATES.INITIAL ) { + if ( + ! hasStartPattern || + modalState !== START_PAGE_MODAL_STATES.INITIAL + ) { return false; } - const { __experimentalGetPatternsByBlockTypes } = - select( blockEditorStore ); - const { - getCurrentPostType, - getEditedPostContent, - isEditedPostSaveable, - } = select( editorStore ); - const { isEditingTemplate, isFeatureActive } = - select( editPostStore ); + const { getEditedPostContent, isEditedPostSaveable } = select( + editorStore + ); + const { isEditingTemplate, isFeatureActive } = select( + editPostStore + ); return ( - getCurrentPostType() === 'page' && ! isEditedPostSaveable() && '' === getEditedPostContent() && ! isEditingTemplate() && - ! isFeatureActive( 'welcomeGuide' ) && - __experimentalGetPatternsByBlockTypes( 'core/post-content' ) - .length >= 1 + ! isFeatureActive( 'welcomeGuide' ) ); }, - [ modalState ] + [ modalState, hasStartPattern ] ); useEffect( () => { From dfe94b9e54d8530a2fb72e0cf835c82100a21d21 Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 21 Jun 2022 11:44:48 +0100 Subject: [PATCH 2/9] linting --- ...tenberg-rest-block-patterns-controller.php | 18 ++++++++--------- .../components/start-page-options/index.js | 20 ++++++++----------- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php index 98fbdd9d7d1d5..d58d2234165db 100644 --- a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php @@ -156,31 +156,31 @@ public function get_item_schema() { 'title' => 'block-pattern', 'type' => 'object', 'properties' => array( - 'name' => array( + 'name' => array( 'description' => __( 'The pattern name.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'title' => array( + 'title' => array( 'description' => __( 'The pattern title, in human readable format.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'description' => array( + 'description' => array( 'description' => __( 'The pattern detailed description.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'viewport_width' => array( + 'viewport_width' => array( 'description' => __( 'The pattern viewport width for inserter preview.' ), 'type' => 'number', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'block_types' => array( + 'block_types' => array( 'description' => __( 'Block types that the pattern is intended to be used with.' ), 'type' => 'array', 'readonly' => true, @@ -192,25 +192,25 @@ public function get_item_schema() { 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'categories' => array( + 'categories' => array( 'description' => __( 'The pattern category slugs.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'keywords' => array( + 'keywords' => array( 'description' => __( 'The pattern keywords.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'content' => array( + 'content' => array( 'description' => __( 'The pattern content.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'inserter' => array( + 'inserter' => array( 'description' => __( 'Determines whether the pattern is visible in inserter.' ), 'type' => 'boolean', 'readonly' => true, diff --git a/packages/edit-post/src/components/start-page-options/index.js b/packages/edit-post/src/components/start-page-options/index.js index 0f571beaabf56..b2b624c5cccd0 100644 --- a/packages/edit-post/src/components/start-page-options/index.js +++ b/packages/edit-post/src/components/start-page-options/index.js @@ -19,14 +19,12 @@ import { store as editPostStore } from '../../store'; function useStartPatterns() { const { blockPatterns, postType } = useSelect( ( select ) => { - const { __experimentalGetPatternsByBlockTypes } = select( - blockEditorStore - ); + const { __experimentalGetPatternsByBlockTypes } = + select( blockEditorStore ); const { getCurrentPostType } = select( editorStore ); return { - blockPatterns: __experimentalGetPatternsByBlockTypes( - 'core/post-content' - ), + blockPatterns: + __experimentalGetPatternsByBlockTypes( 'core/post-content' ), postType: getCurrentPostType(), }; }, [] ); @@ -77,12 +75,10 @@ export default function StartPageOptions() { ) { return false; } - const { getEditedPostContent, isEditedPostSaveable } = select( - editorStore - ); - const { isEditingTemplate, isFeatureActive } = select( - editPostStore - ); + const { getEditedPostContent, isEditedPostSaveable } = + select( editorStore ); + const { isEditingTemplate, isFeatureActive } = + select( editPostStore ); return ( ! isEditedPostSaveable() && '' === getEditedPostContent() && From 291edceb055b4b28c463404182e74ed39fd45793 Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 21 Jun 2022 14:55:50 +0100 Subject: [PATCH 3/9] refactor to postTypes property --- ...tenberg-rest-block-patterns-controller.php | 40 +++++++++---------- packages/core-data/src/resolvers.js | 2 + .../components/start-page-options/index.js | 6 +-- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php index d58d2234165db..b3f3351223b73 100644 --- a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php @@ -119,16 +119,16 @@ public function get_items( $request ) { public function prepare_item_for_response( $item, $request ) { $fields = $this->get_fields_for_response( $request ); $keys = array( - 'name' => 'name', - 'title' => 'title', - 'description' => 'description', - 'viewportWidth' => 'viewport_width', - 'blockTypes' => 'block_types', - 'startContentPostTypes' => 'start_content_post_types', - 'categories' => 'categories', - 'keywords' => 'keywords', - 'content' => 'content', - 'inserter' => 'inserter', + 'name' => 'name', + 'title' => 'title', + 'description' => 'description', + 'viewportWidth' => 'viewport_width', + 'blockTypes' => 'block_types', + 'postTypes' => 'post_types', + 'categories' => 'categories', + 'keywords' => 'keywords', + 'content' => 'content', + 'inserter' => 'inserter', ); $data = array(); foreach ( $keys as $item_key => $rest_key ) { @@ -156,61 +156,61 @@ public function get_item_schema() { 'title' => 'block-pattern', 'type' => 'object', 'properties' => array( - 'name' => array( + 'name' => array( 'description' => __( 'The pattern name.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'title' => array( + 'title' => array( 'description' => __( 'The pattern title, in human readable format.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'description' => array( + 'description' => array( 'description' => __( 'The pattern detailed description.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'viewport_width' => array( + 'viewport_width' => array( 'description' => __( 'The pattern viewport width for inserter preview.' ), 'type' => 'number', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'block_types' => array( + 'block_types' => array( 'description' => __( 'Block types that the pattern is intended to be used with.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'start_content_post_types' => array( + 'post_types' => array( 'description' => __( 'Post types where the pattern is intended to be used as the starting content.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'categories' => array( + 'categories' => array( 'description' => __( 'The pattern category slugs.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'keywords' => array( + 'keywords' => array( 'description' => __( 'The pattern keywords.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'content' => array( + 'content' => array( 'description' => __( 'The pattern content.' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), - 'inserter' => array( + 'inserter' => array( 'description' => __( 'Determines whether the pattern is visible in inserter.' ), 'type' => 'boolean', 'readonly' => true, diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index d475a789b4ec7..a99876da226d4 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -480,6 +480,8 @@ export const getBlockPatterns = switch ( key ) { case 'block_types': return 'blockTypes'; + case 'post_types': + return 'postTypes'; case 'viewport_width': return 'viewportWidth'; default: diff --git a/packages/edit-post/src/components/start-page-options/index.js b/packages/edit-post/src/components/start-page-options/index.js index b2b624c5cccd0..c8a70caad03b6 100644 --- a/packages/edit-post/src/components/start-page-options/index.js +++ b/packages/edit-post/src/components/start-page-options/index.js @@ -31,9 +31,9 @@ function useStartPatterns() { return useMemo( () => { return blockPatterns.filter( ( pattern ) => { return ( - ( postType === 'page' && ! pattern.start_content_post_types ) || - ( Array.isArray( pattern.start_content_post_types ) && - pattern.start_content_post_types.includes( postType ) ) + ( postType === 'page' && ! pattern.postTypes ) || + ( Array.isArray( pattern.postTypes ) && + pattern.postTypes.includes( postType ) ) ); } ); }, [ postType, blockPatterns ] ); From 6184a66af2b6664cfde29493123ccac42486ccfb Mon Sep 17 00:00:00 2001 From: Jorge Date: Tue, 21 Jun 2022 16:31:11 +0100 Subject: [PATCH 4/9] fix merge issue. --- lib/compat/wordpress-6.1/rest-api.php | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/lib/compat/wordpress-6.1/rest-api.php b/lib/compat/wordpress-6.1/rest-api.php index 3c03fe207a27d..4ba0b328857b8 100644 --- a/lib/compat/wordpress-6.1/rest-api.php +++ b/lib/compat/wordpress-6.1/rest-api.php @@ -20,6 +20,21 @@ function gutenberg_update_templates_template_parts_rest_controller( $args, $post } add_filter( 'register_post_type_args', 'gutenberg_update_templates_template_parts_rest_controller', 10, 2 ); + +/** + * Add the post type's `icon`(menu_icon) in the response. + * When we backport this change we will need to add the + * `icon` to WP_REST_Post_Types_Controller schema. + * + * @param WP_REST_Response $response The response object. + * @param WP_Post_Type $post_type The original post type object. + */ +function gutenberg_update_post_types_rest_response( $response, $post_type ) { + $response->data['icon'] = $post_type->menu_icon; + return $response; +} +add_filter( 'rest_prepare_post_type', 'gutenberg_update_post_types_rest_response', 10, 2 ); + /** * Registers the block patterns REST API routes. */ From c0ef524b2a49bbe3aa2bbeba55a5dcd268165dcf Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Tue, 21 Jun 2022 17:05:31 +0100 Subject: [PATCH 5/9] Compat: Pull _register_theme_block_patterns into 6.1, replacing 6.0 shim No changes from Core's _register_theme_block_patterns, just a copy in preparation for the next commit. --- lib/compat/wordpress-6.0/block-patterns.php | 165 ------------------ lib/compat/wordpress-6.1/block-patterns.php | 175 ++++++++++++++++++++ lib/load.php | 1 + 3 files changed, 176 insertions(+), 165 deletions(-) create mode 100644 lib/compat/wordpress-6.1/block-patterns.php diff --git a/lib/compat/wordpress-6.0/block-patterns.php b/lib/compat/wordpress-6.0/block-patterns.php index 0992f8b08964e..772b910deb866 100644 --- a/lib/compat/wordpress-6.0/block-patterns.php +++ b/lib/compat/wordpress-6.0/block-patterns.php @@ -44,168 +44,3 @@ function gutenberg_register_remote_theme_patterns() { } } } - -/** - * Register any patterns that the active theme may provide under its - * `./patterns/` directory. Each pattern is defined as a PHP file and defines - * its metadata using plugin-style headers. The minimum required definition is: - * - * /** - * * Title: My Pattern - * * Slug: my-theme/my-pattern - * * - * - * The output of the PHP source corresponds to the content of the pattern, e.g.: - * - *

- * - * If applicable, this will collect from both parent and child theme. - * - * Other settable fields include: - * - * - Description - * - Viewport Width - * - Categories (comma-separated values) - * - Keywords (comma-separated values) - * - Block Types (comma-separated values) - * - Inserter (yes/no) - * - * @since 6.0.0 - * @access private - * @internal - */ -function gutenberg_register_theme_block_patterns() { - $default_headers = array( - 'title' => 'Title', - 'slug' => 'Slug', - 'description' => 'Description', - 'viewportWidth' => 'Viewport Width', - 'categories' => 'Categories', - 'keywords' => 'Keywords', - 'blockTypes' => 'Block Types', - 'inserter' => 'Inserter', - ); - - // Register patterns for the active theme. If the theme is a child theme, - // let it override any patterns from the parent theme that shares the same slug. - $themes = array(); - $stylesheet = get_stylesheet(); - $template = get_template(); - if ( $stylesheet !== $template ) { - $themes[] = wp_get_theme( $stylesheet ); - } - $themes[] = wp_get_theme( $template ); - - foreach ( $themes as $theme ) { - $dirpath = $theme->get_stylesheet_directory() . '/patterns/'; - if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) { - continue; - } - $files = glob( $dirpath . '*.php' ); - if ( $files ) { - foreach ( $files as $file ) { - $pattern_data = get_file_data( $file, $default_headers ); - - if ( empty( $pattern_data['slug'] ) ) { - _doing_it_wrong( - '_register_theme_block_patterns', - sprintf( - /* translators: %s: file name. */ - __( 'Could not register file "%s" as a block pattern ("Slug" field missing)', 'gutenberg' ), - $file - ), - '6.0.0' - ); - continue; - } - - if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) { - _doing_it_wrong( - '_register_theme_block_patterns', - sprintf( - /* translators: %1s: file name; %2s: slug value found. */ - __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")', 'gutenberg' ), - $file, - $pattern_data['slug'] - ), - '6.0.0' - ); - } - - if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) { - continue; - } - - // Title is a required property. - if ( ! $pattern_data['title'] ) { - _doing_it_wrong( - '_register_theme_block_patterns', - sprintf( - /* translators: %1s: file name; %2s: slug value found. */ - __( 'Could not register file "%s" as a block pattern ("Title" field missing)', 'gutenberg' ), - $file - ), - '6.0.0' - ); - continue; - } - - // For properties of type array, parse data as comma-separated. - foreach ( array( 'categories', 'keywords', 'blockTypes' ) as $property ) { - if ( ! empty( $pattern_data[ $property ] ) ) { - $pattern_data[ $property ] = array_filter( - preg_split( - '/[\s,]+/', - (string) $pattern_data[ $property ] - ) - ); - } else { - unset( $pattern_data[ $property ] ); - } - } - - // Parse properties of type int. - foreach ( array( 'viewportWidth' ) as $property ) { - if ( ! empty( $pattern_data[ $property ] ) ) { - $pattern_data[ $property ] = (int) $pattern_data[ $property ]; - } else { - unset( $pattern_data[ $property ] ); - } - } - - // Parse properties of type bool. - foreach ( array( 'inserter' ) as $property ) { - if ( ! empty( $pattern_data[ $property ] ) ) { - $pattern_data[ $property ] = in_array( - strtolower( $pattern_data[ $property ] ), - array( 'yes', 'true' ), - true - ); - } else { - unset( $pattern_data[ $property ] ); - } - } - - // Translate the pattern metadata. - $text_domain = $theme->get( 'TextDomain' ); - //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction - $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain ); - if ( ! empty( $pattern_data['description'] ) ) { - //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction - $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain ); - } - - // The actual pattern content is the output of the file. - ob_start(); - include $file; - $pattern_data['content'] = ob_get_clean(); - if ( ! $pattern_data['content'] ) { - continue; - } - - register_block_pattern( $pattern_data['slug'], $pattern_data ); - } - } - } -} -add_action( 'init', 'gutenberg_register_theme_block_patterns' ); diff --git a/lib/compat/wordpress-6.1/block-patterns.php b/lib/compat/wordpress-6.1/block-patterns.php new file mode 100644 index 0000000000000..65b922e6f7e92 --- /dev/null +++ b/lib/compat/wordpress-6.1/block-patterns.php @@ -0,0 +1,175 @@ +

+ * + * If applicable, this will collect from both parent and child theme. + * + * Other settable fields include: + * + * - Description + * - Viewport Width + * - Categories (comma-separated values) + * - Keywords (comma-separated values) + * - Block Types (comma-separated values) + * - Inserter (yes/no) + * + * @since 6.0.0 + * @access private + */ +function gutenberg_register_theme_block_patterns() { + $default_headers = array( + 'title' => 'Title', + 'slug' => 'Slug', + 'description' => 'Description', + 'viewportWidth' => 'Viewport Width', + 'categories' => 'Categories', + 'keywords' => 'Keywords', + 'blockTypes' => 'Block Types', + 'inserter' => 'Inserter', + ); + + /* + * Register patterns for the active theme. If the theme is a child theme, + * let it override any patterns from the parent theme that shares the same slug. + */ + $themes = array(); + $stylesheet = get_stylesheet(); + $template = get_template(); + if ( $stylesheet !== $template ) { + $themes[] = wp_get_theme( $stylesheet ); + } + $themes[] = wp_get_theme( $template ); + + foreach ( $themes as $theme ) { + $dirpath = $theme->get_stylesheet_directory() . '/patterns/'; + if ( ! is_dir( $dirpath ) || ! is_readable( $dirpath ) ) { + continue; + } + if ( file_exists( $dirpath ) ) { + $files = glob( $dirpath . '*.php' ); + if ( $files ) { + foreach ( $files as $file ) { + $pattern_data = get_file_data( $file, $default_headers ); + + if ( empty( $pattern_data['slug'] ) ) { + _doing_it_wrong( + '_register_theme_block_patterns', + sprintf( + /* translators: %s: file name. */ + __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ), + $file + ), + '6.0.0' + ); + continue; + } + + if ( ! preg_match( '/^[A-z0-9\/_-]+$/', $pattern_data['slug'] ) ) { + _doing_it_wrong( + '_register_theme_block_patterns', + sprintf( + /* translators: %1s: file name; %2s: slug value found. */ + __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ), + $file, + $pattern_data['slug'] + ), + '6.0.0' + ); + } + + if ( WP_Block_Patterns_Registry::get_instance()->is_registered( $pattern_data['slug'] ) ) { + continue; + } + + // Title is a required property. + if ( ! $pattern_data['title'] ) { + _doing_it_wrong( + '_register_theme_block_patterns', + sprintf( + /* translators: %1s: file name; %2s: slug value found. */ + __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ), + $file + ), + '6.0.0' + ); + continue; + } + + // For properties of type array, parse data as comma-separated. + foreach ( array( 'categories', 'keywords', 'blockTypes' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = array_filter( + preg_split( + '/[\s,]+/', + (string) $pattern_data[ $property ] + ) + ); + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Parse properties of type int. + foreach ( array( 'viewportWidth' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = (int) $pattern_data[ $property ]; + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Parse properties of type bool. + foreach ( array( 'inserter' ) as $property ) { + if ( ! empty( $pattern_data[ $property ] ) ) { + $pattern_data[ $property ] = in_array( + strtolower( $pattern_data[ $property ] ), + array( 'yes', 'true' ), + true + ); + } else { + unset( $pattern_data[ $property ] ); + } + } + + // Translate the pattern metadata. + $text_domain = $theme->get( 'TextDomain' ); + //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction + $pattern_data['title'] = translate_with_gettext_context( $pattern_data['title'], 'Pattern title', $text_domain ); + if ( ! empty( $pattern_data['description'] ) ) { + //phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralText, WordPress.WP.I18n.NonSingularStringLiteralContext, WordPress.WP.I18n.NonSingularStringLiteralDomain, WordPress.WP.I18n.LowLevelTranslationFunction + $pattern_data['description'] = translate_with_gettext_context( $pattern_data['description'], 'Pattern description', $text_domain ); + } + + // The actual pattern content is the output of the file. + ob_start(); + include $file; + $pattern_data['content'] = ob_get_clean(); + if ( ! $pattern_data['content'] ) { + continue; + } + + register_block_pattern( $pattern_data['slug'], $pattern_data ); + } + } + } + } +} +remove_action( 'init', '_register_theme_block_patterns' ); +add_action( 'init', 'gutenberg_register_theme_block_patterns' ); diff --git a/lib/load.php b/lib/load.php index 74431fa51d69c..887d5b7cc99e0 100644 --- a/lib/load.php +++ b/lib/load.php @@ -132,6 +132,7 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.1/wp-theme-get-post-templates.php'; require __DIR__ . '/compat/wordpress-6.1/script-loader.php'; require __DIR__ . '/compat/wordpress-6.1/date-settings.php'; +require __DIR__ . '/compat/wordpress-6.1/block-patterns.php'; // Experimental features. remove_action( 'plugins_loaded', '_wp_theme_json_webfonts_handler' ); // Turns off WP 6.0's stopgap handler for Webfonts API. From e274fc298c05c767e5e81d4b0415c8138dafb6bc Mon Sep 17 00:00:00 2001 From: Miguel Fonseca Date: Tue, 21 Jun 2022 17:10:08 +0100 Subject: [PATCH 6/9] gutenberg_register_theme_block_patterns: support postTypes field --- lib/compat/wordpress-6.1/block-patterns.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/compat/wordpress-6.1/block-patterns.php b/lib/compat/wordpress-6.1/block-patterns.php index 65b922e6f7e92..80c138cf9f2b1 100644 --- a/lib/compat/wordpress-6.1/block-patterns.php +++ b/lib/compat/wordpress-6.1/block-patterns.php @@ -28,6 +28,7 @@ * - Categories (comma-separated values) * - Keywords (comma-separated values) * - Block Types (comma-separated values) + * - Post Types (comma-separated values) * - Inserter (yes/no) * * @since 6.0.0 @@ -42,6 +43,7 @@ function gutenberg_register_theme_block_patterns() { 'categories' => 'Categories', 'keywords' => 'Keywords', 'blockTypes' => 'Block Types', + 'postTypes' => 'Post Types', 'inserter' => 'Inserter', ); @@ -113,7 +115,7 @@ function gutenberg_register_theme_block_patterns() { } // For properties of type array, parse data as comma-separated. - foreach ( array( 'categories', 'keywords', 'blockTypes' ) as $property ) { + foreach ( array( 'categories', 'keywords', 'blockTypes', 'postTypes' ) as $property ) { if ( ! empty( $pattern_data[ $property ] ) ) { $pattern_data[ $property ] = array_filter( preg_split( From 51e9ded314c47af1cdcdebb457763fd73b545594 Mon Sep 17 00:00:00 2001 From: Jorge Date: Wed, 22 Jun 2022 18:55:02 +0100 Subject: [PATCH 7/9] Documentation added and feedback applied. --- .../block-api/block-patterns.md | 15 ++++++++---- ...tenberg-rest-block-patterns-controller.php | 2 +- packages/core-data/src/resolvers.js | 24 +++++++++---------- 3 files changed, 23 insertions(+), 18 deletions(-) diff --git a/docs/reference-guides/block-api/block-patterns.md b/docs/reference-guides/block-api/block-patterns.md index f5aebbdef3a6a..12fac7b479361 100644 --- a/docs/reference-guides/block-api/block-patterns.md +++ b/docs/reference-guides/block-api/block-patterns.md @@ -3,10 +3,16 @@ Block Patterns are predefined block layouts, available from the patterns tab of the block inserter. Once inserted into content, the blocks are ready for additional or modified content and configuration. In this Document: -* [`register_block_pattern`](#register_block_pattern) -* [`unregister_block_pattern`](#unregister_block_pattern) -* [`register_block_pattern_category`](#register_block_pattern_category) -* [`unregister_block_pattern_category`](#unregister_block_pattern_category) +- [Patterns](#patterns) + - [Block Patterns](#block-patterns) + - [register_block_pattern](#register_block_pattern) + - [Unregistering Block Patterns](#unregistering-block-patterns) + - [unregister_block_pattern](#unregister_block_pattern) + - [Block Pattern Categories](#block-pattern-categories) + - [register_block_pattern_category](#register_block_pattern_category) + - [unregister_block_pattern_category](#unregister_block_pattern_category) + - [Block patterns contextual to block types and pattern transformations](#block-patterns-contextual-to-block-types-and-pattern-transformations) + - [Semantic block patterns](#semantic-block-patterns) ## Block Patterns @@ -27,6 +33,7 @@ The properties available for block patterns are: - `keywords` (optional): An array of aliases or keywords that help users discover the pattern while searching. - `viewportWidth` (optional): An integer specifying the intended width of the pattern to allow for a scaled preview of the pattern in the inserter. - `blockTypes` (optional): An array of block types that the pattern is intended to be used with. Each value needs to be the declared block's `name`. +- `postTypes` (optional): An array of post types that the pattern is restricted to be used with. The pattern will only be available when editing one of the post types passed on the array, for all the other post types the pattern is not available at all. - `inserter` (optional): By default, all patterns will appear in the inserter. To hide a pattern so that it can only be inserted programmatically, set the `inserter` to `false`. The following code sample registers a block pattern named 'my-plugin/my-awesome-pattern': diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php index b3f3351223b73..bc5bded5d7ecb 100644 --- a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php @@ -187,7 +187,7 @@ public function get_item_schema() { 'context' => array( 'view', 'edit', 'embed' ), ), 'post_types' => array( - 'description' => __( 'Post types where the pattern is intended to be used as the starting content.' ), + 'description' => __( ' An array of post types that the pattern is restricted to be used with.' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index a99876da226d4..b47e879ecc301 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -1,7 +1,16 @@ /** * External dependencies */ -import { find, includes, get, compact, uniq, map, mapKeys } from 'lodash'; +import { + camelCase, + compact, + find, + get, + includes, + map, + mapKeys, + uniq, +} from 'lodash'; /** * WordPress dependencies @@ -476,18 +485,7 @@ export const getBlockPatterns = path: '/wp/v2/block-patterns/patterns', } ); const patterns = map( restPatterns, ( pattern ) => - mapKeys( pattern, ( value, key ) => { - switch ( key ) { - case 'block_types': - return 'blockTypes'; - case 'post_types': - return 'postTypes'; - case 'viewport_width': - return 'viewportWidth'; - default: - return key; - } - } ) + mapKeys( pattern, ( value, key ) => camelCase( key ) ) ); dispatch( { type: 'RECEIVE_BLOCK_PATTERNS', patterns } ); }; From c8d54c17fcca4a54eacf504ba976d7a1c96a1472 Mon Sep 17 00:00:00 2001 From: Jorge Date: Wed, 22 Jun 2022 22:10:12 +0100 Subject: [PATCH 8/9] Improve documentation --- .../components/start-page-options/index.js | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/edit-post/src/components/start-page-options/index.js b/packages/edit-post/src/components/start-page-options/index.js index c8a70caad03b6..ce86984e032c2 100644 --- a/packages/edit-post/src/components/start-page-options/index.js +++ b/packages/edit-post/src/components/start-page-options/index.js @@ -18,25 +18,37 @@ import { store as editorStore } from '@wordpress/editor'; import { store as editPostStore } from '../../store'; function useStartPatterns() { - const { blockPatterns, postType } = useSelect( ( select ) => { - const { __experimentalGetPatternsByBlockTypes } = - select( blockEditorStore ); - const { getCurrentPostType } = select( editorStore ); - return { - blockPatterns: - __experimentalGetPatternsByBlockTypes( 'core/post-content' ), - postType: getCurrentPostType(), - }; - }, [] ); + // A pattern is a start pattern if it includes 'core/post-content' in its blockTypes, + // and it has no postTypes declares and the current post type is page or if + // the current post type is part of the postTypes declared. + const { blockPatternsWithPostContentBlockType, postType } = useSelect( + ( select ) => { + const { __experimentalGetPatternsByBlockTypes } = + select( blockEditorStore ); + const { getCurrentPostType } = select( editorStore ); + return { + // get pa + blockPatternsWithPostContentBlockType: + __experimentalGetPatternsByBlockTypes( + 'core/post-content' + ), + postType: getCurrentPostType(), + }; + }, + [] + ); + return useMemo( () => { - return blockPatterns.filter( ( pattern ) => { + // filter patterns without postTypes declared if the current postType is page + // or patterns that declare the current postType in its post type array. + return blockPatternsWithPostContentBlockType.filter( ( pattern ) => { return ( ( postType === 'page' && ! pattern.postTypes ) || ( Array.isArray( pattern.postTypes ) && pattern.postTypes.includes( postType ) ) ); } ); - }, [ postType, blockPatterns ] ); + }, [ postType, blockPatternsWithPostContentBlockType ] ); } function PatternSelection( { onChoosePattern } ) { From a7f2181526bd780d9835f953baded8b14d1be3f2 Mon Sep 17 00:00:00 2001 From: Jorge Date: Thu, 23 Jun 2022 15:04:21 +0100 Subject: [PATCH 9/9] lint fixes --- lib/compat/wordpress-6.1/block-patterns.php | 6 ++--- ...tenberg-rest-block-patterns-controller.php | 24 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/compat/wordpress-6.1/block-patterns.php b/lib/compat/wordpress-6.1/block-patterns.php index 80c138cf9f2b1..7860aa6a1dd66 100644 --- a/lib/compat/wordpress-6.1/block-patterns.php +++ b/lib/compat/wordpress-6.1/block-patterns.php @@ -75,7 +75,7 @@ function gutenberg_register_theme_block_patterns() { '_register_theme_block_patterns', sprintf( /* translators: %s: file name. */ - __( 'Could not register file "%s" as a block pattern ("Slug" field missing)' ), + __( 'Could not register file "%s" as a block pattern ("Slug" field missing)', 'gutenberg' ), $file ), '6.0.0' @@ -88,7 +88,7 @@ function gutenberg_register_theme_block_patterns() { '_register_theme_block_patterns', sprintf( /* translators: %1s: file name; %2s: slug value found. */ - __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")' ), + __( 'Could not register file "%1$s" as a block pattern (invalid slug "%2$s")', 'gutenberg' ), $file, $pattern_data['slug'] ), @@ -106,7 +106,7 @@ function gutenberg_register_theme_block_patterns() { '_register_theme_block_patterns', sprintf( /* translators: %1s: file name; %2s: slug value found. */ - __( 'Could not register file "%s" as a block pattern ("Title" field missing)' ), + __( 'Could not register file "%s" as a block pattern ("Title" field missing)', 'gutenberg' ), $file ), '6.0.0' diff --git a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php index bc5bded5d7ecb..a19a34b523e8b 100644 --- a/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php +++ b/lib/compat/wordpress-6.1/class-gutenberg-rest-block-patterns-controller.php @@ -62,7 +62,7 @@ public function register_routes() { * @param WP_REST_Request $request Full details about the request. * @return true|WP_Error True if the request has read access, WP_Error object otherwise. */ - public function get_items_permissions_check( $request ) { + public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable if ( current_user_can( 'edit_posts' ) ) { return true; } @@ -75,7 +75,7 @@ public function get_items_permissions_check( $request ) { return new WP_Error( 'rest_cannot_view', - __( 'Sorry, you are not allowed to view the registered block patterns.' ), + __( 'Sorry, you are not allowed to view the registered block patterns.', 'gutenberg' ), array( 'status' => rest_authorization_required_code() ) ); } @@ -157,61 +157,61 @@ public function get_item_schema() { 'type' => 'object', 'properties' => array( 'name' => array( - 'description' => __( 'The pattern name.' ), + 'description' => __( 'The pattern name.', 'gutenberg' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'title' => array( - 'description' => __( 'The pattern title, in human readable format.' ), + 'description' => __( 'The pattern title, in human readable format.', 'gutenberg' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'description' => array( - 'description' => __( 'The pattern detailed description.' ), + 'description' => __( 'The pattern detailed description.', 'gutenberg' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'viewport_width' => array( - 'description' => __( 'The pattern viewport width for inserter preview.' ), + 'description' => __( 'The pattern viewport width for inserter preview.', 'gutenberg' ), 'type' => 'number', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'block_types' => array( - 'description' => __( 'Block types that the pattern is intended to be used with.' ), + 'description' => __( 'Block types that the pattern is intended to be used with.', 'gutenberg' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'post_types' => array( - 'description' => __( ' An array of post types that the pattern is restricted to be used with.' ), + 'description' => __( ' An array of post types that the pattern is restricted to be used with.', 'gutenberg' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'categories' => array( - 'description' => __( 'The pattern category slugs.' ), + 'description' => __( 'The pattern category slugs.', 'gutenberg' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'keywords' => array( - 'description' => __( 'The pattern keywords.' ), + 'description' => __( 'The pattern keywords.', 'gutenberg' ), 'type' => 'array', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'content' => array( - 'description' => __( 'The pattern content.' ), + 'description' => __( 'The pattern content.', 'gutenberg' ), 'type' => 'string', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ), ), 'inserter' => array( - 'description' => __( 'Determines whether the pattern is visible in inserter.' ), + 'description' => __( 'Determines whether the pattern is visible in inserter.', 'gutenberg' ), 'type' => 'boolean', 'readonly' => true, 'context' => array( 'view', 'edit', 'embed' ),