-
Notifications
You must be signed in to change notification settings - Fork 4.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add: Ability to use creation patterns for other post types besides page #41791
Changes from 3 commits
17678a4
dfe94b9
291edce
6184a66
c0ef524
e274fc2
51e9ded
c8d54c1
a7f2181
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,224 @@ | ||
<?php | ||
/** | ||
* REST API: Gutenberg_REST_Block_Patterns_Controller class | ||
* | ||
* @package Gutenberg | ||
* @subpackage REST_API | ||
*/ | ||
|
||
/** | ||
* Core class used to access block patterns via the REST API. | ||
* | ||
* @since 6.0.0 | ||
* | ||
* @see WP_REST_Controller | ||
*/ | ||
class Gutenberg_REST_Block_Patterns_Controller extends WP_REST_Controller { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to align with the existing controller in core. This means, I believe we need to move forwards this issue: #40902 and see what we'll need to update. Perhaps something like Noting that I didn't check the code here - just some thoughts.. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi @ntsekouras In this PR I'm already aligining the endpoint with core (by accident) private $remote_patterns_loaded; is part of this PR. Basically, I copied this class from core, changed its name, and did the changes I needed. I'm using WordPress filters to overwrite the core class with this new Gutenberg one Following this approach we don't need any extends etc. We are free to apply the changes we need. When backporting the class to core we just copy this file back to the core, change its name, and that's it. I left a comment on @anton-vlasenko PR #40902 (review). |
||
|
||
/** | ||
* Defines whether remote patterns should be loaded. | ||
* | ||
* @since 6.0.0 | ||
* @var bool | ||
*/ | ||
private $remote_patterns_loaded; | ||
|
||
/** | ||
* Constructs the controller. | ||
* | ||
* @since 6.0.0 | ||
*/ | ||
public function __construct() { | ||
$this->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', | ||
'postTypes' => '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' ), | ||
), | ||
'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' ), | ||
), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure what the best way to track this is, but we will also need to update the documentation of Core's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added a PR against core here WordPress/wordpress-develop#2858 so we don't miss the required change in core. |
||
'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 ); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -480,6 +480,8 @@ export const getBlockPatterns = | |
switch ( key ) { | ||
case 'block_types': | ||
return 'blockTypes'; | ||
case 'post_types': | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there any documentation to update around this? cc @juanmaguitar There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we generalise the casing transformation? // hello_world -> helloWorld
function snakeToCamel( string ) {
return string.replace( /_([a-z])/, ( _, letter ) => letter.toUpperCase() );
}
mapKeys( pattern, ( _, phpKey ) => snakeToCamel( phpKey ) ); If not (why not?), then it would be nice to eventually be able to source the mapping from Core rather than keep a copy here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I applied the feedback, using a function from lodash that is already used in other parts of the codebase. |
||
return 'postTypes'; | ||
case 'viewport_width': | ||
return 'viewportWidth'; | ||
default: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,30 @@ import { store as editorStore } from '@wordpress/editor'; | |
*/ | ||
import { store as editPostStore } from '../../store'; | ||
|
||
function PatternSelection( { onChoosePattern } ) { | ||
const { blockPatterns } = useSelect( ( select ) => { | ||
function useStartPatterns() { | ||
const { blockPatterns, postType } = useSelect( ( select ) => { | ||
const { __experimentalGetPatternsByBlockTypes } = | ||
select( blockEditorStore ); | ||
const { getCurrentPostType } = select( editorStore ); | ||
return { | ||
blockPatterns: | ||
__experimentalGetPatternsByBlockTypes( 'core/post-content' ), | ||
postType: getCurrentPostType(), | ||
}; | ||
}, [] ); | ||
return useMemo( () => { | ||
return blockPatterns.filter( ( pattern ) => { | ||
return ( | ||
( postType === 'page' && ! pattern.postTypes ) || | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we include some comments here? The logic and its implications are worth stating since it's not obvious on its own. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I included some inline comments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry, I'm a bit late. But what do you think if we make filtering by post type part of REST API request? There's no need to send a full response if we filter out results on the client-side. P.S. We're already doing something similar for the templates - https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/rest-api/endpoints/class-wp-rest-templates-controller.php#L747-L750 |
||
( Array.isArray( pattern.postTypes ) && | ||
pattern.postTypes.includes( postType ) ) | ||
); | ||
} ); | ||
}, [ postType, blockPatterns ] ); | ||
} | ||
|
||
function PatternSelection( { onChoosePattern } ) { | ||
const blockPatterns = useStartPatterns(); | ||
const shownBlockPatterns = useAsyncList( blockPatterns ); | ||
const { resetEditorBlocks } = useDispatch( editorStore ); | ||
return ( | ||
|
@@ -50,31 +65,28 @@ 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 { 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( () => { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note to reviewers: this file is an exact copy of core with the following changes:
There are no other changes to the core file.