diff --git a/lib/class-wp-rest-menus-controller.php b/lib/class-wp-rest-menus-controller.php index 84fc22952aa28..4ae382dde86a9 100644 --- a/lib/class-wp-rest-menus-controller.php +++ b/lib/class-wp-rest-menus-controller.php @@ -12,6 +12,7 @@ * @see WP_REST_Controller */ class WP_REST_Menus_Controller extends WP_REST_Terms_Controller { + /** * Constructor. * @@ -22,6 +23,77 @@ public function __construct( $taxonomy ) { $this->namespace = '__experimental'; } + /** + * Overrides the route registration to support "allow_batch". + * + * @since 11.5.0 + * + * @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' ), + 'args' => $this->get_collection_params(), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\d]+)', + array( + 'args' => array( + 'id' => array( + 'description' => __( 'Unique identifier for the term.', 'default' ), + 'type' => 'integer', + ), + ), + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + 'args' => array( + 'force' => array( + 'type' => 'boolean', + 'default' => false, + 'description' => __( 'Required to be true, as terms do not support trashing.', 'default' ), + ), + ), + ), + 'allow_batch' => array( 'v1' => true ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** * Checks if a request has access to read terms in the specified taxonomy. * diff --git a/packages/edit-navigation/src/components/sidebar/manage-locations.js b/packages/edit-navigation/src/components/sidebar/manage-locations.js index 97b3b3da322cf..6be9c290c9e8a 100644 --- a/packages/edit-navigation/src/components/sidebar/manage-locations.js +++ b/packages/edit-navigation/src/components/sidebar/manage-locations.js @@ -12,6 +12,9 @@ import { SelectControl, } from '@wordpress/components'; import { decodeEntities } from '@wordpress/html-entities'; +import apiFetch from '@wordpress/api-fetch'; +import { useDispatch } from '@wordpress/data'; +import { store as noticesStore } from '@wordpress/notices'; /** * Internal dependencies @@ -31,6 +34,62 @@ export default function ManageLocations( { const [ isModalOpen, setIsModalOpen ] = useState( false ); const openModal = () => setIsModalOpen( true ); const closeModal = () => setIsModalOpen( false ); + const { createSuccessNotice, createErrorNotice } = useDispatch( + noticesStore + ); + + const validateBatchResponse = ( batchResponse ) => { + if ( batchResponse.failed ) { + return false; + } + + const errorResponses = batchResponse.responses.filter( ( response ) => { + return 200 > response.status || 300 <= response.status; + } ); + + return 1 > errorResponses.length; + }; + + const handleUpdateMenuLocations = async () => { + const method = 'POST'; + const batchRequests = menus.map( ( { id } ) => { + const locations = menuLocations + .filter( ( menuLocation ) => menuLocation.menu === id ) + .map( ( menuLocation ) => menuLocation.name ); + + return { + path: `/__experimental/menus/${ id }`, + body: { + locations, + }, + method, + }; + } ); + + const batchResponse = await apiFetch( { + path: 'batch/v1', + data: { + validation: 'require-all-validate', + requests: batchRequests, + }, + method, + } ); + + const isSuccess = validateBatchResponse( batchResponse ); + + if ( isSuccess ) { + createSuccessNotice( __( 'Menu locations have been updated.' ), { + type: 'snackbar', + } ); + closeModal(); + return; + } + + createErrorNotice( + __( 'An error occurred while trying to update menu locations.' ), + { type: 'snackbar' } + ); + }; if ( ! menuLocations || ! menus?.length ) { return ; @@ -156,6 +215,13 @@ export default function ManageLocations( { { themeLocationCountTextModal } { menuLocationCard } + ) } diff --git a/packages/edit-navigation/src/components/sidebar/style.scss b/packages/edit-navigation/src/components/sidebar/style.scss index 62f49d9c832ce..4cfe5e3c70bf6 100644 --- a/packages/edit-navigation/src/components/sidebar/style.scss +++ b/packages/edit-navigation/src/components/sidebar/style.scss @@ -128,3 +128,7 @@ .edit-navigation-manage-locations__theme-location-text-modal { margin-bottom: $grid-unit-30; } + +.edit-navigation-manage-locations__save-button { + float: right; +} diff --git a/phpunit/class-rest-nav-menus-controller-test.php b/phpunit/class-rest-nav-menus-controller-test.php index 20a8933ff4ca6..6e71fbb1f9392 100644 --- a/phpunit/class-rest-nav-menus-controller-test.php +++ b/phpunit/class-rest-nav-menus-controller-test.php @@ -524,6 +524,16 @@ public function test_get_item_wrong_permission() { $this->assertErrorResponse( 'rest_cannot_view', $response, 403 ); } + public function test_it_allows_batch_requests_when_updating_menus() { + $rest_server = rest_get_server(); + // This call is needed to initialize route_options. + $rest_server->get_routes(); + $route_options = $rest_server->get_route_options( '/__experimental/menus/(?P[\d]+)' ); + + $this->assertArrayHasKey( 'allow_batch', $route_options ); + $this->assertSame( array( 'v1' => true ), $route_options['allow_batch'] ); + } + /** * @param WP_REST_Response $response Response Class. */