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