-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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 preview template in post editor for non administrators #58301
Changes from all commits
339db9c
1da7549
52fad82
ae26dc7
4f147fe
df29500
98e7bee
06c0ae7
b9bbe96
a6871eb
e039f82
dd22cab
a38c4f9
74d934b
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,66 @@ | ||
<?php | ||
/** | ||
* REST API: Gutenberg_REST_Templates_Controller_6_6 class | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
/** | ||
* Gutenberg_REST_Templates_Controller_6_6 class | ||
* | ||
* Templates and template parts currently only allow access to administrators with the | ||
* `edit_theme_options` capability. In order to allow other roles to also view the templates, | ||
* we need to override the permissions check for the REST API endpoints. | ||
*/ | ||
class Gutenberg_REST_Templates_Controller_6_6 extends Gutenberg_REST_Templates_Controller_6_4 { | ||
|
||
/** | ||
* Checks if a given request has access to read templates. | ||
* | ||
* @since 6.6 | ||
* | ||
* @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 ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable | ||
/* | ||
* Allow access to anyone who can edit posts. | ||
*/ | ||
if ( ! current_user_can( 'edit_posts' ) ) { | ||
return new WP_Error( | ||
'rest_cannot_manage_templates', | ||
__( 'Sorry, you are not allowed to access the templates on this site.', 'default' ), | ||
array( | ||
'status' => rest_authorization_required_code(), | ||
) | ||
); | ||
} | ||
|
||
return true; | ||
} | ||
|
||
/** | ||
* Checks if a given request has access to read templates. | ||
* | ||
* @since 6.6 | ||
* | ||
* @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_item_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable | ||
/* | ||
* Allow access to anyone who can edit posts. | ||
*/ | ||
if ( ! current_user_can( 'edit_posts' ) ) { | ||
return new WP_Error( | ||
'rest_cannot_manage_templates', | ||
__( 'Sorry, you are not allowed to access the templates on this site.', 'default' ), | ||
array( | ||
'status' => rest_authorization_required_code(), | ||
) | ||
); | ||
} | ||
|
||
return true; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
/** | ||
* PHP and WordPress configuration compatibility functions for the Gutenberg | ||
* editor plugin changes related to REST API. | ||
* | ||
* @package gutenberg | ||
*/ | ||
|
||
if ( ! defined( 'ABSPATH' ) ) { | ||
die( 'Silence is golden.' ); | ||
} | ||
|
||
if ( ! function_exists( 'wp_api_template_access_controller' ) ) { | ||
/** | ||
* Hook in to the template and template part post types and modify the | ||
* access control for the rest endpoint to allow lower user roles to access | ||
* the templates and template parts. | ||
* | ||
* @param array $args Current registered post type args. | ||
* @param string $post_type Name of post type. | ||
* | ||
* @return array | ||
*/ | ||
function wp_api_template_access_controller( $args, $post_type ) { | ||
if ( 'wp_template' === $post_type || 'wp_template_part' === $post_type ) { | ||
$args['rest_controller_class'] = 'Gutenberg_REST_Templates_Controller_6_6'; | ||
} | ||
return $args; | ||
} | ||
} | ||
add_filter( 'register_post_type_args', 'wp_api_template_access_controller', 10, 2 ); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,8 @@ import { | |
useBlockProps, | ||
Warning, | ||
store as blockEditorStore, | ||
useInnerBlocksProps, | ||
useSettings, | ||
RecursionProvider, | ||
useHasRecursion, | ||
InspectorControls, | ||
|
@@ -15,7 +17,7 @@ import { | |
import { PanelBody, Spinner, Modal, MenuItem } from '@wordpress/components'; | ||
import { useAsyncList } from '@wordpress/compose'; | ||
import { __, sprintf } from '@wordpress/i18n'; | ||
import { store as coreStore } from '@wordpress/core-data'; | ||
import { store as coreStore, useEntityBlockEditor } from '@wordpress/core-data'; | ||
import { useState } from '@wordpress/element'; | ||
import { store as noticesStore } from '@wordpress/notices'; | ||
|
||
|
@@ -88,12 +90,53 @@ function TemplatesList( { availableTemplates, onSelect } ) { | |
); | ||
} | ||
|
||
function NonEditableTemplatePartPreview( { | ||
postId: id, | ||
layout, | ||
tagName: TagName, | ||
blockProps, | ||
} ) { | ||
const themeSupportsLayout = useSelect( ( select ) => { | ||
const { getSettings } = select( blockEditorStore ); | ||
return getSettings()?.supportsLayout; | ||
}, [] ); | ||
const [ defaultLayout ] = useSettings( 'layout' ); | ||
const usedLayout = layout?.inherit ? defaultLayout || {} : layout; | ||
|
||
const [ blocks ] = useEntityBlockEditor( 'postType', 'wp_template_part', { | ||
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't we just retrieve the "blocks" property from the entity without using this hook. |
||
id, | ||
context: 'view', | ||
} ); | ||
|
||
const innerBlocksProps = useInnerBlocksProps( blockProps, { | ||
value: blocks, | ||
onChange: () => {}, | ||
onInput: () => {}, | ||
renderAppender: undefined, | ||
layout: themeSupportsLayout ? usedLayout : undefined, | ||
} ); | ||
|
||
return <TagName { ...innerBlocksProps } />; | ||
} | ||
|
||
export default function TemplatePartEdit( { | ||
attributes, | ||
setAttributes, | ||
clientId, | ||
} ) { | ||
const { createSuccessNotice } = useDispatch( noticesStore ); | ||
const { canEditTemplatePart, canViewTemplatePart } = useSelect( | ||
( select ) => ( { | ||
canEditTemplatePart: | ||
select( coreStore ).canUser( 'create', 'template-parts' ) ?? | ||
false, | ||
canViewTemplatePart: | ||
select( coreStore ).canUser( 'read', 'template-parts' ) ?? | ||
false, | ||
} ), | ||
[] | ||
); | ||
|
||
const currentTheme = useSelect( | ||
( select ) => select( coreStore ).getCurrentTheme()?.stylesheet, | ||
[] | ||
|
@@ -161,6 +204,23 @@ export default function TemplatePartEdit( { | |
setAttributes | ||
); | ||
|
||
if ( ! canEditTemplatePart && canViewTemplatePart ) { | ||
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. What happens if you can't view or edit, is there even a world where you can't view a template part? Aren't these public? 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. Curretly Template parts have the same rest restrictions as templates. They are both locked down so only admins can view them. With my update template parts and templates can now be viewed by any user that has the capability to |
||
return ( | ||
<RecursionProvider uniqueId={ templatePartId }> | ||
<NonEditableTemplatePartPreview | ||
attributes={ attributes } | ||
setAttributes={ setAttributes } | ||
clientId={ clientId } | ||
tagName={ TagName } | ||
blockProps={ blockProps } | ||
postId={ templatePartId } | ||
hasInnerBlocks={ hasInnerBlocks } | ||
layout={ layout } | ||
/> | ||
</RecursionProvider> | ||
); | ||
} | ||
|
||
// We don't want to render a missing state if we have any inner blocks. | ||
// A new template part is automatically created if we have any inner blocks but no entity. | ||
if ( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -145,14 +145,19 @@ const parsedBlocksCache = new WeakMap(); | |
* `BlockEditorProvider` and are intended to be used with it, | ||
* or similar components or hooks. | ||
* | ||
* @param {string} kind The entity kind. | ||
* @param {string} name The entity name. | ||
* @param {string} kind The entity kind. | ||
* @param {string} name The entity name. | ||
* @param {Object} options | ||
* @param {string} [options.id] An entity ID to use instead of the context-provided one. | ||
* @param {string} [options.id] An entity ID to use instead of the context-provided one. | ||
* @param {string} [options.context] The context param to be passed to the REST API. | ||
* | ||
* @return {[WPBlock[], Function, Function]} The block array and setters. | ||
*/ | ||
export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { | ||
export function useEntityBlockEditor( | ||
kind, | ||
name, | ||
{ id: _id, context = 'edit' } = {} | ||
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 don't like the context prop too much here. It's also weird to pass 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 actually think this hook is probably not for "previewing" but should only be used for "editing". |
||
) { | ||
const providerId = useEntityId( kind, name ); | ||
const id = _id ?? providerId; | ||
const { getEntityRecord, getEntityRecordEdits } = useSelect( STORE_NAME ); | ||
|
@@ -162,7 +167,9 @@ export function useEntityBlockEditor( kind, name, { id: _id } = {} ) { | |
return {}; | ||
} | ||
const { getEditedEntityRecord } = select( STORE_NAME ); | ||
const editedRecord = getEditedEntityRecord( kind, name, id ); | ||
const editedRecord = getEditedEntityRecord( kind, name, id, { | ||
context, | ||
} ); | ||
return { | ||
editedBlocks: editedRecord.blocks, | ||
content: editedRecord.content, | ||
|
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.
This preview component does not handle the case where a template part is missing