[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

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


Generated : Thu Nov 21 08:20:01 2024 Cross-referenced by PHPXref