[ Index ]

PHP Cross Reference of WordPress

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


Generated: Tue Dec 3 01:00:02 2024 Cross-referenced by PHPXref 0.7.1