diff --git a/docs/reference-guides/data/data-core.md b/docs/reference-guides/data/data-core.md index 3353b1dd41c24..aa493c7dcd997 100644 --- a/docs/reference-guides/data/data-core.md +++ b/docs/reference-guides/data/data-core.md @@ -94,6 +94,30 @@ _Returns_ - `?Array`: An array of autosaves for the post, or undefined if there is none. +### getBlockPatternCategories + +Retrieve the list of registered block pattern categories. + +_Parameters_ + +- _state_ `Object`: Data state. + +_Returns_ + +- `Array`: Block pattern category list. + +### getBlockPatterns + +Retrieve the list of registered block patterns. + +_Parameters_ + +- _state_ `Object`: Data state. + +_Returns_ + +- `Array`: Block pattern list. + ### getCurrentTheme Return the current theme. diff --git a/lib/compat/wordpress-5.9/block-patterns.php b/lib/compat/wordpress-5.9/block-patterns.php index 46fed5f2a26cc..b79869ea39bd2 100644 --- a/lib/compat/wordpress-5.9/block-patterns.php +++ b/lib/compat/wordpress-5.9/block-patterns.php @@ -17,9 +17,11 @@ function _load_remote_featured_patterns() { * * @param bool $should_load_remote */ - $should_load_remote = apply_filters( 'should_load_remote_block_patterns', true ); + if ( ! apply_filters( 'should_load_remote_block_patterns', true ) ) { + return; + } - if ( ! $should_load_remote ) { + if ( ! get_theme_support( 'core-block-patterns' ) ) { return; } @@ -43,18 +45,4 @@ function _load_remote_featured_patterns() { } } } - - add_action( - 'current_screen', - function( $current_screen ) { - if ( ! get_theme_support( 'core-block-patterns' ) ) { - return; - } - - $is_site_editor = ( function_exists( 'gutenberg_is_edit_site_page' ) && gutenberg_is_edit_site_page( $current_screen->id ) ); - if ( $current_screen->is_block_editor || $is_site_editor ) { - _load_remote_featured_patterns(); - } - } - ); } diff --git a/lib/compat/wordpress-6.0/block-editor-settings.php b/lib/compat/wordpress-6.0/block-editor-settings.php index 7a8b5091a279e..c070f64c7bf51 100644 --- a/lib/compat/wordpress-6.0/block-editor-settings.php +++ b/lib/compat/wordpress-6.0/block-editor-settings.php @@ -166,3 +166,18 @@ function_exists( 'gutenberg_is_edit_site_page' ) && } add_filter( 'block_editor_settings_all', 'gutenberg_get_block_editor_settings', PHP_INT_MAX ); + +/** + * Removes the unwanted block patterns fields from block editor settings. + * + * @param array $settings Existing block editor settings. + * + * @return array New block editor settings. + */ +function gutenberg_remove_block_patterns_settings( $settings ) { + unset( $settings['__experimentalBlockPatterns'] ); + unset( $settings['__experimentalBlockPatternCategories'] ); + return $settings; +} + +add_filter( 'block_editor_settings_all', 'gutenberg_remove_block_patterns_settings', PHP_INT_MAX ); diff --git a/lib/compat/wordpress-6.0/block-patterns.php b/lib/compat/wordpress-6.0/block-patterns.php index b9ca9107fe413..6c7517888124d 100644 --- a/lib/compat/wordpress-6.0/block-patterns.php +++ b/lib/compat/wordpress-6.0/block-patterns.php @@ -10,10 +10,23 @@ * `theme.json` file. */ function gutenberg_register_remote_theme_patterns() { + if ( ! get_theme_support( 'core-block-patterns' ) ) { + return; + } + + if ( ! apply_filters( 'should_load_remote_block_patterns', true ) ) { + return; + } + + if ( ! WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { + return; + } + $pattern_settings = WP_Theme_JSON_Resolver_Gutenberg::get_theme_data()->get_patterns(); if ( empty( $pattern_settings ) ) { return; } + $request = new WP_REST_Request( 'GET', '/wp/v2/pattern-directory/patterns' ); $request['slug'] = implode( ',', $pattern_settings ); $response = rest_do_request( $request ); @@ -32,26 +45,6 @@ function gutenberg_register_remote_theme_patterns() { } } -add_action( - 'current_screen', - function( $current_screen ) { - if ( ! get_theme_support( 'core-block-patterns' ) ) { - return; - } - if ( ! apply_filters( 'should_load_remote_block_patterns', true ) ) { - return; - } - if ( ! WP_Theme_JSON_Resolver_Gutenberg::theme_has_support() ) { - return; - } - - $is_site_editor = ( function_exists( 'gutenberg_is_edit_site_page' ) && gutenberg_is_edit_site_page( $current_screen->id ) ); - if ( $current_screen->is_block_editor || $is_site_editor ) { - 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 diff --git a/lib/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php b/lib/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php new file mode 100644 index 0000000000000..8edc2080b33dd --- /dev/null +++ b/lib/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php @@ -0,0 +1,138 @@ +namespace = '__experimental'; + $this->rest_base = 'block-patterns/categories'; + } + + /** + * Registers the routes for the objects of the controller. + * + * @see register_rest_route() + */ + 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' ), + 'allow_batch' => array( 'v1' => true ), + ) + ); + } + + /** + * Checks whether a given request has permission to read block patterns. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_Error|bool True if the request has read access, WP_Error object otherwise. + */ + public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + 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 pattern categories.', 'gutenberg' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + /** + * Retrieves all block pattern categories. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + $response = array(); + $categories = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(); + foreach ( $categories as $category ) { + $prepared_category = $this->prepare_item_for_response( $category, $request ); + $response[] = $this->prepare_response_for_collection( $prepared_category ); + } + return rest_ensure_response( $response ); + } + + /** + * Prepare a raw block pattern category before it gets output in a REST API response. + * + * @param object $item Raw category as registered, before any changes. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $keys = array( 'name', 'label' ); + $data = array(); + foreach ( $keys as $key ) { + if ( rest_is_field_included( $key, $fields ) ) { + $data[ $key ] = $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 category schema, conforming to JSON Schema. + * + * @return array Item schema data. + */ + public function get_item_schema() { + $schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'block-pattern-category', + 'type' => 'object', + 'properties' => array( + 'name' => array( + 'description' => __( 'The category name.', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'embed' ), + ), + 'label' => array( + 'description' => __( 'The category label, in human readable format.', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'embed' ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } +} diff --git a/lib/compat/wordpress-6.0/class-wp-rest-block-patterns-controller.php b/lib/compat/wordpress-6.0/class-wp-rest-block-patterns-controller.php new file mode 100644 index 0000000000000..f42e733a1f824 --- /dev/null +++ b/lib/compat/wordpress-6.0/class-wp-rest-block-patterns-controller.php @@ -0,0 +1,188 @@ +namespace = '__experimental'; + $this->rest_base = 'block-patterns/patterns'; + } + + /** + * Registers the routes for the objects of the controller. + * + * @see register_rest_route() + */ + 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' ), + 'allow_batch' => array( 'v1' => true ), + ) + ); + } + + /** + * Checks whether a given request has permission to read block patterns. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_Error|bool True if the request has read access, WP_Error object otherwise. + */ + public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + 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.', 'gutenberg' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + /** + * Retrieves all block patterns. + * + * @param WP_REST_Request $request Full details about the request. + * + * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable + // Load block patterns from w.org. + _load_remote_block_patterns(); // Patterns with the `core` keyword. + _load_remote_featured_patterns(); // Patterns in the `featured` category. + gutenberg_register_remote_theme_patterns(); // Patterns requested by current theme. + + $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. + * + * @param object $item Raw pattern as registered, before any changes. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response + */ + public function prepare_item_for_response( $item, $request ) { + $fields = $this->get_fields_for_response( $request ); + $keys = array( + 'name', + 'title', + 'description', + 'viewportWidth', + 'blockTypes', + 'categories', + 'keywords', + 'content', + ); + $data = array(); + foreach ( $keys as $key ) { + if ( isset( $item[ $key ] ) && rest_is_field_included( $key, $fields ) ) { + $data[ $key ] = $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. + * + * @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', 'embed' ), + ), + 'title' => array( + 'description' => __( 'The pattern title, in human readable format.', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'embed' ), + ), + 'description' => array( + 'description' => __( 'The pattern detailed description.', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'embed' ), + ), + 'viewportWidth' => array( + 'description' => __( 'The pattern viewport width for inserter preview.', 'gutenberg' ), + 'type' => 'number', + 'readonly' => true, + 'context' => array( 'view', 'embed' ), + ), + 'blockTypes' => array( + 'description' => __( 'Block types that the pattern is intended to be used with.', 'gutenberg' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'embed' ), + ), + 'categories' => array( + 'description' => __( 'The pattern category slugs.', 'gutenberg' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'embed' ), + ), + 'keywords' => array( + 'description' => __( 'The pattern keywords.', 'gutenberg' ), + 'type' => 'array', + 'readonly' => true, + 'context' => array( 'view', 'embed' ), + ), + 'content' => array( + 'description' => __( 'The pattern content.', 'gutenberg' ), + 'type' => 'string', + 'readonly' => true, + 'context' => array( 'view', 'embed' ), + ), + ), + ); + + return $this->add_additional_fields_schema( $schema ); + } +} diff --git a/lib/compat/wordpress-6.0/edit-form-blocks.php b/lib/compat/wordpress-6.0/edit-form-blocks.php index f4d4bbb5e546e..68fc938e9e742 100644 --- a/lib/compat/wordpress-6.0/edit-form-blocks.php +++ b/lib/compat/wordpress-6.0/edit-form-blocks.php @@ -1,6 +1,6 @@ id ) ); + if ( $is_site_editor || $current_screen->is_block_editor() ) { + add_filter( 'should_load_remote_block_patterns', '__return_false' ); + } +} +add_action( 'current_screen', 'disable_load_remote_patterns' ); diff --git a/lib/compat/wordpress-6.0/rest-api.php b/lib/compat/wordpress-6.0/rest-api.php index da1f6977e73ca..13e64dcf0ff13 100644 --- a/lib/compat/wordpress-6.0/rest-api.php +++ b/lib/compat/wordpress-6.0/rest-api.php @@ -34,3 +34,21 @@ function gutenberg_register_edit_site_export_endpoint() { $editor_settings->register_routes(); } add_action( 'rest_api_init', 'gutenberg_register_edit_site_export_endpoint' ); + +/** + * Registers the block patterns REST API routes. + */ +function gutenberg_register_rest_block_patterns() { + $block_patterns = new WP_REST_Block_Patterns_Controller(); + $block_patterns->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_block_patterns' ); + +/** + * Registers the block pattern categories REST API routes. + */ +function gutenberg_register_rest_block_pattern_categories() { + $block_patterns = new WP_REST_Block_Pattern_Categories_Controller(); + $block_patterns->register_routes(); +} +add_action( 'rest_api_init', 'gutenberg_register_rest_block_pattern_categories' ); diff --git a/lib/full-site-editing/edit-site-page.php b/lib/full-site-editing/edit-site-page.php index 50ff0d0eb2b20..bd9bb17286a38 100644 --- a/lib/full-site-editing/edit-site-page.php +++ b/lib/full-site-editing/edit-site-page.php @@ -114,14 +114,12 @@ static function( $classes ) { } $custom_settings = array( - 'siteUrl' => site_url(), - 'postsPerPage' => get_option( 'posts_per_page' ), - 'styles' => gutenberg_get_editor_styles(), - 'defaultTemplateTypes' => $indexed_template_types, - 'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(), - '__unstableHomeTemplate' => gutenberg_resolve_home_template(), - '__experimentalBlockPatterns' => WP_Block_Patterns_Registry::get_instance()->get_all_registered(), - '__experimentalBlockPatternCategories' => WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered(), + 'siteUrl' => site_url(), + 'postsPerPage' => get_option( 'posts_per_page' ), + 'styles' => gutenberg_get_editor_styles(), + 'defaultTemplateTypes' => $indexed_template_types, + 'defaultTemplatePartAreas' => get_allowed_block_template_part_areas(), + '__unstableHomeTemplate' => gutenberg_resolve_home_template(), ); /** diff --git a/lib/load.php b/lib/load.php index b72686b1f4c42..0e2b3e2796ed7 100644 --- a/lib/load.php +++ b/lib/load.php @@ -97,6 +97,8 @@ function gutenberg_is_experiment_enabled( $name ) { require __DIR__ . '/compat/wordpress-6.0/class-gutenberg-rest-global-styles-controller.php'; require __DIR__ . '/compat/wordpress-6.0/class-gutenberg-rest-pattern-directory-controller.php'; require __DIR__ . '/compat/wordpress-6.0/class-gutenberg-rest-edit-site-export-controller.php'; +require __DIR__ . '/compat/wordpress-6.0/class-wp-rest-block-patterns-controller.php'; +require __DIR__ . '/compat/wordpress-6.0/class-wp-rest-block-pattern-categories-controller.php'; require __DIR__ . '/compat/wordpress-6.0/class-wp-theme-json-gutenberg.php'; require __DIR__ . '/compat/wordpress-6.0/rest-api.php'; require __DIR__ . '/compat/wordpress-6.0/block-patterns.php'; diff --git a/packages/core-data/CHANGELOG.md b/packages/core-data/CHANGELOG.md index 4624ba5c9a6de..4a7722232adb5 100644 --- a/packages/core-data/CHANGELOG.md +++ b/packages/core-data/CHANGELOG.md @@ -6,6 +6,7 @@ ### New Features – The saveEntityRecord, saveEditedEntityRecord, and deleteEntityRecord actions now accept an optional throwOnError option (defaults to false). When set to true, any exceptions occurring when the action was executing are re-thrown, causing dispatch().saveEntityRecord() to reject with an error. ([#39258](https://github.com/WordPress/gutenberg/pull/39258)) +- Added support for fetching block patterns and their categories, with the `getBlockPatterns` and `getBlockPatternCategories` selectors. ## 4.2.0 (2022-03-11) diff --git a/packages/core-data/README.md b/packages/core-data/README.md index aa26746a4df95..2dc7df4a04d71 100644 --- a/packages/core-data/README.md +++ b/packages/core-data/README.md @@ -341,6 +341,30 @@ _Returns_ - `?Array`: An array of autosaves for the post, or undefined if there is none. +### getBlockPatternCategories + +Retrieve the list of registered block pattern categories. + +_Parameters_ + +- _state_ `Object`: Data state. + +_Returns_ + +- `Array`: Block pattern category list. + +### getBlockPatterns + +Retrieve the list of registered block patterns. + +_Parameters_ + +- _state_ `Object`: Data state. + +_Returns_ + +- `Array`: Block pattern list. + ### getCurrentTheme Return the current theme. diff --git a/packages/core-data/src/reducer.js b/packages/core-data/src/reducer.js index 0c901c8ab7a2f..16b265bcf892e 100644 --- a/packages/core-data/src/reducer.js +++ b/packages/core-data/src/reducer.js @@ -592,6 +592,24 @@ export function autosaves( state = {}, action ) { return state; } +export function blockPatterns( state = [], action ) { + switch ( action.type ) { + case 'RECEIVE_BLOCK_PATTERNS': + return action.patterns; + } + + return state; +} + +export function blockPatternCategories( state = [], action ) { + switch ( action.type ) { + case 'RECEIVE_BLOCK_PATTERN_CATEGORIES': + return action.categories; + } + + return state; +} + export default combineReducers( { terms, users, @@ -606,4 +624,6 @@ export default combineReducers( { embedPreviews, userPermissions, autosaves, + blockPatterns, + blockPatternCategories, } ); diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index fda58b2fe9ea8..ad7b56befca3e 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -453,3 +453,17 @@ export const __experimentalGetCurrentThemeGlobalStylesVariations = () => async ( variations ); }; + +export const getBlockPatterns = () => async ( { dispatch } ) => { + const patterns = await apiFetch( { + path: '/__experimental/block-patterns/patterns', + } ); + dispatch( { type: 'RECEIVE_BLOCK_PATTERNS', patterns } ); +}; + +export const getBlockPatternCategories = () => async ( { dispatch } ) => { + const categories = await apiFetch( { + path: '/__experimental/block-patterns/categories', + } ); + dispatch( { type: 'RECEIVE_BLOCK_PATTERN_CATEGORIES', categories } ); +}; diff --git a/packages/core-data/src/selectors.js b/packages/core-data/src/selectors.js index 01dc3024152af..67cd6b1aa5087 100644 --- a/packages/core-data/src/selectors.js +++ b/packages/core-data/src/selectors.js @@ -962,3 +962,25 @@ export function __experimentalGetCurrentThemeGlobalStylesVariations( state ) { } return state.themeGlobalStyleVariations[ currentTheme.stylesheet ]; } + +/** + * Retrieve the list of registered block patterns. + * + * @param {Object} state Data state. + * + * @return {Array} Block pattern list. + */ +export function getBlockPatterns( state ) { + return state.blockPatterns; +} + +/** + * Retrieve the list of registered block pattern categories. + * + * @param {Object} state Data state. + * + * @return {Array} Block pattern category list. + */ +export function getBlockPatternCategories( state ) { + return state.blockPatternCategories; +} diff --git a/packages/edit-site/src/components/block-editor/index.js b/packages/edit-site/src/components/block-editor/index.js index de18566e66fff..d0e390165fd7d 100644 --- a/packages/edit-site/src/components/block-editor/index.js +++ b/packages/edit-site/src/components/block-editor/index.js @@ -8,7 +8,7 @@ import classnames from 'classnames'; */ import { useSelect, useDispatch } from '@wordpress/data'; import { useCallback, useRef } from '@wordpress/element'; -import { useEntityBlockEditor } from '@wordpress/core-data'; +import { useEntityBlockEditor, store as coreStore } from '@wordpress/core-data'; import { BlockList, BlockEditorProvider, @@ -42,17 +42,44 @@ const LAYOUT = { }; export default function BlockEditor( { setIsInserterOpen } ) { - const { settings, templateType, templateId, page } = useSelect( + const { settings } = useSelect( ( select ) => { - const { - getSettings, - getEditedPostType, - getEditedPostId, - getPage, - } = select( editSiteStore ); + let storedSettings = select( editSiteStore ).getSettings( + setIsInserterOpen + ); + + if ( ! storedSettings.__experimentalBlockPatterns ) { + storedSettings = { + ...storedSettings, + __experimentalBlockPatterns: select( + coreStore + ).getBlockPatterns(), + }; + } + + if ( ! storedSettings.__experimentalBlockPatternCategories ) { + storedSettings = { + ...storedSettings, + __experimentalBlockPatternCategories: select( + coreStore + ).getBlockPatternCategories(), + }; + } + + return { + settings: storedSettings, + }; + }, + [ setIsInserterOpen ] + ); + + const { templateType, templateId, page } = useSelect( + ( select ) => { + const { getEditedPostType, getEditedPostId, getPage } = select( + editSiteStore + ); return { - settings: getSettings( setIsInserterOpen ), templateType: getEditedPostType(), templateId: getEditedPostId(), page: getPage(), @@ -60,6 +87,7 @@ export default function BlockEditor( { setIsInserterOpen } ) { }, [ setIsInserterOpen ] ); + const [ blocks, onInput, onChange ] = useEntityBlockEditor( 'postType', templateType diff --git a/packages/editor/src/components/provider/use-block-editor-settings.js b/packages/editor/src/components/provider/use-block-editor-settings.js index 68849f85a6b53..65f0577f9fd16 100644 --- a/packages/editor/src/components/provider/use-block-editor-settings.js +++ b/packages/editor/src/components/provider/use-block-editor-settings.js @@ -61,6 +61,22 @@ function useBlockEditorSettings( settings, hasTemplate ) { }; }, [] ); + const { + __experimentalBlockPatterns: settingsBlockPatterns, + __experimentalBlockPatternCategories: settingsBlockPatternCategories, + } = settings; + + const { blockPatterns, blockPatternCategories } = useSelect( + ( select ) => ( { + blockPatterns: + settingsBlockPatterns ?? select( coreStore ).getBlockPatterns(), + blockPatternCategories: + settingsBlockPatternCategories ?? + select( coreStore ).getBlockPatternCategories(), + } ), + [ settingsBlockPatterns, settingsBlockPatternCategories ] + ); + const { undo } = useDispatch( editorStore ); const { saveEntityRecord } = useDispatch( coreStore ); @@ -85,8 +101,6 @@ function useBlockEditorSettings( settings, hasTemplate ) { () => ( { ...pick( settings, [ '__experimentalBlockDirectory', - '__experimentalBlockPatternCategories', - '__experimentalBlockPatterns', '__experimentalDiscussionSettings', '__experimentalFeatures', '__experimentalPreferredStyleVariations', @@ -128,6 +142,8 @@ function useBlockEditorSettings( settings, hasTemplate ) { ] ), mediaUpload: hasUploadPermissions ? mediaUpload : undefined, __experimentalReusableBlocks: reusableBlocks, + __experimentalBlockPatterns: blockPatterns, + __experimentalBlockPatternCategories: blockPatternCategories, __experimentalFetchLinkSuggestions: ( search, searchOptions ) => fetchLinkSuggestions( search, searchOptions, settings ), __experimentalFetchRichUrlData: fetchUrlData, @@ -143,6 +159,8 @@ function useBlockEditorSettings( settings, hasTemplate ) { settings, hasUploadPermissions, reusableBlocks, + blockPatterns, + blockPatternCategories, canUseUnfilteredHTML, undo, hasTemplate, diff --git a/phpunit/class-wp-rest-block-pattern-categories-controller-test.php b/phpunit/class-wp-rest-block-pattern-categories-controller-test.php new file mode 100644 index 0000000000000..8f22b0eefad5b --- /dev/null +++ b/phpunit/class-wp-rest-block-pattern-categories-controller-test.php @@ -0,0 +1,86 @@ +user->create( array( 'role' => 'administrator' ) ); + + // Setup an empty testing instance of `WP_Block_Pattern_Categories_Registry` and save the original. + $reflection = new ReflectionClass( 'WP_Block_Pattern_Categories_Registry' ); + $reflection->getProperty( 'instance' )->setAccessible( true ); + self::$orig_registry = $reflection->getStaticPropertyValue( 'instance' ); + $test_registry = new WP_Block_Pattern_Categories_Registry(); + $reflection->setStaticPropertyValue( 'instance', $test_registry ); + + // Register some categories in the test registry. + $test_registry->register( 'test', array( 'label' => 'Test' ) ); + $test_registry->register( 'query', array( 'label' => 'Query' ) ); + } + + public static function wpTearDownAfterClass() { + // Delete the test user. + self::delete_user( self::$admin_id ); + + // Restore the original registry instance. + $reflection = new ReflectionClass( 'WP_Block_Pattern_Categories_Registry' ); + $reflection->setStaticPropertyValue( 'instance', self::$orig_registry ); + } + + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( + '/__experimental/block-patterns/categories', + $routes, + 'The categories route does not exist' + ); + } + + public function test_get_items() { + wp_set_current_user( self::$admin_id ); + + $expected_names = array( 'test', 'query' ); + $expected_fields = array( 'name', 'label' ); + + $request = new WP_REST_Request( 'GET', '/__experimental/block-patterns/categories' ); + $request['_fields'] = 'name,label'; + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertCount( count( $expected_names ), $data ); + foreach ( $data as $idx => $item ) { + $this->assertEquals( $expected_names[ $idx ], $item['name'] ); + $this->assertEquals( $expected_fields, array_keys( $item ) ); + } + } + + /** + * Abstract methods that we must implement. + */ + public function test_context_param() {} + public function test_get_item() {} + public function test_create_item() {} + public function test_update_item() {} + public function test_delete_item() {} + public function test_prepare_item() {} + public function test_get_item_schema() {} +} diff --git a/phpunit/class-wp-rest-block-patterns-controller-test.php b/phpunit/class-wp-rest-block-patterns-controller-test.php new file mode 100644 index 0000000000000..6db9f1ca49962 --- /dev/null +++ b/phpunit/class-wp-rest-block-patterns-controller-test.php @@ -0,0 +1,102 @@ +user->create( array( 'role' => 'administrator' ) ); + + // Setup an empty testing instance of `WP_Block_Patterns_Registry` and save the original. + $reflection = new ReflectionClass( 'WP_Block_Patterns_Registry' ); + $reflection->getProperty( 'instance' )->setAccessible( true ); + self::$orig_registry = $reflection->getStaticPropertyValue( 'instance' ); + $test_registry = new WP_Block_Patterns_Registry(); + $reflection->setStaticPropertyValue( 'instance', $test_registry ); + + // Register some patterns in the test registry. + $test_registry->register( + 'test/one', + array( + 'title' => 'Pattern One', + 'categories' => array( 'test' ), + 'viewportWidth' => 1440, + 'content' => '

One

', + ) + ); + + $test_registry->register( + 'test/two', + array( + 'title' => 'Pattern Two', + 'categories' => array( 'test' ), + 'content' => '

Two

', + ) + ); + } + + public static function wpTearDownAfterClass() { + // Delete the test user. + self::delete_user( self::$admin_id ); + + // Restore the original registry instance. + $reflection = new ReflectionClass( 'WP_Block_Patterns_Registry' ); + $reflection->setStaticPropertyValue( 'instance', self::$orig_registry ); + } + + public function test_register_routes() { + $routes = rest_get_server()->get_routes(); + $this->assertArrayHasKey( + '/__experimental/block-patterns/patterns', + $routes, + 'The patterns route does not exist' + ); + } + + public function test_get_items() { + wp_set_current_user( self::$admin_id ); + + $expected_names = array( 'test/one', 'test/two' ); + $expected_fields = array( 'name', 'content' ); + + $request = new WP_REST_Request( 'GET', '/__experimental/block-patterns/patterns' ); + $request['_fields'] = 'name,content'; + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + + $this->assertCount( count( $expected_names ), $data ); + foreach ( $data as $idx => $item ) { + $this->assertEquals( $expected_names[ $idx ], $item['name'] ); + $this->assertEquals( $expected_fields, array_keys( $item ) ); + } + } + + /** + * Abstract methods that we must implement. + */ + public function test_context_param() {} + public function test_get_item() {} + public function test_create_item() {} + public function test_update_item() {} + public function test_delete_item() {} + public function test_prepare_item() {} + public function test_get_item_schema() {} +}