Skip to content

Commit 7623fc1

Browse files
committed
REST API: Introduce filter for controlling menu read access.
The menu, menu item, and menu location endpoints were added to the REST API in [52079]. In that commit, menu data was treated as private and restricted to logged-in users with the edit_theme_options capability. However, in many cases, this data can be considered public. Previously, there was no simple way for developers to allow this data to be exposed via the REST API. This commit introduces the rest_menu_read_access filter, enabling developers to control read access to menus, menu items, and menu locations in the REST API. The same filter is applied across all three REST API classes, simplifying the process of opting into exposing this data. Each instance of the filter provides the current request and the relevant class instance as context, allowing developers to selectively or globally enable access to the data. Props spacedmonkey, antonvlasenko, kadamwhite, julianmar, masteradhoc. Fixes #54304. git-svn-id: https://develop.svn.wordpress.org/trunk@59718 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 05e923c commit 7623fc1

File tree

6 files changed

+152
-18
lines changed

6 files changed

+152
-18
lines changed

src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,19 @@ public function get_item_permissions_check( $request ) {
8080
* @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
8181
*/
8282
protected function check_has_read_only_access( $request ) {
83+
/**
84+
* Filters whether the current user has read access to menu items via the REST API.
85+
*
86+
* @since 6.8.0
87+
* @param $read_only_access bool Whether the current user has read access to menu items via the REST API.
88+
* @param $request WP_REST_Request Full details about the request.
89+
* @param $this WP_REST_Controller The current instance of the controller.
90+
*/
91+
$read_only_access = apply_filters( 'rest_menu_read_access', false, $request, $this );
92+
if ( $read_only_access ) {
93+
return true;
94+
}
95+
8396
if ( current_user_can( 'edit_theme_options' ) ) {
8497
return true;
8598
}

src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,7 @@ public function register_routes() {
8080
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
8181
*/
8282
public function get_items_permissions_check( $request ) {
83-
if ( ! current_user_can( 'edit_theme_options' ) ) {
84-
return new WP_Error(
85-
'rest_cannot_view',
86-
__( 'Sorry, you are not allowed to view menu locations.' ),
87-
array( 'status' => rest_authorization_required_code() )
88-
);
89-
}
90-
91-
return true;
83+
return $this->check_has_read_only_access( $request );
9284
}
9385

9486
/**
@@ -123,15 +115,7 @@ public function get_items( $request ) {
123115
* @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
124116
*/
125117
public function get_item_permissions_check( $request ) {
126-
if ( ! current_user_can( 'edit_theme_options' ) ) {
127-
return new WP_Error(
128-
'rest_cannot_view',
129-
__( 'Sorry, you are not allowed to view menu locations.' ),
130-
array( 'status' => rest_authorization_required_code() )
131-
);
132-
}
133-
134-
return true;
118+
return $this->check_has_read_only_access( $request );
135119
}
136120

137121
/**
@@ -157,6 +141,32 @@ public function get_item( $request ) {
157141
return rest_ensure_response( $data );
158142
}
159143

144+
/**
145+
* Checks whether the current user has read permission for the endpoint.
146+
*
147+
* @since 6.8.0
148+
*
149+
* @param WP_REST_Request $request Full details about the request.
150+
* @return true|WP_Error True if the current user has permission, WP_Error object otherwise.
151+
*/
152+
protected function check_has_read_only_access( $request ) {
153+
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */
154+
$read_only_access = apply_filters( 'rest_menu_read_access', false, $request, $this );
155+
if ( $read_only_access ) {
156+
return true;
157+
}
158+
159+
if ( ! current_user_can( 'edit_theme_options' ) ) {
160+
return new WP_Error(
161+
'rest_cannot_view',
162+
__( 'Sorry, you are not allowed to view menu locations.' ),
163+
array( 'status' => rest_authorization_required_code() )
164+
);
165+
}
166+
167+
return true;
168+
}
169+
160170
/**
161171
* Prepares a menu location object for serialization.
162172
*

src/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,12 @@ protected function get_term( $id ) {
8484
* @return true|WP_Error True if the current user has permission, WP_Error object otherwise.
8585
*/
8686
protected function check_has_read_only_access( $request ) {
87+
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */
88+
$read_only_access = apply_filters( 'rest_menu_read_access', false, $request, $this );
89+
if ( $read_only_access ) {
90+
return true;
91+
}
92+
8793
if ( current_user_can( 'edit_theme_options' ) ) {
8894
return true;
8995
}

tests/phpunit/tests/rest-api/wpRestMenuItemsController.php

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,30 @@ public function test_get_item() {
183183
$this->check_get_menu_item_response( $response, 'view' );
184184
}
185185

186+
/**
187+
* @ticket 54304
188+
* @covers ::get_items
189+
*/
190+
public function test_get_items_filter() {
191+
add_filter( 'rest_menu_read_access', '__return_true' );
192+
$request = new WP_REST_Request( 'GET', '/wp/v2/menu-items' );
193+
$response = rest_get_server()->dispatch( $request );
194+
195+
$this->check_get_menu_items_response( $response );
196+
}
197+
198+
/**
199+
* @ticket 54304
200+
* @covers ::get_item
201+
*/
202+
public function test_get_item_filter() {
203+
add_filter( 'rest_menu_read_access', '__return_true' );
204+
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/menu-items/%d', $this->menu_item_id ) );
205+
$response = rest_get_server()->dispatch( $request );
206+
207+
$this->check_get_menu_item_response( $response, 'view' );
208+
}
209+
186210
/**
187211
* @ticket 40878
188212
* @covers ::get_item

tests/phpunit/tests/rest-api/wpRestMenuLocationsController.php

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,45 @@ public function test_get_item() {
120120
$this->assertSame( $menu, $data['name'] );
121121
}
122122

123+
/**
124+
* @ticket 54304
125+
* @covers ::get_items
126+
*/
127+
public function test_get_items_filter() {
128+
$menus = array( 'primary', 'secondary' );
129+
$this->register_nav_menu_locations( array( 'primary', 'secondary' ) );
130+
add_filter( 'rest_menu_read_access', '__return_true' );
131+
132+
$request = new WP_REST_Request( 'GET', '/wp/v2/menu-locations' );
133+
$response = rest_get_server()->dispatch( $request );
134+
$data = $response->get_data();
135+
$data = array_values( $data );
136+
$this->assertCount( 2, $data, 'Number of menu location are not 2' );
137+
138+
$names = wp_list_pluck( $data, 'name' );
139+
$descriptions = wp_list_pluck( $data, 'description' );
140+
$this->assertSame( $menus, $names );
141+
$menu_descriptions = array_map( 'ucfirst', $names );
142+
143+
$this->assertSame( $menu_descriptions, $descriptions, 'Menu descriptions do not match' );
144+
}
145+
146+
/**
147+
* @ticket 54304
148+
* @covers ::get_item
149+
*/
150+
public function test_get_item_filter() {
151+
$menu = 'primary';
152+
$this->register_nav_menu_locations( array( $menu ) );
153+
154+
add_filter( 'rest_menu_read_access', '__return_true' );
155+
$request = new WP_REST_Request( 'GET', '/wp/v2/menu-locations/' . $menu );
156+
$response = rest_get_server()->dispatch( $request );
157+
$data = $response->get_data();
158+
159+
$this->assertSame( $menu, $data['name'] );
160+
}
161+
123162
/**
124163
* @ticket 40878
125164
* @covers ::get_item

tests/phpunit/tests/rest-api/wpRestMenusController.php

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,48 @@ public function test_get_item() {
208208
$this->check_get_taxonomy_term_response( $response, $nav_menu_id );
209209
}
210210

211+
212+
/**
213+
* @ticket 54304
214+
* @covers ::get_items
215+
*/
216+
public function test_get_items_filter() {
217+
add_filter( 'rest_menu_read_access', '__return_true' );
218+
wp_update_nav_menu_object(
219+
0,
220+
array(
221+
'description' => 'Test get',
222+
'menu-name' => 'test Name get',
223+
)
224+
);
225+
$request = new WP_REST_Request( 'GET', '/wp/v2/menus' );
226+
$request->set_param( 'per_page', self::$per_page );
227+
$response = rest_get_server()->dispatch( $request );
228+
$this->check_get_taxonomy_terms_response( $response );
229+
}
230+
231+
/**
232+
* @ticket 54304
233+
* @covers ::get_item
234+
*/
235+
public function test_get_item_filter() {
236+
add_filter( 'rest_menu_read_access', '__return_true' );
237+
$nav_menu_id = wp_update_nav_menu_object(
238+
0,
239+
array(
240+
'description' => 'Test menu',
241+
'menu-name' => 'test Name',
242+
)
243+
);
244+
245+
$this->register_nav_menu_locations( array( 'primary' ) );
246+
set_theme_mod( 'nav_menu_locations', array( 'primary' => $nav_menu_id ) );
247+
248+
$request = new WP_REST_Request( 'GET', '/wp/v2/menus/' . $nav_menu_id );
249+
$response = rest_get_server()->dispatch( $request );
250+
$this->check_get_taxonomy_term_response( $response, $nav_menu_id );
251+
}
252+
211253
/**
212254
* @ticket 40878
213255
* @covers ::create_item

0 commit comments

Comments
 (0)