diff --git a/lib/compat/wordpress-6.6/rest-api.php b/lib/compat/wordpress-6.6/rest-api.php index 54eaaf28de82d..fcea1a0801b7d 100644 --- a/lib/compat/wordpress-6.6/rest-api.php +++ b/lib/compat/wordpress-6.6/rest-api.php @@ -77,6 +77,55 @@ function gutenberg_add_class_list_to_public_post_types() { } add_action( 'rest_api_init', 'gutenberg_add_class_list_to_public_post_types' ); +/** + * Adds the allow methods to the REST API response. + * + * @param array $post The response object data. + * + * @return string + */ +function gutenberg_add_allow_to_api_response( $post ) { + $request = new WP_REST_Request( 'OPTIONS', rest_get_route_for_post( $post['id'] ) ); + $response = rest_do_request( $request ); + $server = rest_get_server(); + $response = apply_filters( 'rest_post_dispatch', rest_ensure_response( $response ), $server, $request ); + + if ( is_wp_error( $response ) ) { + return array(); + } + + return $response->get_headers()["Allow"]; +} + +/** + * Adds the allow methods to public post types in the REST API. + */ +function gutenberg_add_allow_to_public_post_types() { + $post_types = get_post_types( + array( + 'public' => true, + 'show_in_rest' => true, + ), + 'names' + ); + + if ( ! empty( $post_types ) ) { + register_rest_field( + $post_types, + 'allow', + array( + 'get_callback' => 'gutenberg_add_allow_to_api_response', + 'schema' => array( + 'description' => __( 'Allowed methods for the post.', 'gutenberg' ), + 'type' => 'string', + ), + 'context' => array( 'edit' ), + ) + ); + } +} +add_action( 'rest_api_init', 'gutenberg_add_allow_to_public_post_types' ); + /** * Registers the Global Styles Revisions REST API routes. diff --git a/packages/core-data/src/actions.js b/packages/core-data/src/actions.js index 53ab935d868a3..b9ec01d3ddffc 100644 --- a/packages/core-data/src/actions.js +++ b/packages/core-data/src/actions.js @@ -83,39 +83,70 @@ export function addEntities( entities ) { * @param {?Object} meta Meta information about pagination. * @return {Object} Action object. */ -export function receiveEntityRecords( - kind, - name, - records, - query, - invalidateCache = false, - edits, - meta -) { - // Auto drafts should not have titles, but some plugins rely on them so we can't filter this - // on the server. - if ( kind === 'postType' ) { - records = ( Array.isArray( records ) ? records : [ records ] ).map( - ( record ) => - record.status === 'auto-draft' - ? { ...record, title: '' } - : record - ); - } - let action; - if ( query ) { - action = receiveQueriedItems( records, query, edits, meta ); - } else { - action = receiveItems( records, edits, meta ); - } +export const receiveEntityRecords = + ( kind, name, records, query, invalidateCache = false, edits, meta ) => + ( { dispatch, select } ) => { + // Auto drafts should not have titles, but some plugins rely on them so we can't filter this + // on the server. + if ( kind === 'postType' ) { + records = ( Array.isArray( records ) ? records : [ records ] ).map( + ( record ) => + record.status === 'auto-draft' + ? { ...record, title: '' } + : record + ); - return { - ...action, - kind, - name, - invalidateCache, + const postTypeObject = select.getPostType( name ); + const resource = postTypeObject?.rest_base || ''; + + for ( const record of records ) { + const allowedMethods = record.allow; + + if ( allowedMethods && resource ) { + const resourcePath = `${ resource }/${ record.id }`; + const retrievedActions = [ + 'create', + 'read', + 'update', + 'delete', + ]; + const permissions = {}; + const methods = { + create: 'POST', + read: 'GET', + update: 'PUT', + delete: 'DELETE', + }; + for ( const [ actionName, methodName ] of Object.entries( + methods + ) ) { + permissions[ actionName ] = + allowedMethods.includes( methodName ); + } + + for ( const action of retrievedActions ) { + dispatch.receiveUserPermission( + `${ action }/${ resourcePath }`, + permissions[ action ] + ); + } + } + } + } + let action; + if ( query ) { + action = receiveQueriedItems( records, query, edits, meta ); + } else { + action = receiveItems( records, edits, meta ); + } + + return dispatch( { + ...action, + kind, + name, + invalidateCache, + } ); }; -} /** * Returns an action object used in signalling that the current theme has been received. diff --git a/packages/core-data/src/resolvers.js b/packages/core-data/src/resolvers.js index 4ab446e6ec323..ac73a355ea8f3 100644 --- a/packages/core-data/src/resolvers.js +++ b/packages/core-data/src/resolvers.js @@ -360,6 +360,14 @@ export const getEmbedPreview = export const canUser = ( requestedAction, resource, id ) => async ( { dispatch, registry } ) => { + const value = registry + .select( STORE_NAME ) + .canUser( requestedAction, resource, id ); + + if ( value !== undefined ) { + return; + } + const { hasStartedResolution } = registry.select( STORE_NAME ); const resourcePath = id ? `${ resource }/${ id }` : resource;