From c819200904335a4ce4c67cf48ee64a7427e0a09c Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Wed, 21 Jun 2023 12:27:21 +1000 Subject: [PATCH] Patterns: Allow for filtering of block patterns by source (#51672) --------- Co-authored-by: kevin940726 Co-authored-by: Daniel Richards --- .../block-api/block-patterns.md | 1 + lib/compat/wordpress-6.2/block-patterns.php | 125 ------------- lib/compat/wordpress-6.2/rest-api.php | 9 - lib/compat/wordpress-6.3/block-patterns.php | 165 ++++++++++++++++++ ...erg-rest-block-patterns-controller-6-3.php | 155 ++++++++++++++++ lib/compat/wordpress-6.3/rest-api.php | 9 + lib/load.php | 2 + 7 files changed, 332 insertions(+), 134 deletions(-) create mode 100644 lib/compat/wordpress-6.3/block-patterns.php create mode 100644 lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php diff --git a/docs/reference-guides/block-api/block-patterns.md b/docs/reference-guides/block-api/block-patterns.md index 68e47d5094237..a1da09d63a6c9 100644 --- a/docs/reference-guides/block-api/block-patterns.md +++ b/docs/reference-guides/block-api/block-patterns.md @@ -36,6 +36,7 @@ The properties available for block patterns are: - `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. - `templateTypes` (optional): An array of template types where the pattern makes sense e.g: '404' if the pattern is for a 404 page, single-post if the pattern is for showing a single post. - `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`. +- `source` (optional): A string that denotes the source of the pattern. For a plugin registering a pattern, pass the string 'plugin'. For a theme, pass the string 'theme'. The following code sample registers a block pattern named 'my-plugin/my-awesome-pattern': diff --git a/lib/compat/wordpress-6.2/block-patterns.php b/lib/compat/wordpress-6.2/block-patterns.php index 2112a8c2c890f..12b19bdf4c54e 100644 --- a/lib/compat/wordpress-6.2/block-patterns.php +++ b/lib/compat/wordpress-6.2/block-patterns.php @@ -331,128 +331,3 @@ function gutenberg_normalize_remote_pattern( $pattern ) { return (array) $pattern; } - -/** - * Register Core's official patterns from wordpress.org/patterns. - * - * @since 5.8.0 - * @since 5.9.0 The $current_screen argument was removed. - * @since 6.2.0 Normalize the pattern from the API (snake_case) to the format expected by `register_block_pattern` (camelCase). - * - * @param WP_Screen $deprecated Unused. Formerly the screen that the current request was triggered from. - */ -function gutenberg_load_remote_block_patterns( $deprecated = null ) { - if ( ! empty( $deprecated ) ) { - _deprecated_argument( __FUNCTION__, '5.9.0' ); - $current_screen = $deprecated; - if ( ! $current_screen->is_block_editor ) { - return; - } - } - - $supports_core_patterns = get_theme_support( 'core-block-patterns' ); - - /** - * Filter to disable remote block patterns. - * - * @since 5.8.0 - * - * @param bool $should_load_remote - */ - $should_load_remote = apply_filters( 'should_load_remote_block_patterns', true ); - - if ( $supports_core_patterns && $should_load_remote ) { - $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); - $core_keyword_id = 11; // 11 is the ID for "core". - $request->set_param( 'keyword', $core_keyword_id ); - $response = rest_do_request( $request ); - if ( $response->is_error() ) { - return; - } - $patterns = $response->get_data(); - - foreach ( $patterns as $pattern ) { - $normalized_pattern = gutenberg_normalize_remote_pattern( $pattern ); - $pattern_name = 'core/' . sanitize_title( $normalized_pattern['title'] ); - register_block_pattern( $pattern_name, (array) $normalized_pattern ); - } - } -} - -/** - * Register `Featured` (category) patterns from wordpress.org/patterns. - * - * @since 5.9.0 - * @since 6.2.0 Normalize the pattern from the API (snake_case) to the format expected by `register_block_pattern` (camelCase). - */ -function gutenberg_load_remote_featured_patterns() { - $supports_core_patterns = get_theme_support( 'core-block-patterns' ); - - /** This filter is documented in wp-includes/block-patterns.php */ - $should_load_remote = apply_filters( 'should_load_remote_block_patterns', true ); - - if ( ! $should_load_remote || ! $supports_core_patterns ) { - return; - } - - $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); - $featured_cat_id = 26; // This is the `Featured` category id from pattern directory. - $request->set_param( 'category', $featured_cat_id ); - $response = rest_do_request( $request ); - if ( $response->is_error() ) { - return; - } - $patterns = $response->get_data(); - $registry = WP_Block_Patterns_Registry::get_instance(); - foreach ( $patterns as $pattern ) { - $normalized_pattern = gutenberg_normalize_remote_pattern( $pattern ); - $pattern_name = sanitize_title( $normalized_pattern['title'] ); - // Some patterns might be already registered as core patterns with the `core` prefix. - $is_registered = $registry->is_registered( $pattern_name ) || $registry->is_registered( "core/$pattern_name" ); - if ( ! $is_registered ) { - register_block_pattern( $pattern_name, (array) $normalized_pattern ); - } - } -} - -/** - * Registers patterns from Pattern Directory provided by a theme's - * `theme.json` file. - * - * @since 6.0.0 - * @since 6.2.0 Normalize the pattern from the API (snake_case) to the format expected by `register_block_pattern` (camelCase). - * @access private - */ -function gutenberg_register_remote_theme_patterns() { - /** This filter is documented in wp-includes/block-patterns.php */ - if ( ! apply_filters( 'should_load_remote_block_patterns', true ) ) { - return; - } - - if ( ! wp_theme_has_theme_json() ) { - return; - } - - $pattern_settings = gutenberg_get_remote_theme_patterns(); - if ( empty( $pattern_settings ) ) { - return; - } - - $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); - $request['slug'] = $pattern_settings; - $response = rest_do_request( $request ); - if ( $response->is_error() ) { - return; - } - $patterns = $response->get_data(); - $patterns_registry = WP_Block_Patterns_Registry::get_instance(); - foreach ( $patterns as $pattern ) { - $normalized_pattern = gutenberg_normalize_remote_pattern( $pattern ); - $pattern_name = sanitize_title( $normalized_pattern['title'] ); - // Some patterns might be already registered as core patterns with the `core` prefix. - $is_registered = $patterns_registry->is_registered( $pattern_name ) || $patterns_registry->is_registered( "core/$pattern_name" ); - if ( ! $is_registered ) { - register_block_pattern( $pattern_name, (array) $normalized_pattern ); - } - } -} diff --git a/lib/compat/wordpress-6.2/rest-api.php b/lib/compat/wordpress-6.2/rest-api.php index b648ba9bae015..c0f098fa6893a 100644 --- a/lib/compat/wordpress-6.2/rest-api.php +++ b/lib/compat/wordpress-6.2/rest-api.php @@ -14,15 +14,6 @@ function gutenberg_register_rest_block_pattern_categories() { } add_action( 'rest_api_init', 'gutenberg_register_rest_block_pattern_categories' ); -/** - * Registers the block patterns REST API routes. - */ -function gutenberg_register_rest_block_patterns() { - $block_patterns = new Gutenberg_REST_Block_Patterns_Controller_6_2(); - $block_patterns->register_routes(); -} -add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' ); - /** * Add extra collection params to pattern directory requests. * diff --git a/lib/compat/wordpress-6.3/block-patterns.php b/lib/compat/wordpress-6.3/block-patterns.php new file mode 100644 index 0000000000000..b620317f86966 --- /dev/null +++ b/lib/compat/wordpress-6.3/block-patterns.php @@ -0,0 +1,165 @@ +is_block_editor ) { + return; + } + } + + $supports_core_patterns = get_theme_support( 'core-block-patterns' ); + + /** + * Filter to disable remote block patterns. + * + * @since 5.8.0 + * + * @param bool $should_load_remote + */ + $should_load_remote = apply_filters( 'should_load_remote_block_patterns', true ); + + if ( $supports_core_patterns && $should_load_remote ) { + $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); + $core_keyword_id = 11; // 11 is the ID for "core". + $request->set_param( 'keyword', $core_keyword_id ); + $response = rest_do_request( $request ); + if ( $response->is_error() ) { + return; + } + $patterns = $response->get_data(); + + foreach ( $patterns as $pattern ) { + $pattern['source'] = 'pattern-directory/core'; // Added in 6.3.0. + $normalized_pattern = gutenberg_normalize_remote_pattern( $pattern ); + $pattern_name = 'core/' . sanitize_title( $normalized_pattern['title'] ); + register_block_pattern( $pattern_name, (array) $normalized_pattern ); + } + } +} + +/** + * Register `Featured` (category) patterns from wordpress.org/patterns. + * + * @since 5.9.0 + * @since 6.2.0 Normalize the pattern from the API (snake_case) to the format expected by `register_block_pattern` (camelCase). + * @since 6.3.0 Add 'pattern-directory/featured' to the pattern's 'source'. + */ +function gutenberg_load_remote_featured_patterns() { + $supports_core_patterns = get_theme_support( 'core-block-patterns' ); + + /** This filter is documented in wp-includes/block-patterns.php */ + $should_load_remote = apply_filters( 'should_load_remote_block_patterns', true ); + + if ( ! $should_load_remote || ! $supports_core_patterns ) { + return; + } + + $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); + $featured_cat_id = 26; // This is the `Featured` category id from pattern directory. + $request->set_param( 'category', $featured_cat_id ); + $response = rest_do_request( $request ); + if ( $response->is_error() ) { + return; + } + $patterns = $response->get_data(); + $registry = WP_Block_Patterns_Registry::get_instance(); + foreach ( $patterns as $pattern ) { + $pattern['source'] = 'pattern-directory/featured'; // Added in 6.3.0. + $normalized_pattern = gutenberg_normalize_remote_pattern( $pattern ); + $pattern_name = sanitize_title( $normalized_pattern['title'] ); + // Some patterns might be already registered as core patterns with the `core` prefix. + $is_registered = $registry->is_registered( $pattern_name ) || $registry->is_registered( "core/$pattern_name" ); + if ( ! $is_registered ) { + register_block_pattern( $pattern_name, (array) $normalized_pattern ); + } + } +} + +/** + * Registers patterns from Pattern Directory provided by a theme's + * `theme.json` file. + * + * @since 6.0.0 + * @since 6.2.0 Normalize the pattern from the API (snake_case) to the format expected by `register_block_pattern` (camelCase). + * @since 6.3.0 Add 'pattern-directory/theme' to the pattern's 'source'. + * @access private + */ +function gutenberg_register_remote_theme_patterns() { + /** This filter is documented in wp-includes/block-patterns.php */ + if ( ! apply_filters( 'should_load_remote_block_patterns', true ) ) { + return; + } + + if ( ! wp_theme_has_theme_json() ) { + return; + } + + $pattern_settings = gutenberg_get_remote_theme_patterns(); + if ( empty( $pattern_settings ) ) { + return; + } + + $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); + $request['slug'] = $pattern_settings; + $response = rest_do_request( $request ); + if ( $response->is_error() ) { + return; + } + $patterns = $response->get_data(); + $patterns_registry = WP_Block_Patterns_Registry::get_instance(); + foreach ( $patterns as $pattern ) { + $pattern['source'] = 'pattern-directory/theme'; // Added in 6.3.0. + $normalized_pattern = gutenberg_normalize_remote_pattern( $pattern ); + $pattern_name = sanitize_title( $normalized_pattern['title'] ); + // Some patterns might be already registered as core patterns with the `core` prefix. + $is_registered = $patterns_registry->is_registered( $pattern_name ) || $patterns_registry->is_registered( "core/$pattern_name" ); + if ( ! $is_registered ) { + register_block_pattern( $pattern_name, (array) $normalized_pattern ); + } + } +} diff --git a/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php b/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php new file mode 100644 index 0000000000000..036864bd8190f --- /dev/null +++ b/lib/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php @@ -0,0 +1,155 @@ +get_fields_for_response( $request ); + $keys = array( + 'name' => 'name', + 'title' => 'title', + 'description' => 'description', + 'viewportWidth' => 'viewport_width', + 'blockTypes' => 'block_types', + 'postTypes' => 'post_types', + 'categories' => 'categories', + 'keywords' => 'keywords', + 'content' => 'content', + 'inserter' => 'inserter', + 'templateTypes' => 'template_types', + 'source' => 'source', + ); + $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 + * @since 6.1.0 Added `post_types` property. + * @since 6.3.0 Added `source` property. + * + * @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.', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'title' => array( + '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.', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'viewport_width' => array( + '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.', '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.', 'gutenberg' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'categories' => array( + 'description' => __( 'The pattern category slugs.', 'gutenberg' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'keywords' => array( + 'description' => __( 'The pattern keywords.', 'gutenberg' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'template_types' => array( + 'description' => __( 'An array of template types where the pattern fits.', 'gutenberg' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'content' => array( + '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.', 'gutenberg' ), + 'type' => 'boolean', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'source' => array( + 'description' => __( 'Where the pattern comes from e.g. core', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'edit', 'embed' ), + 'enum' => array( + 'core', + 'plugin', + 'theme', + 'pattern-directory/core', + 'pattern-directory/theme', + 'pattern-directory/featured', + ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } +} diff --git a/lib/compat/wordpress-6.3/rest-api.php b/lib/compat/wordpress-6.3/rest-api.php index a2dd66c5e922f..ed6832c3b8411 100644 --- a/lib/compat/wordpress-6.3/rest-api.php +++ b/lib/compat/wordpress-6.3/rest-api.php @@ -101,3 +101,12 @@ function add_modified_wp_template_schema() { ); } add_filter( 'rest_api_init', 'add_modified_wp_template_schema' ); + +/** + * Registers the block patterns REST API routes. + */ +function gutenberg_register_rest_block_patterns() { + $block_patterns = new Gutenberg_REST_Block_Patterns_Controller_6_3(); + $block_patterns->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' ); diff --git a/lib/load.php b/lib/load.php index afda5db277a52..0fd23fc28f513 100644 --- a/lib/load.php +++ b/lib/load.php @@ -44,6 +44,7 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/compat/wordpress-6.2/class-gutenberg-rest-global-styles-controller-6-2.php'; // WordPress 6.3 compat. + require_once __DIR__ . '/compat/wordpress-6.3/class-gutenberg-rest-block-patterns-controller-6-3.php'; require_once __DIR__ . '/compat/wordpress-6.3/class-gutenberg-rest-templates-controller-6-3.php'; require_once __DIR__ . '/compat/wordpress-6.3/class-gutenberg-rest-global-styles-controller-6-3.php'; require_once __DIR__ . '/compat/wordpress-6.3/class-gutenberg-rest-global-styles-revisions-controller-6-3.php'; @@ -52,6 +53,7 @@ function gutenberg_is_experiment_enabled( $name ) { require_once __DIR__ . '/compat/wordpress-6.3/theme-previews.php'; require_once __DIR__ . '/compat/wordpress-6.3/navigation-block-preloading.php'; require_once __DIR__ . '/compat/wordpress-6.3/link-template.php'; + require_once __DIR__ . '/compat/wordpress-6.3/block-patterns.php'; // Experimental. if ( ! class_exists( 'WP_Rest_Customizer_Nonces' ) ) {