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