[ 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          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  }


Generated : Sat Dec 21 08:20:01 2024 Cross-referenced by PHPXref