[ Index ]

PHP Cross Reference of WordPress

title

Body

[close]

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

   1  <?php
   2  /**
   3   * REST API: WP_REST_Menu_Items_Controller class
   4   *
   5   * @package WordPress
   6   * @subpackage REST_API
   7   * @since 5.9.0
   8   */
   9  
  10  /**
  11   * Core class to access nav items via the REST API.
  12   *
  13   * @since 5.9.0
  14   *
  15   * @see WP_REST_Posts_Controller
  16   */
  17  class WP_REST_Menu_Items_Controller extends WP_REST_Posts_Controller {
  18  
  19      /**
  20       * Get the nav menu item, if the ID is valid.
  21       *
  22       * @since 5.9.0
  23       *
  24       * @param int $id Supplied ID.
  25       * @return object|WP_Error Post object if ID is valid, WP_Error otherwise.
  26       */
  27  	protected function get_nav_menu_item( $id ) {
  28          $post = $this->get_post( $id );
  29          if ( is_wp_error( $post ) ) {
  30              return $post;
  31          }
  32  
  33          return wp_setup_nav_menu_item( $post );
  34      }
  35  
  36      /**
  37       * Checks if a given request has access to read menu items.
  38       *
  39       * @since 5.9.0
  40       *
  41       * @param WP_REST_Request $request Full details about the request.
  42       * @return true|WP_Error True if the request has read access, WP_Error object otherwise.
  43       */
  44  	public function get_items_permissions_check( $request ) {
  45          $has_permission = parent::get_items_permissions_check( $request );
  46  
  47          if ( true !== $has_permission ) {
  48              return $has_permission;
  49          }
  50  
  51          return $this->check_has_read_only_access( $request );
  52      }
  53  
  54      /**
  55       * Checks if a given request has access to read a menu item if they have access to edit them.
  56       *
  57       * @since 5.9.0
  58       *
  59       * @param WP_REST_Request $request Full details about the request.
  60       * @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise.
  61       */
  62  	public function get_item_permissions_check( $request ) {
  63          $permission_check = parent::get_item_permissions_check( $request );
  64  
  65          if ( true !== $permission_check ) {
  66              return $permission_check;
  67          }
  68  
  69          return $this->check_has_read_only_access( $request );
  70      }
  71  
  72      /**
  73       * Checks whether the current user has read permission for the endpoint.
  74       *
  75       * This allows for any user that can `edit_theme_options` or edit any REST API available post type.
  76       *
  77       * @since 5.9.0
  78       *
  79       * @param WP_REST_Request $request Full details about the request.
  80       * @return bool|WP_Error Whether the current user has permission.
  81       */
  82  	protected function check_has_read_only_access( $request ) {
  83          if ( current_user_can( 'edit_theme_options' ) ) {
  84              return true;
  85          }
  86  
  87          if ( current_user_can( 'edit_posts' ) ) {
  88              return true;
  89          }
  90  
  91          foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
  92              if ( current_user_can( $post_type->cap->edit_posts ) ) {
  93                  return true;
  94              }
  95          }
  96  
  97          return new WP_Error(
  98              'rest_cannot_view',
  99              __( 'Sorry, you are not allowed to view menu items.' ),
 100              array( 'status' => rest_authorization_required_code() )
 101          );
 102      }
 103  
 104      /**
 105       * Creates a single post.
 106       *
 107       * @since 5.9.0
 108       *
 109       * @param WP_REST_Request $request Full details about the request.
 110       *
 111       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 112       */
 113  	public function create_item( $request ) {
 114          if ( ! empty( $request['id'] ) ) {
 115              return new WP_Error( 'rest_post_exists', __( 'Cannot create existing post.' ), array( 'status' => 400 ) );
 116          }
 117  
 118          $prepared_nav_item = $this->prepare_item_for_database( $request );
 119  
 120          if ( is_wp_error( $prepared_nav_item ) ) {
 121              return $prepared_nav_item;
 122          }
 123          $prepared_nav_item = (array) $prepared_nav_item;
 124  
 125          $nav_menu_item_id = wp_update_nav_menu_item( $prepared_nav_item['menu-id'], $prepared_nav_item['menu-item-db-id'], wp_slash( $prepared_nav_item ), false );
 126          if ( is_wp_error( $nav_menu_item_id ) ) {
 127              if ( 'db_insert_error' === $nav_menu_item_id->get_error_code() ) {
 128                  $nav_menu_item_id->add_data( array( 'status' => 500 ) );
 129              } else {
 130                  $nav_menu_item_id->add_data( array( 'status' => 400 ) );
 131              }
 132  
 133              return $nav_menu_item_id;
 134          }
 135  
 136          $nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id );
 137          if ( is_wp_error( $nav_menu_item ) ) {
 138              $nav_menu_item->add_data( array( 'status' => 404 ) );
 139  
 140              return $nav_menu_item;
 141          }
 142  
 143          /**
 144           * Fires after a single menu item is created or updated via the REST API.
 145           *
 146           * @since 5.9.0
 147           *
 148           * @param object          $nav_menu_item Inserted or updated menu item object.
 149           * @param WP_REST_Request $request       Request object.
 150           * @param bool            $creating      True when creating a menu item, false when updating.
 151           */
 152          do_action( 'rest_insert_nav_menu_item', $nav_menu_item, $request, true );
 153  
 154          $schema = $this->get_item_schema();
 155  
 156          if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
 157              $meta_update = $this->meta->update_value( $request['meta'], $nav_menu_item_id );
 158  
 159              if ( is_wp_error( $meta_update ) ) {
 160                  return $meta_update;
 161              }
 162          }
 163  
 164          $nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id );
 165          $fields_update = $this->update_additional_fields_for_object( $nav_menu_item, $request );
 166  
 167          if ( is_wp_error( $fields_update ) ) {
 168              return $fields_update;
 169          }
 170  
 171          $request->set_param( 'context', 'edit' );
 172  
 173          /**
 174           * Fires after a single menu item is completely created or updated via the REST API.
 175           *
 176           * @since 5.9.0
 177           *
 178           * @param object          $nav_menu_item Inserted or updated menu item object.
 179           * @param WP_REST_Request $request       Request object.
 180           * @param bool            $creating      True when creating a menu item, false when updating.
 181           */
 182          do_action( 'rest_after_insert_nav_menu_item', $nav_menu_item, $request, true );
 183  
 184          $post = get_post( $nav_menu_item_id );
 185          wp_after_insert_post( $post, false, null );
 186  
 187          $response = $this->prepare_item_for_response( $post, $request );
 188          $response = rest_ensure_response( $response );
 189  
 190          $response->set_status( 201 );
 191          $response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $nav_menu_item_id ) ) );
 192  
 193          return $response;
 194      }
 195  
 196      /**
 197       * Updates a single nav menu item.
 198       *
 199       * @since 5.9.0
 200       *
 201       * @param WP_REST_Request $request Full details about the request.
 202       *
 203       * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
 204       */
 205  	public function update_item( $request ) {
 206          $valid_check = $this->get_nav_menu_item( $request['id'] );
 207          if ( is_wp_error( $valid_check ) ) {
 208              return $valid_check;
 209          }
 210          $post_before       = get_post( $request['id'] );
 211          $prepared_nav_item = $this->prepare_item_for_database( $request );
 212  
 213          if ( is_wp_error( $prepared_nav_item ) ) {
 214              return $prepared_nav_item;
 215          }
 216  
 217          $prepared_nav_item = (array) $prepared_nav_item;
 218  
 219          $nav_menu_item_id = wp_update_nav_menu_item( $prepared_nav_item['menu-id'], $prepared_nav_item['menu-item-db-id'], wp_slash( $prepared_nav_item ), false );
 220  
 221          if ( is_wp_error( $nav_menu_item_id ) ) {
 222              if ( 'db_update_error' === $nav_menu_item_id->get_error_code() ) {
 223                  $nav_menu_item_id->add_data( array( 'status' => 500 ) );
 224              } else {
 225                  $nav_menu_item_id->add_data( array( 'status' => 400 ) );
 226              }
 227  
 228              return $nav_menu_item_id;
 229          }
 230  
 231          $nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id );
 232          if ( is_wp_error( $nav_menu_item ) ) {
 233              $nav_menu_item->add_data( array( 'status' => 404 ) );
 234  
 235              return $nav_menu_item;
 236          }
 237  
 238          /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */
 239          do_action( 'rest_insert_nav_menu_item', $nav_menu_item, $request, false );
 240  
 241          $schema = $this->get_item_schema();
 242  
 243          if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
 244              $meta_update = $this->meta->update_value( $request['meta'], $nav_menu_item->ID );
 245  
 246              if ( is_wp_error( $meta_update ) ) {
 247                  return $meta_update;
 248              }
 249          }
 250  
 251          $post          = get_post( $nav_menu_item_id );
 252          $nav_menu_item = $this->get_nav_menu_item( $nav_menu_item_id );
 253          $fields_update = $this->update_additional_fields_for_object( $nav_menu_item, $request );
 254  
 255          if ( is_wp_error( $fields_update ) ) {
 256              return $fields_update;
 257          }
 258  
 259          $request->set_param( 'context', 'edit' );
 260  
 261          /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-menu-items-controller.php */
 262          do_action( 'rest_after_insert_nav_menu_item', $nav_menu_item, $request, false );
 263  
 264          wp_after_insert_post( $post, true, $post_before );
 265  
 266          $response = $this->prepare_item_for_response( get_post( $nav_menu_item_id ), $request );
 267  
 268          return rest_ensure_response( $response );
 269      }
 270  
 271      /**
 272       * Deletes a single menu item.
 273       *
 274       * @since 5.9.0
 275       *
 276       * @param WP_REST_Request $request Full details about the request.
 277       * @return WP_REST_Response|WP_Error True on success, or WP_Error object on failure.
 278       */
 279  	public function delete_item( $request ) {
 280          $menu_item = $this->get_nav_menu_item( $request['id'] );
 281          if ( is_wp_error( $menu_item ) ) {
 282              return $menu_item;
 283          }
 284  
 285          // We don't support trashing for menu items.
 286          if ( ! $request['force'] ) {
 287              /* translators: %s: force=true */
 288              return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Menu items do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) );
 289          }
 290  
 291          $previous = $this->prepare_item_for_response( get_post( $request['id'] ), $request );
 292  
 293          $result = wp_delete_post( $request['id'], true );
 294  
 295          if ( ! $result ) {
 296              return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
 297          }
 298  
 299          $response = new WP_REST_Response();
 300          $response->set_data(
 301              array(
 302                  'deleted'  => true,
 303                  'previous' => $previous->get_data(),
 304              )
 305          );
 306  
 307          /**
 308           * Fires immediately after a single menu item is deleted via the REST API.
 309           *
 310           * @since 5.9.0
 311           *
 312           * @param object          $nav_menu_item Inserted or updated menu item object.
 313           * @param WP_REST_Response $response The response data.
 314           * @param WP_REST_Request $request       Request object.
 315           */
 316          do_action( 'rest_delete_nav_menu_item', $menu_item, $response, $request );
 317  
 318          return $response;
 319      }
 320  
 321      /**
 322       * Prepares a single post for create or update.
 323       *
 324       * @since 5.9.0
 325       *
 326       * @param WP_REST_Request $request Request object.
 327       *
 328       * @return object|WP_Error
 329       */
 330  	protected function prepare_item_for_database( $request ) {
 331          $menu_item_db_id = $request['id'];
 332          $menu_item_obj   = $this->get_nav_menu_item( $menu_item_db_id );
 333          // Need to persist the menu item data. See https://core.trac.wordpress.org/ticket/28138
 334          if ( ! is_wp_error( $menu_item_obj ) ) {
 335              // Correct the menu position if this was the first item. See https://core.trac.wordpress.org/ticket/28140
 336              $position = ( 0 === $menu_item_obj->menu_order ) ? 1 : $menu_item_obj->menu_order;
 337  
 338              $prepared_nav_item = array(
 339                  'menu-item-db-id'       => $menu_item_db_id,
 340                  'menu-item-object-id'   => $menu_item_obj->object_id,
 341                  'menu-item-object'      => $menu_item_obj->object,
 342                  'menu-item-parent-id'   => $menu_item_obj->menu_item_parent,
 343                  'menu-item-position'    => $position,
 344                  'menu-item-type'        => $menu_item_obj->type,
 345                  'menu-item-title'       => $menu_item_obj->title,
 346                  'menu-item-url'         => $menu_item_obj->url,
 347                  'menu-item-description' => $menu_item_obj->description,
 348                  'menu-item-attr-title'  => $menu_item_obj->attr_title,
 349                  'menu-item-target'      => $menu_item_obj->target,
 350                  'menu-item-classes'     => $menu_item_obj->classes,
 351                  // Stored in the database as a string.
 352                  'menu-item-xfn'         => explode( ' ', $menu_item_obj->xfn ),
 353                  'menu-item-status'      => $menu_item_obj->post_status,
 354                  'menu-id'               => $this->get_menu_id( $menu_item_db_id ),
 355              );
 356          } else {
 357              $prepared_nav_item = array(
 358                  'menu-id'               => 0,
 359                  'menu-item-db-id'       => 0,
 360                  'menu-item-object-id'   => 0,
 361                  'menu-item-object'      => '',
 362                  'menu-item-parent-id'   => 0,
 363                  'menu-item-position'    => 1,
 364                  'menu-item-type'        => 'custom',
 365                  'menu-item-title'       => '',
 366                  'menu-item-url'         => '',
 367                  'menu-item-description' => '',
 368                  'menu-item-attr-title'  => '',
 369                  'menu-item-target'      => '',
 370                  'menu-item-classes'     => array(),
 371                  'menu-item-xfn'         => array(),
 372                  'menu-item-status'      => 'publish',
 373              );
 374          }
 375  
 376          $mapping = array(
 377              'menu-item-db-id'       => 'id',
 378              'menu-item-object-id'   => 'object_id',
 379              'menu-item-object'      => 'object',
 380              'menu-item-parent-id'   => 'parent',
 381              'menu-item-position'    => 'menu_order',
 382              'menu-item-type'        => 'type',
 383              'menu-item-url'         => 'url',
 384              'menu-item-description' => 'description',
 385              'menu-item-attr-title'  => 'attr_title',
 386              'menu-item-target'      => 'target',
 387              'menu-item-classes'     => 'classes',
 388              'menu-item-xfn'         => 'xfn',
 389              'menu-item-status'      => 'status',
 390          );
 391  
 392          $schema = $this->get_item_schema();
 393  
 394          foreach ( $mapping as $original => $api_request ) {
 395              if ( isset( $request[ $api_request ] ) ) {
 396                  $prepared_nav_item[ $original ] = $request[ $api_request ];
 397              }
 398          }
 399  
 400          $taxonomy = get_taxonomy( 'nav_menu' );
 401          $base     = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
 402          // If menus submitted, cast to int.
 403          if ( ! empty( $request[ $base ] ) ) {
 404              $prepared_nav_item['menu-id'] = absint( $request[ $base ] );
 405          }
 406  
 407          // Nav menu title.
 408          if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) {
 409              if ( is_string( $request['title'] ) ) {
 410                  $prepared_nav_item['menu-item-title'] = $request['title'];
 411              } elseif ( ! empty( $request['title']['raw'] ) ) {
 412                  $prepared_nav_item['menu-item-title'] = $request['title']['raw'];
 413              }
 414          }
 415  
 416          $error = new WP_Error();
 417  
 418          // Check if object id exists before saving.
 419          if ( ! $prepared_nav_item['menu-item-object'] ) {
 420              // If taxonomy, check if term exists.
 421              if ( 'taxonomy' === $prepared_nav_item['menu-item-type'] ) {
 422                  $original = get_term( absint( $prepared_nav_item['menu-item-object-id'] ) );
 423                  if ( empty( $original ) || is_wp_error( $original ) ) {
 424                      $error->add( 'rest_term_invalid_id', __( 'Invalid term ID.' ), array( 'status' => 400 ) );
 425                  } else {
 426                      $prepared_nav_item['menu-item-object'] = get_term_field( 'taxonomy', $original );
 427                  }
 428                  // If post, check if post object exists.
 429              } elseif ( 'post_type' === $prepared_nav_item['menu-item-type'] ) {
 430                  $original = get_post( absint( $prepared_nav_item['menu-item-object-id'] ) );
 431                  if ( empty( $original ) ) {
 432                      $error->add( 'rest_post_invalid_id', __( 'Invalid post ID.' ), array( 'status' => 400 ) );
 433                  } else {
 434                      $prepared_nav_item['menu-item-object'] = get_post_type( $original );
 435                  }
 436              }
 437          }
 438  
 439          // If post type archive, check if post type exists.
 440          if ( 'post_type_archive' === $prepared_nav_item['menu-item-type'] ) {
 441              $post_type = $prepared_nav_item['menu-item-object'] ? $prepared_nav_item['menu-item-object'] : false;
 442              $original  = get_post_type_object( $post_type );
 443              if ( ! $original ) {
 444                  $error->add( 'rest_post_invalid_type', __( 'Invalid post type.' ), array( 'status' => 400 ) );
 445              }
 446          }
 447  
 448          // Check if menu item is type custom, then title and url are required.
 449          if ( 'custom' === $prepared_nav_item['menu-item-type'] ) {
 450              if ( '' === $prepared_nav_item['menu-item-title'] ) {
 451                  $error->add( 'rest_title_required', __( 'The title is required when using a custom menu item type.' ), array( 'status' => 400 ) );
 452              }
 453              if ( empty( $prepared_nav_item['menu-item-url'] ) ) {
 454                  $error->add( 'rest_url_required', __( 'The url is required when using a custom menu item type.' ), array( 'status' => 400 ) );
 455              }
 456          }
 457  
 458          if ( $error->has_errors() ) {
 459              return $error;
 460          }
 461  
 462          // The xfn and classes properties are arrays, but passed to wp_update_nav_menu_item as a string.
 463          foreach ( array( 'menu-item-xfn', 'menu-item-classes' ) as $key ) {
 464              $prepared_nav_item[ $key ] = implode( ' ', $prepared_nav_item[ $key ] );
 465          }
 466  
 467          // Only draft / publish are valid post status for menu items.
 468          if ( 'publish' !== $prepared_nav_item['menu-item-status'] ) {
 469              $prepared_nav_item['menu-item-status'] = 'draft';
 470          }
 471  
 472          $prepared_nav_item = (object) $prepared_nav_item;
 473  
 474          /**
 475           * Filters a menu item before it is inserted via the REST API.
 476           *
 477           * @since 5.9.0
 478           *
 479           * @param object          $prepared_nav_item An object representing a single menu item prepared
 480           *                                           for inserting or updating the database.
 481           * @param WP_REST_Request $request           Request object.
 482           */
 483          return apply_filters( 'rest_pre_insert_nav_menu_item', $prepared_nav_item, $request );
 484      }
 485  
 486      /**
 487       * Prepares a single post output for response.
 488       *
 489       * @since 5.9.0
 490       *
 491       * @param WP_Post          $item   Post object.
 492       * @param WP_REST_Request $request Request object.
 493       * @return WP_REST_Response Response object.
 494       */
 495  	public function prepare_item_for_response( $item, $request ) {
 496          // Base fields for every post.
 497          $fields    = $this->get_fields_for_response( $request );
 498          $menu_item = $this->get_nav_menu_item( $item->ID );
 499          $data      = array();
 500  
 501          if ( rest_is_field_included( 'id', $fields ) ) {
 502              $data['id'] = $menu_item->ID;
 503          }
 504  
 505          if ( rest_is_field_included( 'title', $fields ) ) {
 506              $data['title'] = array();
 507          }
 508  
 509          if ( rest_is_field_included( 'title.raw', $fields ) ) {
 510              $data['title']['raw'] = $menu_item->title;
 511          }
 512  
 513          if ( rest_is_field_included( 'title.rendered', $fields ) ) {
 514              add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
 515  
 516              /** This filter is documented in wp-includes/post-template.php */
 517              $title = apply_filters( 'the_title', $menu_item->title, $menu_item->ID );
 518  
 519              $data['title']['rendered'] = $title;
 520  
 521              remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
 522          }
 523  
 524          if ( rest_is_field_included( 'status', $fields ) ) {
 525              $data['status'] = $menu_item->post_status;
 526          }
 527  
 528          if ( rest_is_field_included( 'url', $fields ) ) {
 529              $data['url'] = $menu_item->url;
 530          }
 531  
 532          if ( rest_is_field_included( 'attr_title', $fields ) ) {
 533              // Same as post_excerpt.
 534              $data['attr_title'] = $menu_item->attr_title;
 535          }
 536  
 537          if ( rest_is_field_included( 'description', $fields ) ) {
 538              // Same as post_content.
 539              $data['description'] = $menu_item->description;
 540          }
 541  
 542          if ( rest_is_field_included( 'type', $fields ) ) {
 543              $data['type'] = $menu_item->type;
 544          }
 545  
 546          if ( rest_is_field_included( 'type_label', $fields ) ) {
 547              $data['type_label'] = $menu_item->type_label;
 548          }
 549  
 550          if ( rest_is_field_included( 'object', $fields ) ) {
 551              $data['object'] = $menu_item->object;
 552          }
 553  
 554          if ( rest_is_field_included( 'object_id', $fields ) ) {
 555              // It is stored as a string, but should be exposed as an integer.
 556              $data['object_id'] = absint( $menu_item->object_id );
 557          }
 558  
 559          if ( rest_is_field_included( 'parent', $fields ) ) {
 560              // Same as post_parent, exposed as an integer.
 561              $data['parent'] = (int) $menu_item->menu_item_parent;
 562          }
 563  
 564          if ( rest_is_field_included( 'menu_order', $fields ) ) {
 565              // Same as post_parent, exposed as an integer.
 566              $data['menu_order'] = (int) $menu_item->menu_order;
 567          }
 568  
 569          if ( rest_is_field_included( 'target', $fields ) ) {
 570              $data['target'] = $menu_item->target;
 571          }
 572  
 573          if ( rest_is_field_included( 'classes', $fields ) ) {
 574              $data['classes'] = (array) $menu_item->classes;
 575          }
 576  
 577          if ( rest_is_field_included( 'xfn', $fields ) ) {
 578              $data['xfn'] = array_map( 'sanitize_html_class', explode( ' ', $menu_item->xfn ) );
 579          }
 580  
 581          if ( rest_is_field_included( 'invalid', $fields ) ) {
 582              $data['invalid'] = (bool) $menu_item->_invalid;
 583          }
 584  
 585          if ( rest_is_field_included( 'meta', $fields ) ) {
 586              $data['meta'] = $this->meta->get_value( $menu_item->ID, $request );
 587          }
 588  
 589          $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
 590  
 591          foreach ( $taxonomies as $taxonomy ) {
 592              $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
 593  
 594              if ( rest_is_field_included( $base, $fields ) ) {
 595                  $terms = get_the_terms( $item, $taxonomy->name );
 596                  if ( ! is_array( $terms ) ) {
 597                      continue;
 598                  }
 599                  $term_ids = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array();
 600                  if ( 'nav_menu' === $taxonomy->name ) {
 601                      $data[ $base ] = $term_ids ? array_shift( $term_ids ) : 0;
 602                  } else {
 603                      $data[ $base ] = $term_ids;
 604                  }
 605              }
 606          }
 607  
 608          $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 609          $data    = $this->add_additional_fields_to_object( $data, $request );
 610          $data    = $this->filter_response_by_context( $data, $context );
 611  
 612          // Wrap the data in a response object.
 613          $response = rest_ensure_response( $data );
 614  
 615          $links = $this->prepare_links( $item );
 616          $response->add_links( $links );
 617  
 618          if ( ! empty( $links['self']['href'] ) ) {
 619              $actions = $this->get_available_actions( $item, $request );
 620  
 621              $self = $links['self']['href'];
 622  
 623              foreach ( $actions as $rel ) {
 624                  $response->add_link( $rel, $self );
 625              }
 626          }
 627  
 628          /**
 629           * Filters the menu item data for a REST API response.
 630           *
 631           * @since 5.9.0
 632           *
 633           * @param WP_REST_Response $response  The response object.
 634           * @param object           $menu_item Menu item setup by {@see wp_setup_nav_menu_item()}.
 635           * @param WP_REST_Request  $request   Request object.
 636           */
 637          return apply_filters( 'rest_prepare_nav_menu_item', $response, $menu_item, $request );
 638      }
 639  
 640      /**
 641       * Prepares links for the request.
 642       *
 643       * @since 5.9.0
 644       *
 645       * @param WP_Post $post Post object.
 646       * @return array Links for the given post.
 647       */
 648  	protected function prepare_links( $post ) {
 649          $links     = parent::prepare_links( $post );
 650          $menu_item = $this->get_nav_menu_item( $post->ID );
 651  
 652          if ( empty( $menu_item->object_id ) ) {
 653              return $links;
 654          }
 655  
 656          $path = '';
 657          $type = '';
 658          $key  = $menu_item->type;
 659          if ( 'post_type' === $menu_item->type ) {
 660              $path = rest_get_route_for_post( $menu_item->object_id );
 661              $type = get_post_type( $menu_item->object_id );
 662          } elseif ( 'taxonomy' === $menu_item->type ) {
 663              $path = rest_get_route_for_term( $menu_item->object_id );
 664              $type = get_term_field( 'taxonomy', $menu_item->object_id );
 665          }
 666  
 667          if ( $path && $type ) {
 668              $links['https://api.w.org/menu-item-object'][] = array(
 669                  'href'       => rest_url( $path ),
 670                  $key         => $type,
 671                  'embeddable' => true,
 672              );
 673          }
 674  
 675          return $links;
 676      }
 677  
 678      /**
 679       * Retrieve Link Description Objects that should be added to the Schema for the posts collection.
 680       *
 681       * @since 5.9.0
 682       *
 683       * @return array
 684       */
 685  	protected function get_schema_links() {
 686          $links   = parent::get_schema_links();
 687          $href    = rest_url( "{$this->namespace}/{$this->rest_base}/{id}" );
 688          $links[] = array(
 689              'rel'          => 'https://api.w.org/menu-item-object',
 690              'title'        => __( 'Get linked object.' ),
 691              'href'         => $href,
 692              'targetSchema' => array(
 693                  'type'       => 'object',
 694                  'properties' => array(
 695                      'object' => array(
 696                          'type' => 'integer',
 697                      ),
 698                  ),
 699              ),
 700          );
 701  
 702          return $links;
 703      }
 704  
 705      /**
 706       * Retrieves the term's schema, conforming to JSON Schema.
 707       *
 708       * @since 5.9.0
 709       *
 710       * @return array Item schema data.
 711       */
 712  	public function get_item_schema() {
 713          $schema = array(
 714              '$schema' => 'http://json-schema.org/draft-04/schema#',
 715              'title'   => $this->post_type,
 716              'type'    => 'object',
 717          );
 718  
 719          $schema['properties']['title'] = array(
 720              'description' => __( 'The title for the object.' ),
 721              'type'        => array( 'string', 'object' ),
 722              'context'     => array( 'view', 'edit', 'embed' ),
 723              'properties'  => array(
 724                  'raw'      => array(
 725                      'description' => __( 'Title for the object, as it exists in the database.' ),
 726                      'type'        => 'string',
 727                      'context'     => array( 'edit' ),
 728                  ),
 729                  'rendered' => array(
 730                      'description' => __( 'HTML title for the object, transformed for display.' ),
 731                      'type'        => 'string',
 732                      'context'     => array( 'view', 'edit', 'embed' ),
 733                      'readonly'    => true,
 734                  ),
 735              ),
 736          );
 737  
 738          $schema['properties']['id'] = array(
 739              'description' => __( 'Unique identifier for the object.' ),
 740              'type'        => 'integer',
 741              'default'     => 0,
 742              'minimum'     => 0,
 743              'context'     => array( 'view', 'edit', 'embed' ),
 744              'readonly'    => true,
 745          );
 746  
 747          $schema['properties']['type_label'] = array(
 748              'description' => __( 'Name of type.' ),
 749              'type'        => 'string',
 750              'context'     => array( 'view', 'edit', 'embed' ),
 751              'readonly'    => true,
 752          );
 753  
 754          $schema['properties']['type'] = array(
 755              'description' => __( 'The family of objects originally represented, such as "post_type" or "taxonomy".' ),
 756              'type'        => 'string',
 757              'enum'        => array( 'taxonomy', 'post_type', 'post_type_archive', 'custom' ),
 758              'context'     => array( 'view', 'edit', 'embed' ),
 759              'default'     => 'custom',
 760          );
 761  
 762          $schema['properties']['status'] = array(
 763              'description' => __( 'A named status for the object.' ),
 764              'type'        => 'string',
 765              'enum'        => array_keys( get_post_stati( array( 'internal' => false ) ) ),
 766              'default'     => 'publish',
 767              'context'     => array( 'view', 'edit', 'embed' ),
 768          );
 769  
 770          $schema['properties']['parent'] = array(
 771              'description' => __( 'The ID for the parent of the object.' ),
 772              'type'        => 'integer',
 773              'minimum'     => 0,
 774              'default'     => 0,
 775              'context'     => array( 'view', 'edit', 'embed' ),
 776          );
 777  
 778          $schema['properties']['attr_title'] = array(
 779              'description' => __( 'Text for the title attribute of the link element for this menu item.' ),
 780              'type'        => 'string',
 781              'context'     => array( 'view', 'edit', 'embed' ),
 782              'arg_options' => array(
 783                  'sanitize_callback' => 'sanitize_text_field',
 784              ),
 785          );
 786  
 787          $schema['properties']['classes'] = array(
 788              'description' => __( 'Class names for the link element of this menu item.' ),
 789              'type'        => 'array',
 790              'items'       => array(
 791                  'type' => 'string',
 792              ),
 793              'context'     => array( 'view', 'edit', 'embed' ),
 794              'arg_options' => array(
 795                  'sanitize_callback' => function ( $value ) {
 796                      return array_map( 'sanitize_html_class', wp_parse_list( $value ) );
 797                  },
 798              ),
 799          );
 800  
 801          $schema['properties']['description'] = array(
 802              'description' => __( 'The description of this menu item.' ),
 803              'type'        => 'string',
 804              'context'     => array( 'view', 'edit', 'embed' ),
 805              'arg_options' => array(
 806                  'sanitize_callback' => 'sanitize_text_field',
 807              ),
 808          );
 809  
 810          $schema['properties']['menu_order'] = array(
 811              'description' => __( 'The DB ID of the nav_menu_item that is this item\'s menu parent, if any, otherwise 0.' ),
 812              'context'     => array( 'view', 'edit', 'embed' ),
 813              'type'        => 'integer',
 814              'minimum'     => 1,
 815              'default'     => 1,
 816          );
 817  
 818          $schema['properties']['object'] = array(
 819              'description' => __( 'The type of object originally represented, such as "category", "post", or "attachment".' ),
 820              'context'     => array( 'view', 'edit', 'embed' ),
 821              'type'        => 'string',
 822              'arg_options' => array(
 823                  'sanitize_callback' => 'sanitize_key',
 824              ),
 825          );
 826  
 827          $schema['properties']['object_id'] = array(
 828              'description' => __( 'The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.' ),
 829              'context'     => array( 'view', 'edit', 'embed' ),
 830              'type'        => 'integer',
 831              'minimum'     => 0,
 832              'default'     => 0,
 833          );
 834  
 835          $schema['properties']['target'] = array(
 836              'description' => __( 'The target attribute of the link element for this menu item.' ),
 837              'type'        => 'string',
 838              'context'     => array( 'view', 'edit', 'embed' ),
 839              'enum'        => array(
 840                  '_blank',
 841                  '',
 842              ),
 843          );
 844  
 845          $schema['properties']['type_label'] = array(
 846              'description' => __( 'The singular label used to describe this type of menu item.' ),
 847              'context'     => array( 'view', 'edit', 'embed' ),
 848              'type'        => 'string',
 849              'readonly'    => true,
 850          );
 851  
 852          $schema['properties']['url'] = array(
 853              'description' => __( 'The URL to which this menu item points.' ),
 854              'type'        => 'string',
 855              'format'      => 'uri',
 856              'context'     => array( 'view', 'edit', 'embed' ),
 857              'arg_options' => array(
 858                  'validate_callback' => static function ( $url ) {
 859                      if ( '' === $url ) {
 860                          return true;
 861                      }
 862  
 863                      if ( esc_url_raw( $url ) ) {
 864                          return true;
 865                      }
 866  
 867                      return new WP_Error(
 868                          'rest_invalid_url',
 869                          __( 'Invalid URL.' )
 870                      );
 871                  },
 872              ),
 873          );
 874  
 875          $schema['properties']['xfn'] = array(
 876              'description' => __( 'The XFN relationship expressed in the link of this menu item.' ),
 877              'type'        => 'array',
 878              'items'       => array(
 879                  'type' => 'string',
 880              ),
 881              'context'     => array( 'view', 'edit', 'embed' ),
 882              'arg_options' => array(
 883                  'sanitize_callback' => function ( $value ) {
 884                      return array_map( 'sanitize_html_class', wp_parse_list( $value ) );
 885                  },
 886              ),
 887          );
 888  
 889          $schema['properties']['invalid'] = array(
 890              'description' => __( 'Whether the menu item represents an object that no longer exists.' ),
 891              'context'     => array( 'view', 'edit', 'embed' ),
 892              'type'        => 'boolean',
 893              'readonly'    => true,
 894          );
 895  
 896          $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
 897  
 898          foreach ( $taxonomies as $taxonomy ) {
 899              $base                          = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
 900              $schema['properties'][ $base ] = array(
 901                  /* translators: %s: taxonomy name */
 902                  'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ),
 903                  'type'        => 'array',
 904                  'items'       => array(
 905                      'type' => 'integer',
 906                  ),
 907                  'context'     => array( 'view', 'edit' ),
 908              );
 909  
 910              if ( 'nav_menu' === $taxonomy->name ) {
 911                  $schema['properties'][ $base ]['type'] = 'integer';
 912                  unset( $schema['properties'][ $base ]['items'] );
 913              }
 914          }
 915  
 916          $schema['properties']['meta'] = $this->meta->get_field_schema();
 917  
 918          $schema_links = $this->get_schema_links();
 919  
 920          if ( $schema_links ) {
 921              $schema['links'] = $schema_links;
 922          }
 923  
 924          return $this->add_additional_fields_schema( $schema );
 925      }
 926  
 927      /**
 928       * Retrieves the query params for the posts collection.
 929       *
 930       * @since 5.9.0
 931       *
 932       * @return array Collection parameters.
 933       */
 934  	public function get_collection_params() {
 935          $query_params = parent::get_collection_params();
 936  
 937          $query_params['menu_order'] = array(
 938              'description' => __( 'Limit result set to posts with a specific menu_order value.' ),
 939              'type'        => 'integer',
 940          );
 941  
 942          $query_params['order'] = array(
 943              'description' => __( 'Order sort attribute ascending or descending.' ),
 944              'type'        => 'string',
 945              'default'     => 'asc',
 946              'enum'        => array( 'asc', 'desc' ),
 947          );
 948  
 949          $query_params['orderby'] = array(
 950              'description' => __( 'Sort collection by object attribute.' ),
 951              'type'        => 'string',
 952              'default'     => 'menu_order',
 953              'enum'        => array(
 954                  'author',
 955                  'date',
 956                  'id',
 957                  'include',
 958                  'modified',
 959                  'parent',
 960                  'relevance',
 961                  'slug',
 962                  'include_slugs',
 963                  'title',
 964                  'menu_order',
 965              ),
 966          );
 967          // Change default to 100 items.
 968          $query_params['per_page']['default'] = 100;
 969  
 970          return $query_params;
 971      }
 972  
 973      /**
 974       * Determines the allowed query_vars for a get_items() response and prepares
 975       * them for WP_Query.
 976       *
 977       * @since 5.9.0
 978       *
 979       * @param array           $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
 980       * @param WP_REST_Request $request       Optional. Full details about the request.
 981       * @return array Items query arguments.
 982       */
 983  	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
 984          $query_args = parent::prepare_items_query( $prepared_args, $request );
 985  
 986          // Map to proper WP_Query orderby param.
 987          if ( isset( $query_args['orderby'], $request['orderby'] ) ) {
 988              $orderby_mappings = array(
 989                  'id'            => 'ID',
 990                  'include'       => 'post__in',
 991                  'slug'          => 'post_name',
 992                  'include_slugs' => 'post_name__in',
 993                  'menu_order'    => 'menu_order',
 994              );
 995  
 996              if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
 997                  $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
 998              }
 999          }
1000  
1001          return $query_args;
1002      }
1003  
1004      /**
1005       * Gets the id of the menu that the given menu item belongs to.
1006       *
1007       * @since 5.9.0
1008       *
1009       * @param int $menu_item_id Menu item id.
1010       * @return int
1011       */
1012  	protected function get_menu_id( $menu_item_id ) {
1013          $menu_ids = wp_get_post_terms( $menu_item_id, 'nav_menu', array( 'fields' => 'ids' ) );
1014          $menu_id  = 0;
1015          if ( $menu_ids && ! is_wp_error( $menu_ids ) ) {
1016              $menu_id = array_shift( $menu_ids );
1017          }
1018  
1019          return $menu_id;
1020      }
1021  }


Generated: Thu Nov 21 01:00:03 2024 Cross-referenced by PHPXref 0.7.1