[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/rest-api/endpoints/ -> class-wp-rest-menus-controller.php (source)

   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  }


Generated : Fri Feb 21 08:20:01 2025 Cross-referenced by PHPXref