[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_Menus_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 5.9.0 8 */ 9 10 /** 11 * Core class used to managed menu terms associated via the REST API. 12 * 13 * @since 5.9.0 14 * 15 * @see WP_REST_Controller 16 */ 17 class WP_REST_Menus_Controller extends WP_REST_Terms_Controller { 18 19 /** 20 * Checks if a request has access to read menus. 21 * 22 * @since 5.9.0 23 * 24 * @param WP_REST_Request $request Full details about the request. 25 * @return bool|WP_Error True if the request has read access, otherwise false or WP_Error object. 26 */ 27 public function get_items_permissions_check( $request ) { 28 $has_permission = parent::get_items_permissions_check( $request ); 29 30 if ( true !== $has_permission ) { 31 return $has_permission; 32 } 33 34 return $this->check_has_read_only_access( $request ); 35 } 36 37 /** 38 * Checks if a request has access to read or edit the specified menu. 39 * 40 * @since 5.9.0 41 * 42 * @param WP_REST_Request $request Full details about the request. 43 * @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object. 44 */ 45 public function get_item_permissions_check( $request ) { 46 $has_permission = parent::get_item_permissions_check( $request ); 47 48 if ( true !== $has_permission ) { 49 return $has_permission; 50 } 51 52 return $this->check_has_read_only_access( $request ); 53 } 54 55 /** 56 * Gets the term, if the ID is valid. 57 * 58 * @since 5.9.0 59 * 60 * @param int $id Supplied ID. 61 * @return WP_Term|WP_Error Term object if ID is valid, WP_Error otherwise. 62 */ 63 protected function get_term( $id ) { 64 $term = parent::get_term( $id ); 65 66 if ( is_wp_error( $term ) ) { 67 return $term; 68 } 69 70 $nav_term = wp_get_nav_menu_object( $term ); 71 $nav_term->auto_add = $this->get_menu_auto_add( $nav_term->term_id ); 72 73 return $nav_term; 74 } 75 76 /** 77 * Checks whether the current user has read permission for the endpoint. 78 * 79 * This allows for any user that can `edit_theme_options` or edit any REST API available post type. 80 * 81 * @since 5.9.0 82 * 83 * @param WP_REST_Request $request Full details about the request. 84 * @return true|WP_Error True if the current user has permission, WP_Error object otherwise. 85 */ 86 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 93 if ( current_user_can( 'edit_theme_options' ) ) { 94 return true; 95 } 96 97 if ( current_user_can( 'edit_posts' ) ) { 98 return true; 99 } 100 101 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { 102 if ( current_user_can( $post_type->cap->edit_posts ) ) { 103 return true; 104 } 105 } 106 107 return new WP_Error( 108 'rest_cannot_view', 109 __( 'Sorry, you are not allowed to view menus.' ), 110 array( 'status' => rest_authorization_required_code() ) 111 ); 112 } 113 114 /** 115 * Prepares a single term output for response. 116 * 117 * @since 5.9.0 118 * 119 * @param WP_Term $term Term object. 120 * @param WP_REST_Request $request Request object. 121 * @return WP_REST_Response Response object. 122 */ 123 public function prepare_item_for_response( $term, $request ) { 124 $nav_menu = wp_get_nav_menu_object( $term ); 125 $response = parent::prepare_item_for_response( $nav_menu, $request ); 126 127 $fields = $this->get_fields_for_response( $request ); 128 $data = $response->get_data(); 129 130 if ( rest_is_field_included( 'locations', $fields ) ) { 131 $data['locations'] = $this->get_menu_locations( $nav_menu->term_id ); 132 } 133 134 if ( rest_is_field_included( 'auto_add', $fields ) ) { 135 $data['auto_add'] = $this->get_menu_auto_add( $nav_menu->term_id ); 136 } 137 138 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 139 $data = $this->add_additional_fields_to_object( $data, $request ); 140 $data = $this->filter_response_by_context( $data, $context ); 141 142 $response = rest_ensure_response( $data ); 143 144 if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) { 145 $response->add_links( $this->prepare_links( $term ) ); 146 } 147 148 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ 149 return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $term, $request ); 150 } 151 152 /** 153 * Prepares links for the request. 154 * 155 * @since 5.9.0 156 * 157 * @param WP_Term $term Term object. 158 * @return array Links for the given term. 159 */ 160 protected function prepare_links( $term ) { 161 $links = parent::prepare_links( $term ); 162 163 $locations = $this->get_menu_locations( $term->term_id ); 164 foreach ( $locations as $location ) { 165 $url = rest_url( sprintf( 'wp/v2/menu-locations/%s', $location ) ); 166 167 $links['https://api.w.org/menu-location'][] = array( 168 'href' => $url, 169 'embeddable' => true, 170 ); 171 } 172 173 return $links; 174 } 175 176 /** 177 * Prepares a single term for create or update. 178 * 179 * @since 5.9.0 180 * 181 * @param WP_REST_Request $request Request object. 182 * @return object Prepared term data. 183 */ 184 public function prepare_item_for_database( $request ) { 185 $prepared_term = parent::prepare_item_for_database( $request ); 186 187 $schema = $this->get_item_schema(); 188 189 if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) { 190 $prepared_term->{'menu-name'} = $request['name']; 191 } 192 193 return $prepared_term; 194 } 195 196 /** 197 * Creates a single term in a taxonomy. 198 * 199 * @since 5.9.0 200 * 201 * @param WP_REST_Request $request Full details about the request. 202 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 203 */ 204 public function create_item( $request ) { 205 if ( isset( $request['parent'] ) ) { 206 if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { 207 return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) ); 208 } 209 210 $parent = wp_get_nav_menu_object( (int) $request['parent'] ); 211 212 if ( ! $parent ) { 213 return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) ); 214 } 215 } 216 217 $prepared_term = $this->prepare_item_for_database( $request ); 218 219 $term = wp_update_nav_menu_object( 0, wp_slash( (array) $prepared_term ) ); 220 221 if ( is_wp_error( $term ) ) { 222 /* 223 * If we're going to inform the client that the term already exists, 224 * give them the identifier for future use. 225 */ 226 227 if ( in_array( 'menu_exists', $term->get_error_codes(), true ) ) { 228 $existing_term = get_term_by( 'name', $prepared_term->{'menu-name'}, $this->taxonomy ); 229 $term->add_data( $existing_term->term_id, 'menu_exists' ); 230 $term->add_data( 231 array( 232 'status' => 400, 233 'term_id' => $existing_term->term_id, 234 ) 235 ); 236 } else { 237 $term->add_data( array( 'status' => 400 ) ); 238 } 239 240 return $term; 241 } 242 243 $term = $this->get_term( $term ); 244 245 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ 246 do_action( "rest_insert_{$this->taxonomy}", $term, $request, true ); 247 248 $schema = $this->get_item_schema(); 249 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 250 $meta_update = $this->meta->update_value( $request['meta'], $term->term_id ); 251 252 if ( is_wp_error( $meta_update ) ) { 253 return $meta_update; 254 } 255 } 256 257 $locations_update = $this->handle_locations( $term->term_id, $request ); 258 259 if ( is_wp_error( $locations_update ) ) { 260 return $locations_update; 261 } 262 263 $this->handle_auto_add( $term->term_id, $request ); 264 265 $fields_update = $this->update_additional_fields_for_object( $term, $request ); 266 267 if ( is_wp_error( $fields_update ) ) { 268 return $fields_update; 269 } 270 271 $request->set_param( 'context', 'view' ); 272 273 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ 274 do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, true ); 275 276 $response = $this->prepare_item_for_response( $term, $request ); 277 $response = rest_ensure_response( $response ); 278 279 $response->set_status( 201 ); 280 $response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) ); 281 282 return $response; 283 } 284 285 /** 286 * Updates a single term from a taxonomy. 287 * 288 * @since 5.9.0 289 * 290 * @param WP_REST_Request $request Full details about the request. 291 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 292 */ 293 public function update_item( $request ) { 294 $term = $this->get_term( $request['id'] ); 295 if ( is_wp_error( $term ) ) { 296 return $term; 297 } 298 299 if ( isset( $request['parent'] ) ) { 300 if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) { 301 return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) ); 302 } 303 304 $parent = get_term( (int) $request['parent'], $this->taxonomy ); 305 306 if ( ! $parent ) { 307 return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) ); 308 } 309 } 310 311 $prepared_term = $this->prepare_item_for_database( $request ); 312 313 // Only update the term if we have something to update. 314 if ( ! empty( $prepared_term ) ) { 315 if ( ! isset( $prepared_term->{'menu-name'} ) ) { 316 // wp_update_nav_menu_object() requires that the menu-name is always passed. 317 $prepared_term->{'menu-name'} = $term->name; 318 } 319 320 $update = wp_update_nav_menu_object( $term->term_id, wp_slash( (array) $prepared_term ) ); 321 322 if ( is_wp_error( $update ) ) { 323 return $update; 324 } 325 } 326 327 $term = get_term( $term->term_id, $this->taxonomy ); 328 329 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ 330 do_action( "rest_insert_{$this->taxonomy}", $term, $request, false ); 331 332 $schema = $this->get_item_schema(); 333 if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) { 334 $meta_update = $this->meta->update_value( $request['meta'], $term->term_id ); 335 336 if ( is_wp_error( $meta_update ) ) { 337 return $meta_update; 338 } 339 } 340 341 $locations_update = $this->handle_locations( $term->term_id, $request ); 342 343 if ( is_wp_error( $locations_update ) ) { 344 return $locations_update; 345 } 346 347 $this->handle_auto_add( $term->term_id, $request ); 348 349 $fields_update = $this->update_additional_fields_for_object( $term, $request ); 350 351 if ( is_wp_error( $fields_update ) ) { 352 return $fields_update; 353 } 354 355 $request->set_param( 'context', 'view' ); 356 357 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ 358 do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, false ); 359 360 $response = $this->prepare_item_for_response( $term, $request ); 361 362 return rest_ensure_response( $response ); 363 } 364 365 /** 366 * Deletes a single term from a taxonomy. 367 * 368 * @since 5.9.0 369 * 370 * @param WP_REST_Request $request Full details about the request. 371 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 372 */ 373 public function delete_item( $request ) { 374 $term = $this->get_term( $request['id'] ); 375 if ( is_wp_error( $term ) ) { 376 return $term; 377 } 378 379 // We don't support trashing for terms. 380 if ( ! $request['force'] ) { 381 /* translators: %s: force=true */ 382 return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Menus do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) ); 383 } 384 385 $request->set_param( 'context', 'view' ); 386 387 $previous = $this->prepare_item_for_response( $term, $request ); 388 389 $result = wp_delete_nav_menu( $term ); 390 391 if ( ! $result || is_wp_error( $result ) ) { 392 return new WP_Error( 'rest_cannot_delete', __( 'The menu cannot be deleted.' ), array( 'status' => 500 ) ); 393 } 394 395 $response = new WP_REST_Response(); 396 $response->set_data( 397 array( 398 'deleted' => true, 399 'previous' => $previous->get_data(), 400 ) 401 ); 402 403 /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */ 404 do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request ); 405 406 return $response; 407 } 408 409 /** 410 * Returns the value of a menu's auto_add setting. 411 * 412 * @since 5.9.0 413 * 414 * @param int $menu_id The menu id to query. 415 * @return bool The value of auto_add. 416 */ 417 protected function get_menu_auto_add( $menu_id ) { 418 $nav_menu_option = (array) get_option( 'nav_menu_options', array( 'auto_add' => array() ) ); 419 420 return in_array( $menu_id, $nav_menu_option['auto_add'], true ); 421 } 422 423 /** 424 * Updates the menu's auto add from a REST request. 425 * 426 * @since 5.9.0 427 * 428 * @param int $menu_id The menu id to update. 429 * @param WP_REST_Request $request Full details about the request. 430 * @return bool True if the auto add setting was successfully updated. 431 */ 432 protected function handle_auto_add( $menu_id, $request ) { 433 if ( ! isset( $request['auto_add'] ) ) { 434 return true; 435 } 436 437 $nav_menu_option = (array) get_option( 'nav_menu_options', array( 'auto_add' => array() ) ); 438 439 if ( ! isset( $nav_menu_option['auto_add'] ) ) { 440 $nav_menu_option['auto_add'] = array(); 441 } 442 443 $auto_add = $request['auto_add']; 444 445 $i = array_search( $menu_id, $nav_menu_option['auto_add'], true ); 446 447 if ( $auto_add && false === $i ) { 448 $nav_menu_option['auto_add'][] = $menu_id; 449 } elseif ( ! $auto_add && false !== $i ) { 450 array_splice( $nav_menu_option['auto_add'], $i, 1 ); 451 } 452 453 $update = update_option( 'nav_menu_options', $nav_menu_option ); 454 455 /** This action is documented in wp-includes/nav-menu.php */ 456 do_action( 'wp_update_nav_menu', $menu_id ); 457 458 return $update; 459 } 460 461 /** 462 * Returns the names of the locations assigned to the menu. 463 * 464 * @since 5.9.0 465 * 466 * @param int $menu_id The menu id. 467 * @return string[] The locations assigned to the menu. 468 */ 469 protected function get_menu_locations( $menu_id ) { 470 $locations = get_nav_menu_locations(); 471 $menu_locations = array(); 472 473 foreach ( $locations as $location => $assigned_menu_id ) { 474 if ( $menu_id === $assigned_menu_id ) { 475 $menu_locations[] = $location; 476 } 477 } 478 479 return $menu_locations; 480 } 481 482 /** 483 * Updates the menu's locations from a REST request. 484 * 485 * @since 5.9.0 486 * 487 * @param int $menu_id The menu id to update. 488 * @param WP_REST_Request $request Full details about the request. 489 * @return true|WP_Error True on success, a WP_Error on an error updating any of the locations. 490 */ 491 protected function handle_locations( $menu_id, $request ) { 492 if ( ! isset( $request['locations'] ) ) { 493 return true; 494 } 495 496 $menu_locations = get_registered_nav_menus(); 497 $menu_locations = array_keys( $menu_locations ); 498 $new_locations = array(); 499 foreach ( $request['locations'] as $location ) { 500 if ( ! in_array( $location, $menu_locations, true ) ) { 501 return new WP_Error( 502 'rest_invalid_menu_location', 503 __( 'Invalid menu location.' ), 504 array( 505 'status' => 400, 506 'location' => $location, 507 ) 508 ); 509 } 510 $new_locations[ $location ] = $menu_id; 511 } 512 $assigned_menu = get_nav_menu_locations(); 513 foreach ( $assigned_menu as $location => $term_id ) { 514 if ( $term_id === $menu_id ) { 515 unset( $assigned_menu[ $location ] ); 516 } 517 } 518 $new_assignments = array_merge( $assigned_menu, $new_locations ); 519 set_theme_mod( 'nav_menu_locations', $new_assignments ); 520 521 return true; 522 } 523 524 /** 525 * Retrieves the term's schema, conforming to JSON Schema. 526 * 527 * @since 5.9.0 528 * 529 * @return array Item schema data. 530 */ 531 public function get_item_schema() { 532 if ( $this->schema ) { 533 return $this->add_additional_fields_schema( $this->schema ); 534 } 535 536 $schema = parent::get_item_schema(); 537 unset( $schema['properties']['count'], $schema['properties']['link'], $schema['properties']['taxonomy'] ); 538 539 $schema['properties']['locations'] = array( 540 'description' => __( 'The locations assigned to the menu.' ), 541 'type' => 'array', 542 'items' => array( 543 'type' => 'string', 544 ), 545 'context' => array( 'view', 'edit' ), 546 'arg_options' => array( 547 'validate_callback' => static function ( $locations, $request, $param ) { 548 $valid = rest_validate_request_arg( $locations, $request, $param ); 549 550 if ( true !== $valid ) { 551 return $valid; 552 } 553 554 $locations = rest_sanitize_request_arg( $locations, $request, $param ); 555 556 foreach ( $locations as $location ) { 557 if ( ! array_key_exists( $location, get_registered_nav_menus() ) ) { 558 return new WP_Error( 559 'rest_invalid_menu_location', 560 __( 'Invalid menu location.' ), 561 array( 562 'location' => $location, 563 ) 564 ); 565 } 566 } 567 568 return true; 569 }, 570 ), 571 ); 572 573 $schema['properties']['auto_add'] = array( 574 'description' => __( 'Whether to automatically add top level pages to this menu.' ), 575 'context' => array( 'view', 'edit' ), 576 'type' => 'boolean', 577 ); 578 579 $this->schema = $schema; 580 581 return $this->add_additional_fields_schema( $this->schema ); 582 } 583 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Fri Feb 21 08:20:01 2025 | Cross-referenced by PHPXref |