Ticket #40878: 40878.4.diff
File 40878.4.diff, 35.2 KB (added by , 6 years ago) |
---|
-
src/wp-includes/post.php
From 52818fda83f9dd47a2e1a5e7aa349537369317c6 Mon Sep 17 00:00:00 2001 From: Micah Wood <micah@wpscholar.com> Date: Mon, 25 Mar 2019 17:46:55 -0400 Subject: [PATCH] Implemented REST API endpoints for menus, menu items, menu locations, and menu settings. --- src/wp-includes/post.php | 17 +- src/wp-includes/rest-api.php | 18 + .../class-wp-rest-menu-items-controller.php | 292 ++++++++++++++ ...lass-wp-rest-menu-locations-controller.php | 369 ++++++++++++++++++ ...class-wp-rest-menu-settings-controller.php | 289 ++++++++++++++ .../class-wp-rest-menus-controller.php | 49 +++ src/wp-includes/taxonomy.php | 19 +- src/wp-settings.php | 4 + 8 files changed, 1042 insertions(+), 15 deletions(-) create mode 100644 src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php create mode 100644 src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php create mode 100644 src/wp-includes/rest-api/endpoints/class-wp-rest-menu-settings-controller.php create mode 100644 src/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php diff --git a/src/wp-includes/post.php b/src/wp-includes/post.php index eb69ac727b..7bd770b55a 100644
a b function create_initial_post_types() { 123 123 register_post_type( 124 124 'nav_menu_item', 125 125 array( 126 'labels' => array(126 'labels' => array( 127 127 'name' => __( 'Navigation Menu Items' ), 128 128 'singular_name' => __( 'Navigation Menu Item' ), 129 129 ), 130 'public' => false, 131 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ 132 'hierarchical' => false, 133 'rewrite' => false, 134 'delete_with_user' => false, 135 'query_var' => false, 130 'public' => false, 131 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */ 132 'hierarchical' => false, 133 'rewrite' => false, 134 'delete_with_user' => false, 135 'query_var' => false, 136 'show_in_rest' => true, 137 'rest_base' => 'menu-items', 138 'rest_controller_class' => 'WP_REST_Posts_Controller' 136 139 ) 137 140 ); 138 141 -
src/wp-includes/rest-api.php
diff --git a/src/wp-includes/rest-api.php b/src/wp-includes/rest-api.php index 1c73b97824..c4f83e87b6 100644
a b function create_initial_rest_routes() { 276 276 $controller = new WP_REST_Themes_Controller; 277 277 $controller->register_routes(); 278 278 279 // Menu Items. 280 $controller = new WP_REST_Menu_Items_Controller( 'nav_menu_item' ); 281 $controller->register_routes(); 282 283 // Menus. 284 $controller = new WP_REST_Menus_Controller( 'nav_menu' ); 285 $controller->register_routes(); 286 287 // Menu Locations. 288 $controller = new WP_REST_Menu_Locations_Controller(); 289 $controller->register_routes(); 290 291 // Menu Settings. 292 $controller = new WP_REST_Menu_Settings_Controller(); 293 $controller->register_routes(); 294 295 296 279 297 } 280 298 281 299 /** -
new file src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php new file mode 100644 index 0000000000..699da2ab73
- + 1 <?php 2 /** 3 * REST API: WP_REST_Menu_Items_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 5.x 8 */ 9 10 /** 11 * Core controller used to access menu items via the REST API. 12 * 13 * @since 5.x 14 * 15 * @see WP_REST_Posts_Controller 16 */ 17 class WP_REST_Menu_Items_Controller extends WP_REST_Posts_Controller { 18 19 /** 20 * Constructor 21 * 22 * @since 5.x 23 * 24 * @param string $post_type Post type name 25 */ 26 public function __construct( $post_type ) { 27 28 parent::__construct( $post_type ); 29 30 add_filter( "rest_pre_insert_{$post_type}", array( $this, 'pre_insert_item' ), 0, 2 ); 31 add_action( "rest_insert_{$post_type}", array( $this, 'post_insert_item' ), 0, 2 ); 32 add_action( "rest_after_insert_{$post_type}", array( $this, 'post_insert_item' ), 0, 2 ); 33 add_filter( "rest_prepare_{$post_type}", array( $this, 'prepare_item_response' ), 0, 3 ); 34 35 } 36 37 /** 38 * Sets the menu item properties before storing the post in the database. 39 * 40 * @since 5.x 41 * 42 * @param stdClass $prepared_post Post 43 * @param WP_REST_Request $request Request 44 * 45 * @return stdClass|WP_Error 46 */ 47 public function pre_insert_item( $prepared_post, $request ) { 48 49 $schema = $this->get_item_schema(); 50 51 // Set the menu item type (stored as meta) 52 if ( ! empty( $schema['properties']['item_type'] ) && isset( $request['item_type'] ) ) { 53 $prepared_post->meta_input['_menu_item_type'] = $request['item_type']; 54 } 55 56 // Set the menu item attr_title (stored as post excerpt) 57 if ( ! empty( $schema['properties']['attr_title'] ) && isset( $request['attr_title'] ) ) { 58 $prepared_post->post_excerpt = $request['attr_title']; 59 } 60 61 // Set the menu item classes (stored as meta) 62 if ( ! empty( $schema['properties']['classes'] ) && isset( $request['classes'] ) ) { 63 $prepared_post->meta_input['_menu_item_classes'] = $request['classes']; 64 } 65 66 // Set the menu item description (stored as post content) 67 if ( ! empty( $schema['properties']['description'] ) && isset( $request['description'] ) ) { 68 $prepared_post->post_content = $request['description']; 69 } 70 71 if ( ! empty( $schema['properties']['menus'] ) ) { 72 if ( empty( $request['menus'] ) ) { 73 // If no menu is set, go ahead and mark as orphaned. 74 $prepared_post->meta_input['_menu_item_orphaned'] = (string) time(); 75 } 76 } 77 78 // Set the menu item object type (stored as meta) 79 if ( ! empty( $schema['properties']['object'] ) && isset( $request['object'] ) ) { 80 $prepared_post->meta_input['_menu_item_object'] = $request['object']; 81 } 82 83 // Set the menu item object id (stored as meta) 84 if ( ! empty( $schema['properties']['object_id'] ) && isset( $request['object_id'] ) ) { 85 $prepared_post->meta_input['_menu_item_object_id'] = $request['object_id']; 86 } 87 88 // Set the menu item parent (stored as meta) 89 if ( ! empty( $schema['properties']['parent'] ) && isset( $request['parent'] ) ) { 90 $prepared_post->meta_input['_menu_item_menu_item_parent'] = $request['parent']; 91 } 92 93 // Set the menu item parent (stored as meta) 94 if ( ! empty( $schema['properties']['target'] ) && isset( $request['target'] ) ) { 95 $prepared_post->meta_input['_menu_item_target'] = $request['target']; 96 } 97 98 // Set the menu item URL (stored as meta) 99 if ( ! empty( $schema['properties']['url'] ) && isset( $request['url'] ) ) { 100 $prepared_post->meta_input['_menu_item_url'] = $request['url']; 101 } 102 103 // Set the menu item xfn (stored as meta) 104 if ( ! empty( $schema['properties']['xfn'] ) && isset( $request['xfn'] ) ) { 105 $prepared_post->meta_input['_menu_item_xfn'] = $request['xfn']; 106 } 107 108 return $prepared_post; 109 } 110 111 /** 112 * Handles special cases after a post has been updated. 113 * 114 * @since 5.x 115 * 116 * @param WP_Post $post Post object. 117 * @param WP_REST_Request $request Request object. 118 */ 119 public function post_insert_item( $post, $request ) { 120 // If a menu is set, make sure we remove the orphaned marker. 121 if ( ! empty( $request['menus'] ) ) { 122 delete_post_meta( $post->ID, '_menu_item_orphaned' ); 123 } 124 } 125 126 /** 127 * Prepares a single item response. 128 * 129 * @since 5.x 130 * 131 * @param WP_REST_Response $response Response object. 132 * @param WP_Post $post Post object. 133 * @param WP_REST_Request $request Request object. 134 * 135 * @return WP_REST_Response Response object. 136 */ 137 public function prepare_item_response( $response, $post, $request ) { 138 139 $menu_item = wp_setup_nav_menu_item( $post ); 140 141 $response->data['attr_title'] = $menu_item->attr_title; // Same as post_excerpt 142 $response->data['classes'] = $menu_item->classes; 143 $response->data['description'] = $menu_item->description; // Same as post_content 144 $response->data['item_type'] = $menu_item->type; // Using 'item_type' since 'type' already exists. 145 $response->data['item_type_label'] = $menu_item->type_label; // Using 'item_type_label' to match up with 'item_type' - IS READ ONLY! 146 $response->data['object'] = $menu_item->object; 147 $response->data['object_id'] = absint( $menu_item->object_id ); // Usually is a string, but lets expose as an integer. 148 $response->data['parent'] = absint( $menu_item->menu_item_parent ); // Same as post_parent, expose as integer 149 $response->data['target'] = $menu_item->target; 150 $response->data['title']['rendered'] = $menu_item->title; // Overwrites 'title' (should be same as post_title) 151 $response->data['url'] = $menu_item->url; 152 $response->data['xfn'] = $menu_item->xfn; 153 154 return $response; 155 } 156 157 /** 158 * Retrieves the menu item's schema, conforming to JSON Schema. 159 * 160 * @since 5.x 161 * 162 * @return array Item schema as an array. 163 */ 164 public function get_item_schema() { 165 166 $schema = parent::get_item_schema(); 167 168 $schema['properties']['attr_title'] = array( 169 'description' => __( 'Text for the title attribute of the link element for this menu item.' ), 170 'type' => 'string', 171 'context' => array( 'view', 'edit', 'embed' ), 172 'arg_options' => array( 173 'sanitize_callback' => 'sanitize_text_field', 174 ), 175 ); 176 177 $schema['properties']['classes'] = array( 178 'description' => __( 'Class names for the link element of this menu item.' ), 179 'type' => 'array', 180 'context' => array( 'view', 'edit', 'embed' ), 181 'arg_options' => array( 182 'sanitize_callback' => function ( $value ) { 183 return array_map( 'sanitize_html_class', explode( ' ', $value ) ); 184 }, 185 ), 186 ); 187 188 $schema['properties']['description'] = array( 189 'description' => __( 'The description of this menu item.' ), 190 'type' => 'string', 191 'context' => array( 'view', 'edit', 'embed' ), 192 'arg_options' => array( 193 'sanitize_callback' => 'sanitize_text_field', 194 ), 195 ); 196 197 $schema['properties']['item_type'] = array( 198 'description' => __( 'The family of objects originally represented, such as "post_type" or "taxonomy".' ), 199 'type' => 'string', 200 'context' => array( 'view', 'edit', 'embed' ), 201 'default' => 'custom', 202 'arg_options' => array( 203 'sanitize_callback' => 'sanitize_key', 204 ), 205 'required' => true, 206 ); 207 208 $schema['properties']['item_type_label'] = array( 209 'description' => __( 'The singular label used to describe this type of menu item.' ), 210 'type' => 'string', 211 'context' => array( 'view', 'edit', 'embed' ), 212 'readonly' => true, 213 ); 214 215 $schema['properties']['menu_order'] = array( 216 'description' => __( 'The order of the object in relation to other objects of its type.' ), 217 'type' => 'integer', 218 'context' => array( 'view', 'edit', 'embed' ), 219 ); 220 221 $schema['properties']['menus'] = array( 222 'description' => __( 'The IDs representing the menus to which this menu item should be added.' ), 223 'type' => 'array', 224 'context' => array( 'view', 'edit', 'embed' ), 225 ); 226 227 $schema['properties']['object'] = array( 228 'description' => __( 'The type of object originally represented, such as "category," "post", or "attachment."' ), 229 'type' => 'string', 230 'context' => array( 'view', 'edit' ), 231 'default' => 'custom', 232 'arg_options' => array( 233 'sanitize_callback' => 'sanitize_key', 234 ), 235 'required' => true, 236 ); 237 238 $schema['properties']['object_id'] = array( 239 'description' => __( 'The DB ID of the original object this menu item represents, e.g. ID for posts and term_id for categories.' ), 240 'type' => 'integer', 241 'context' => array( 'view', 'edit' ), 242 'default' => 0, 243 'required' => true, 244 ); 245 246 $schema['properties']['parent'] = array( 247 'description' => __( "The DB ID of the nav_menu_item that is this item's menu parent, if any. 0 otherwise." ), 248 'type' => 'integer', 249 'context' => array( 'view', 'edit' ), 250 ); 251 252 $schema['properties']['target'] = array( 253 'description' => __( 'The target attribute of the link element for this menu item.' ), 254 'type' => 'string', 255 'context' => array( 'view', 'edit', 'embed' ), 256 'enum' => array( 257 '_blank', 258 '', 259 ), 260 ); 261 262 $schema['properties']['title']['required'] = true; 263 264 $schema['properties']['url'] = array( 265 'description' => __( 'The URL to which this menu item points.' ), 266 'type' => 'string', 267 'format' => 'uri', 268 'context' => array( 'view', 'edit', 'embed' ), 269 'arg_options' => array( 270 'sanitize_callback' => 'esc_url_raw', 271 ), 272 ); 273 274 $schema['properties']['xfn'] = array( 275 'description' => __( 'The XFN relationship expressed in the link of this menu item.' ), 276 'type' => 'string', 277 'context' => array( 'view', 'edit', 'embed' ), 278 'arg_options' => array( 279 'sanitize_callback' => function ( $value ) { 280 return implode( ' ', array_map( 'sanitize_html_class', explode( ' ', $value ) ) ); 281 }, 282 ), 283 ); 284 285 unset( 286 $schema['properties']['link'], 287 $schema['properties']['password'] 288 ); 289 290 return $schema; 291 } 292 } -
new file src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-locations-controller.php new file mode 100644 index 0000000000..4ebd8beee0
- + 1 <?php 2 /** 3 * REST API: WP_REST_Menu_Locations_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 5.x 8 */ 9 10 /** 11 * Core controller used to access menu locations via the REST API. 12 * 13 * @since 5.x 14 * 15 * @see WP_REST_Controller 16 */ 17 class WP_REST_Menu_Locations_Controller extends WP_REST_Controller { 18 19 /** 20 * The namespace of this controller's route. 21 * 22 * @since 5.x 23 * 24 * @var string 25 */ 26 protected $namespace = 'wp/v2'; 27 28 /** 29 * The base of this controller's route. 30 * 31 * @since 5.x 32 * 33 * @var string 34 */ 35 protected $rest_base = 'menu-locations'; 36 37 /** 38 * Registers the routes for the objects of the controller. 39 * 40 * @since 5.x 41 * 42 * @see register_rest_route() 43 */ 44 public function register_routes() { 45 46 register_rest_route( 47 $this->namespace, 48 '/' . $this->rest_base, 49 array( 50 array( 51 'methods' => WP_REST_Server::READABLE, 52 'callback' => array( $this, 'get_items' ), 53 'permission_callback' => array( $this, 'get_items_permissions_check' ), 54 'args' => $this->get_collection_params(), 55 ), 56 'schema' => array( $this, 'get_public_item_schema' ), 57 ) 58 ); 59 60 register_rest_route( 61 $this->namespace, 62 '/' . $this->rest_base . '/(?P<location>[\w-]+)', 63 array( 64 'args' => array( 65 'location' => array( 66 'description' => __( 'An alphanumeric identifier for the menu location.' ), 67 'type' => 'string', 68 ), 69 ), 70 array( 71 'methods' => WP_REST_Server::READABLE, 72 'callback' => array( $this, 'get_item' ), 73 'permission_callback' => array( $this, 'get_item_permissions_check' ), 74 'args' => array( 75 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 76 ), 77 ), 78 array( 79 'methods' => WP_REST_Server::EDITABLE, 80 'callback' => array( $this, 'update_item' ), 81 'permission_callback' => array( $this, 'update_item_permissions_check' ), 82 'args' => array( 83 'menu' => array( 84 'validate_callback' => function( $id ) { 85 return 0 === $id || false !== wp_get_nav_menu_object( $id ); 86 }, 87 'required' => true, 88 ), 89 ), 90 ), 91 'schema' => array( $this, 'get_public_item_schema' ), 92 ) 93 ); 94 } 95 96 /** 97 * Checks whether a given request has permission to read menu locations. 98 * 99 * @since 5.x 100 * 101 * @param WP_REST_Request $request Full details about the request. 102 * 103 * @return WP_Error|bool True if the request has read access, WP_Error object otherwise. 104 */ 105 public function get_items_permissions_check( $request ) { 106 if ( ! current_user_can( 'edit_theme_options' ) ) { 107 return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to view menu locations.' ), array( 'status' => rest_authorization_required_code() ) ); 108 } 109 110 return true; 111 } 112 113 /** 114 * Retrieves all menu locations, depending on user context. 115 * 116 * @since 5.x 117 * 118 * @param WP_REST_Request $request Full details about the request. 119 * 120 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. 121 */ 122 public function get_items( $request ) { 123 124 $data = array(); 125 126 $locations = $this->get_locations(); 127 128 foreach ( $locations as $location ) { 129 $item = $this->prepare_item_for_response( $location, $request ); 130 $data[ $location->name ] = $this->prepare_response_for_collection( $item ); 131 } 132 133 return rest_ensure_response( $data ); 134 } 135 136 /** 137 * Checks if a given request has access to read a menu location. 138 * 139 * @since 5.x 140 * 141 * @param WP_REST_Request $request Full details about the request. 142 * 143 * @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise. 144 */ 145 public function get_item_permissions_check( $request ) { 146 147 if ( ! current_user_can( 'edit_theme_options' ) ) { 148 return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to view menu locations.' ), array( 'status' => rest_authorization_required_code() ) ); 149 } 150 151 if ( ! array_key_exists( $request['location'], get_registered_nav_menus() ) ) { 152 return new WP_Error( 'rest_menu_location_invalid', __( 'Invalid menu location.' ), array( 'status' => 404 ) ); 153 } 154 155 return true; 156 } 157 158 /** 159 * Retrieves a specific menu location. 160 * 161 * @since 5.x 162 * 163 * @param WP_REST_Request $request Full details about the request. 164 * 165 * @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure. 166 */ 167 public function get_item( $request ) { 168 169 $locations = $this->get_locations(); 170 if ( ! array_key_exists( $request['location'], $locations ) ) { 171 return new WP_Error( 'rest_menu_location_invalid', __( 'Invalid menu location.' ), array( 'status' => 404 ) ); 172 } 173 174 $item = $locations[ $request['location'] ]; 175 176 return rest_ensure_response( $this->prepare_item_for_response( $item, $request ) ); 177 } 178 179 /** 180 * Checks if a given request has access to update a menu location. 181 * 182 * @since 5.x 183 * 184 * @param WP_REST_Request $request Full details about the request. 185 * 186 * @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise. 187 */ 188 public function update_item_permissions_check( $request ) { 189 190 if ( ! current_user_can( 'edit_theme_options' ) ) { 191 return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to manage menu locations.' ), array( 'status' => rest_authorization_required_code() ) ); 192 } 193 194 if ( ! array_key_exists( $request['location'], get_registered_nav_menus() ) ) { 195 return new WP_Error( 'rest_menu_location_invalid', __( 'Invalid menu location.' ), array( 'status' => 404 ) ); 196 } 197 198 return true; 199 } 200 201 /** 202 * Updates one item from the collection. 203 * 204 * @since 5.x 205 * 206 * @param WP_REST_Request $request REST request 207 * 208 * @return WP_REST_Response|WP_Error 209 */ 210 public function update_item( $request ) { 211 212 $locations = $this->get_locations(); 213 $location = $locations[ $request['location'] ]; 214 $location->menu = $request['menu']; 215 216 // Update theme mod 217 $theme_mod = get_nav_menu_locations(); 218 $theme_mod[ $location->name ] = $request['menu']; 219 set_theme_mod( 'nav_menu_locations', $theme_mod ); 220 221 return rest_ensure_response( $this->prepare_item_for_response( $location, $request ) ); 222 } 223 224 /** 225 * Prepares a menu location object for serialization. 226 * 227 * @since 5.x 228 * 229 * @param stdClass $location Menu location data. 230 * @param WP_REST_Request $request Full details about the request. 231 * 232 * @return WP_REST_Response Response object. 233 */ 234 public function prepare_item_for_response( $location, $request ) { 235 236 $item = (array) $location; 237 238 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 239 $data = $this->add_additional_fields_to_object( $item, $request ); 240 $data = $this->filter_response_by_context( $data, $context ); 241 242 $response = rest_ensure_response( $data ); 243 244 $response->add_links( $this->prepare_links( $item ) ); 245 246 /** 247 * Filters a menu location returned from the REST API. 248 * 249 * Allows modification of the menu location data right before it is 250 * returned. 251 * 252 * @since 5.x 253 * 254 * @param WP_REST_Response $response The response object. 255 * @param stdClass $location The original location object. 256 * @param WP_REST_Request $request Request used to generate the response. 257 */ 258 return apply_filters( 'rest_prepare_nav_menu_location', $response, $location, $request ); 259 } 260 261 /** 262 * Retrieves the menu location's schema, conforming to JSON Schema. 263 * 264 * @since 5.x 265 * 266 * @return array Item schema data. 267 */ 268 public function get_item_schema() { 269 270 $schema = array( 271 '$schema' => 'http://json-schema.org/draft-04/schema#', 272 'title' => 'menu-location', 273 'type' => 'object', 274 'properties' => array( 275 'name' => array( 276 'description' => __( 'The name of the menu location.' ), 277 'type' => 'string', 278 'context' => array( 'view', 'edit' ), 279 'readonly' => true, 280 ), 281 'description' => array( 282 'description' => __( 'The description of the menu location.' ), 283 'type' => 'string', 284 'context' => array( 'view', 'edit' ), 285 'readonly' => true, 286 ), 287 'menu' => array( 288 'description' => __( 'The ID of the assigned menu.' ), 289 'type' => 'int', 290 'context' => array( 'embed', 'view', 'edit' ), 291 ), 292 ), 293 ); 294 295 return $this->add_additional_fields_schema( $schema ); 296 } 297 298 /** 299 * Retrieves the query params for collections. 300 * 301 * @since 5.x 302 * 303 * @return array Collection parameters. 304 */ 305 public function get_collection_params() { 306 return array( 307 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 308 ); 309 } 310 311 /** 312 * Prepares links for the request. 313 * 314 * @since 5.x 315 * 316 * @param array $location Menu location. 317 * 318 * @return array Links for the given menu location. 319 */ 320 protected function prepare_links( $location ) { 321 $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); 322 323 // Entity meta. 324 $links = array( 325 'self' => array( 326 'href' => rest_url( trailingslashit( $base ) . $location['name'] ), 327 ), 328 'collection' => array( 329 'href' => rest_url( $base ), 330 ), 331 ); 332 333 if ( $location['menu'] > 0 ) { 334 $links['menu'] = array( 335 'href' => rest_url( sprintf( '%s/menus/%d', $this->namespace, $location['menu'] ) ), 336 'embeddable' => true, 337 ); 338 } 339 340 return $links; 341 } 342 343 /** 344 * Get all nav menu locations as well as assigned menu IDs. 345 * 346 * @since 5.x 347 * 348 * @return stdClass[] 349 */ 350 protected function get_locations() { 351 352 $locations = array(); 353 354 $registered = get_registered_nav_menus(); 355 $assigned = get_nav_menu_locations(); 356 357 foreach ( $registered as $name => $description ) { 358 $locations[ $name ] = (object) array( 359 'name' => $name, 360 'description' => $description, 361 'menu' => isset( $assigned[ $name ] ) ? absint( $assigned[ $name ] ) : 0, 362 ); 363 } 364 365 return $locations; 366 367 } 368 369 } -
new file src/wp-includes/rest-api/endpoints/class-wp-rest-menu-settings-controller.php
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-settings-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-menu-settings-controller.php new file mode 100644 index 0000000000..a860efbd70
- + 1 <?php 2 /** 3 * REST API: WP_REST_Menu_Settings_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 5.x 8 */ 9 10 /** 11 * Core controller used to access menu settings via the REST API. 12 * 13 * @since 5.x 14 * 15 * @see WP_REST_Posts_Controller 16 */ 17 class WP_REST_Menu_Settings_Controller extends WP_REST_Controller { 18 19 /** 20 * The namespace of this controller's route. 21 * 22 * @since 5.x 23 * 24 * @var string 25 */ 26 protected $namespace = 'wp/v2'; 27 28 /** 29 * The base of this controller's route. 30 * 31 * @since 5.x 32 * 33 * @var string 34 */ 35 protected $rest_base = 'menus'; 36 37 /** 38 * Registers the routes for the objects of the controller. 39 * 40 * @since 5.x 41 */ 42 public function register_routes() { 43 44 register_rest_route( 45 $this->namespace, 46 "/{$this->rest_base}/(?P<id>[\d]+)/settings", 47 array( 48 'args' => array( 49 'id' => array( 50 'description' => __( 'Unique identifier for the menu.' ), 51 'type' => 'integer', 52 ), 53 ), 54 array( 55 'methods' => WP_REST_Server::READABLE, 56 'callback' => array( $this, 'get_item' ), 57 'permission_callback' => array( $this, 'get_item_permissions_check' ), 58 'args' => array( 59 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 60 ), 61 ), 62 array( 63 'methods' => WP_REST_Server::EDITABLE, 64 'callback' => array( $this, 'update_item' ), 65 'permission_callback' => array( $this, 'update_item_permissions_check' ), 66 'args' => array( 67 'auto_add' => array( 68 'sanitize_callback' => 'wp_validate_boolean', 69 'validate_callback' => 'rest_is_boolean', 70 'required' => true, 71 ), 72 ), 73 ), 74 'schema' => array( $this, 'get_public_item_schema' ), 75 ) 76 ); 77 78 } 79 80 /** 81 * Retrieves one item from the collection. 82 * 83 * @since 5.x 84 * 85 * @param WP_REST_Request $request REST request 86 * 87 * @return WP_REST_Response|WP_Error 88 */ 89 public function get_item( $request ) { 90 91 $menu_id = $request['id']; 92 $menu = wp_get_nav_menu_object( $menu_id ); 93 94 if ( ! $menu ) { 95 return new WP_Error( 'rest_invalid_menu_id', __( 'Invalid menu ID' ), array( 'status' => 404 ) ); 96 } 97 98 $item = array( 99 'id' => $menu_id, 100 'auto_add' => false, 101 ); 102 103 $nav_menu_options = (array) get_option( 'nav_menu_options' ); 104 105 if ( isset( $nav_menu_options['auto_add'] ) ) { 106 if ( in_array( $menu_id, $nav_menu_options['auto_add'], true ) ) { 107 $item['auto_add'] = (bool) $nav_menu_options['auto_add']; 108 } 109 } 110 111 return $this->prepare_item_for_response( $item, $request ); 112 } 113 114 /** 115 * Checks if a given request has access to get a specific item. 116 * 117 * @since 5.x 118 * 119 * @param WP_REST_Request $request REST request 120 * 121 * @return bool|WP_Error 122 */ 123 public function get_item_permissions_check( $request ) { 124 if ( ! current_user_can( 'edit_theme_options' ) ) { 125 return new WP_Error( 'rest_forbidden_context', __( "Sorry, you are not allowed to view this menu's settings." ), array( 'status' => rest_authorization_required_code() ) ); 126 } 127 128 return true; 129 } 130 131 /** 132 * Updates one item from the collection. 133 * 134 * @since 5.x 135 * 136 * @param WP_REST_Request $request REST request 137 * 138 * @return WP_REST_Response|WP_Error 139 */ 140 public function update_item( $request ) { 141 142 $menu_id = $request['id']; 143 $menu = wp_get_nav_menu_object( $menu_id ); 144 145 if ( ! $menu ) { 146 return new WP_Error( 'rest_invalid_menu_id', __( 'Invalid menu ID' ), array( 'status' => 404 ) ); 147 } 148 149 $item = array( 150 'id' => $menu_id, 151 'auto_add' => false, 152 ); 153 154 $nav_menu_options = (array) get_option( 'nav_menu_options' ); 155 156 if ( isset( $nav_menu_options['auto_add'] ) ) { 157 if ( in_array( $menu_id, $nav_menu_options['auto_add'], true ) ) { 158 $item['auto_add'] = true; 159 } 160 } else { 161 $nav_menu_options['auto_add'] = array(); 162 } 163 164 // Update auto add pages setting 165 $item['auto_add'] = $request->get_param( 'auto_add' ); 166 if ( $item['auto_add'] ) { 167 $nav_menu_options['auto_add'][] = $menu_id; 168 } else { 169 $key = array_search( $menu_id, $nav_menu_options['auto_add'], true ); 170 if ( false !== $key ) { 171 unset( $nav_menu_options['auto_add'][ $key ] ); 172 } 173 } 174 update_option( 'nav_menu_options', $nav_menu_options ); 175 176 return $this->prepare_item_for_response( $item, $request ); 177 } 178 179 /** 180 * Checks if a given request has access to update a specific item. 181 * 182 * @since 5.x 183 * 184 * @param WP_REST_Request $request REST request 185 * 186 * @return bool|WP_Error 187 */ 188 public function update_item_permissions_check( $request ) { 189 if ( ! current_user_can( 'edit_theme_options' ) ) { 190 return new WP_Error( 'rest_cannot_update', __( "Sorry, you are not allowed to edit this menu's settings." ), array( 'status' => rest_authorization_required_code() ) ); 191 } 192 193 return true; 194 } 195 196 /** 197 * Prepares the item for the REST response. 198 * 199 * @since 5.x 200 * 201 * @param array $item Menu settings 202 * @param WP_REST_Request $request REST request 203 * 204 * @return WP_REST_Response|WP_Error 205 */ 206 public function prepare_item_for_response( $item, $request ) { 207 208 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 209 $data = $this->add_additional_fields_to_object( $item, $request ); 210 $data = $this->filter_response_by_context( $data, $context ); 211 212 $response = rest_ensure_response( $data ); 213 214 $response->add_links( $this->prepare_links( $item ) ); 215 216 /** 217 * Filters a menu location returned from the REST API. 218 * 219 * Allows modification of the menu's settings data right before it is 220 * returned. 221 * 222 * @since 5.x 223 * 224 * @param WP_REST_Response $response The response object. 225 * @param array $item The original status object. 226 * @param WP_REST_Request $request Request used to generate the response. 227 */ 228 return apply_filters( 'rest_prepare_nav_menu_settings', $response, $item, $request ); 229 } 230 231 /** 232 * Prepares links for the request. 233 * 234 * @since 5.x 235 * 236 * @param array $item Menu settings. 237 * 238 * @return array Links for the given menu settings. 239 */ 240 protected function prepare_links( $item ) { 241 242 $base = sprintf( '%s/%s', $this->namespace, $this->rest_base ); 243 244 // Entity meta. 245 $links = array( 246 'self' => array( 247 'href' => rest_url( trailingslashit( $base ) . $item['id'] . '/settings' ), 248 ), 249 'menu' => array( 250 'href' => rest_url( trailingslashit( $base ) . $item['id'] ), 251 'embeddable' => true, 252 ), 253 ); 254 255 return $links; 256 } 257 258 /** 259 * Retrieves the menu settings schema, conforming to JSON Schema. 260 * 261 * @since 5.x 262 * 263 * @return array Item schema data. 264 */ 265 public function get_item_schema() { 266 267 $schema = array( 268 '$schema' => 'http://json-schema.org/draft-04/schema#', 269 'title' => 'menu-settings', 270 'type' => 'object', 271 'properties' => array( 272 'id' => array( 273 'description' => 'Unique identifier for the menu.', 274 'type' => 'integer', 275 'context' => array( 'view', 'edit' ), 276 'readonly' => true, 277 ), 278 'auto_add' => array( 279 'description' => __( 'Whether or not to automatically add top level pages to the menu.' ), 280 'type' => 'boolean', 281 'context' => array( 'view', 'edit' ), 282 ), 283 ), 284 ); 285 286 return $this->add_additional_fields_schema( $schema ); 287 } 288 289 } -
new file src/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php
diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-menus-controller.php new file mode 100644 index 0000000000..a3433291bc
- + 1 <?php 2 /** 3 * REST API: WP_REST_Menus_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 5.x 8 */ 9 10 /** 11 * Core controller used to access menus via the REST API. 12 * 13 * @since 5.x 14 * 15 * @see WP_REST_Posts_Controller 16 */ 17 class WP_REST_Menus_Controller extends WP_REST_Terms_Controller { 18 19 /** 20 * Prepares links for the request. 21 * 22 * @since 5.x 23 * 24 * @param object $term Term object. 25 * @return array Links for the given term. 26 */ 27 protected function prepare_links( $term ) { 28 29 $links = parent::prepare_links( $term ); 30 31 // Let's make sure that menu items are embeddable for a menu collection. 32 if ( array_key_exists( 'https://api.w.org/post_type', $links ) ) { 33 $post_type_links = $links['https://api.w.org/post_type']; 34 35 foreach ( $post_type_links as $index => $post_type_link ) { 36 if ( ! array_key_exists( 'href', $post_type_link ) || strpos( $post_type_link['href'], '/menu-items?' ) === false ) { 37 continue; 38 } 39 40 $post_type_links[ $index ]['embeddable'] = true; 41 } 42 43 $links['https://api.w.org/post_type'] = $post_type_links; 44 } 45 46 return $links; 47 } 48 49 } -
src/wp-includes/taxonomy.php
diff --git a/src/wp-includes/taxonomy.php b/src/wp-includes/taxonomy.php index 12864b343a..266682a367 100644
a b function create_initial_taxonomies() { 107 107 'nav_menu', 108 108 'nav_menu_item', 109 109 array( 110 'public' => false,111 'hierarchical' => false,112 'labels' => array(110 'public' => false, 111 'hierarchical' => false, 112 'labels' => array( 113 113 'name' => __( 'Navigation Menus' ), 114 114 'singular_name' => __( 'Navigation Menu' ), 115 115 ), 116 'query_var' => false, 117 'rewrite' => false, 118 'show_ui' => false, 119 '_builtin' => true, 120 'show_in_nav_menus' => false, 116 'query_var' => false, 117 'rewrite' => false, 118 'show_ui' => false, 119 '_builtin' => true, 120 'show_in_nav_menus' => false, 121 'show_in_rest' => true, 122 'rest_base' => 'menus', 123 'rest_controller_class' => 'WP_REST_Menus_Controller', 121 124 ) 122 125 ); 123 126 -
src/wp-settings.php
diff --git a/src/wp-settings.php b/src/wp-settings.php index fa9f9a3a25..42680af27f 100644
a b require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-blocks-controller. 251 251 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-block-renderer-controller.php' ); 252 252 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-settings-controller.php' ); 253 253 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-themes-controller.php' ); 254 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-menu-items-controller.php' ); 255 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-menu-locations-controller.php' ); 256 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-menu-settings-controller.php' ); 257 require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-menus-controller.php' ); 254 258 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php' ); 255 259 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php' ); 256 260 require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php' );