[ 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 post.
 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 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 post 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 post 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  
 514              /** This filter is documented in wp-includes/post-template.php */
 515              $title = apply_filters( 'the_title', $menu_item->title, $menu_item->ID );
 516  
 517              $data['title']['rendered'] = $title;
 518  
 519              remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
 520          }
 521  
 522          if ( rest_is_field_included( 'status', $fields ) ) {
 523              $data['status'] = $menu_item->post_status;
 524          }
 525  
 526          if ( rest_is_field_included( 'url', $fields ) ) {
 527              $data['url'] = $menu_item->url;
 528          }
 529  
 530          if ( rest_is_field_included( 'attr_title', $fields ) ) {
 531              // Same as post_excerpt.
 532              $data['attr_title'] = $menu_item->attr_title;
 533          }
 534  
 535          if ( rest_is_field_included( 'description', $fields ) ) {
 536              // Same as post_content.
 537              $data['description'] = $menu_item->description;
 538          }
 539  
 540          if ( rest_is_field_included( 'type', $fields ) ) {
 541              $data['type'] = $menu_item->type;
 542          }
 543  
 544          if ( rest_is_field_included( 'type_label', $fields ) ) {
 545              $data['type_label'] = $menu_item->type_label;
 546          }
 547  
 548          if ( rest_is_field_included( 'object', $fields ) ) {
 549              $data['object'] = $menu_item->object;
 550          }
 551  
 552          if ( rest_is_field_included( 'object_id', $fields ) ) {
 553              // It is stored as a string, but should be exposed as an integer.
 554              $data['object_id'] = absint( $menu_item->object_id );
 555          }
 556  
 557          if ( rest_is_field_included( 'parent', $fields ) ) {
 558              // Same as post_parent, exposed as an integer.
 559              $data['parent'] = (int) $menu_item->menu_item_parent;
 560          }
 561  
 562          if ( rest_is_field_included( 'menu_order', $fields ) ) {
 563              // Same as post_parent, exposed as an integer.
 564              $data['menu_order'] = (int) $menu_item->menu_order;
 565          }
 566  
 567          if ( rest_is_field_included( 'target', $fields ) ) {
 568              $data['target'] = $menu_item->target;
 569          }
 570  
 571          if ( rest_is_field_included( 'classes', $fields ) ) {
 572              $data['classes'] = (array) $menu_item->classes;
 573          }
 574  
 575          if ( rest_is_field_included( 'xfn', $fields ) ) {
 576              $data['xfn'] = array_map( 'sanitize_html_class', explode( ' ', $menu_item->xfn ) );
 577          }
 578  
 579          if ( rest_is_field_included( 'invalid', $fields ) ) {
 580              $data['invalid'] = (bool) $menu_item->_invalid;
 581          }
 582  
 583          if ( rest_is_field_included( 'meta', $fields ) ) {
 584              $data['meta'] = $this->meta->get_value( $menu_item->ID, $request );
 585          }
 586  
 587          $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
 588  
 589          foreach ( $taxonomies as $taxonomy ) {
 590              $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
 591  
 592              if ( rest_is_field_included( $base, $fields ) ) {
 593                  $terms = get_the_terms( $item, $taxonomy->name );
 594                  if ( ! is_array( $terms ) ) {
 595                      continue;
 596                  }
 597                  $term_ids = $terms ? array_values( wp_list_pluck( $terms, 'term_id' ) ) : array();
 598                  if ( 'nav_menu' === $taxonomy->name ) {
 599                      $data[ $base ] = $term_ids ? array_shift( $term_ids ) : 0;
 600                  } else {
 601                      $data[ $base ] = $term_ids;
 602                  }
 603              }
 604          }
 605  
 606          $context = ! empty( $request['context'] ) ? $request['context'] : 'view';
 607          $data    = $this->add_additional_fields_to_object( $data, $request );
 608          $data    = $this->filter_response_by_context( $data, $context );
 609  
 610          // Wrap the data in a response object.
 611          $response = rest_ensure_response( $data );
 612  
 613          if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
 614              $links = $this->prepare_links( $item );
 615              $response->add_links( $links );
 616  
 617              if ( ! empty( $links['self']['href'] ) ) {
 618                  $actions = $this->get_available_actions( $item, $request );
 619  
 620                  $self = $links['self']['href'];
 621  
 622                  foreach ( $actions as $rel ) {
 623                      $response->add_link( $rel, $self );
 624                  }
 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       * Retrieves 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          if ( $this->schema ) {
 714              return $this->add_additional_fields_schema( $this->schema );
 715          }
 716  
 717          $schema = array(
 718              '$schema' => 'http://json-schema.org/draft-04/schema#',
 719              'title'   => $this->post_type,
 720              'type'    => 'object',
 721          );
 722  
 723          $schema['properties']['title'] = array(
 724              'description' => __( 'The title for the object.' ),
 725              'type'        => array( 'string', 'object' ),
 726              'context'     => array( 'view', 'edit', 'embed' ),
 727              'properties'  => array(
 728                  'raw'      => array(
 729                      'description' => __( 'Title for the object, as it exists in the database.' ),
 730                      'type'        => 'string',
 731                      'context'     => array( 'edit' ),
 732                  ),
 733                  'rendered' => array(
 734                      'description' => __( 'HTML title for the object, transformed for display.' ),
 735                      'type'        => 'string',
 736                      'context'     => array( 'view', 'edit', 'embed' ),
 737                      'readonly'    => true,
 738                  ),
 739              ),
 740          );
 741  
 742          $schema['properties']['id'] = array(
 743              'description' => __( 'Unique identifier for the object.' ),
 744              'type'        => 'integer',
 745              'default'     => 0,
 746              'minimum'     => 0,
 747              'context'     => array( 'view', 'edit', 'embed' ),
 748              'readonly'    => true,
 749          );
 750  
 751          $schema['properties']['type_label'] = array(
 752              'description' => __( 'The singular label used to describe this type of menu item.' ),
 753              'type'        => 'string',
 754              'context'     => array( 'view', 'edit', 'embed' ),
 755              'readonly'    => true,
 756          );
 757  
 758          $schema['properties']['type'] = array(
 759              'description' => __( 'The family of objects originally represented, such as "post_type" or "taxonomy".' ),
 760              'type'        => 'string',
 761              'enum'        => array( 'taxonomy', 'post_type', 'post_type_archive', 'custom' ),
 762              'context'     => array( 'view', 'edit', 'embed' ),
 763              'default'     => 'custom',
 764          );
 765  
 766          $schema['properties']['status'] = array(
 767              'description' => __( 'A named status for the object.' ),
 768              'type'        => 'string',
 769              'enum'        => array_keys( get_post_stati( array( 'internal' => false ) ) ),
 770              'default'     => 'publish',
 771              'context'     => array( 'view', 'edit', 'embed' ),
 772          );
 773  
 774          $schema['properties']['parent'] = array(
 775              'description' => __( 'The ID for the parent of the object.' ),
 776              'type'        => 'integer',
 777              'minimum'     => 0,
 778              'default'     => 0,
 779              'context'     => array( 'view', 'edit', 'embed' ),
 780          );
 781  
 782          $schema['properties']['attr_title'] = array(
 783              'description' => __( 'Text for the title attribute of the link element for this menu item.' ),
 784              'type'        => 'string',
 785              'context'     => array( 'view', 'edit', 'embed' ),
 786              'arg_options' => array(
 787                  'sanitize_callback' => 'sanitize_text_field',
 788              ),
 789          );
 790  
 791          $schema['properties']['classes'] = array(
 792              'description' => __( 'Class names for the link element of this menu item.' ),
 793              'type'        => 'array',
 794              'items'       => array(
 795                  'type' => 'string',
 796              ),
 797              'context'     => array( 'view', 'edit', 'embed' ),
 798              'arg_options' => array(
 799                  'sanitize_callback' => static function ( $value ) {
 800                      return array_map( 'sanitize_html_class', wp_parse_list( $value ) );
 801                  },
 802              ),
 803          );
 804  
 805          $schema['properties']['description'] = array(
 806              'description' => __( 'The description of this menu item.' ),
 807              'type'        => 'string',
 808              'context'     => array( 'view', 'edit', 'embed' ),
 809              'arg_options' => array(
 810                  'sanitize_callback' => 'sanitize_text_field',
 811              ),
 812          );
 813  
 814          $schema['properties']['menu_order'] = array(
 815              'description' => __( 'The DB ID of the nav_menu_item that is this item\'s menu parent, if any, otherwise 0.' ),
 816              'context'     => array( 'view', 'edit', 'embed' ),
 817              'type'        => 'integer',
 818              'minimum'     => 1,
 819              'default'     => 1,
 820          );
 821  
 822          $schema['properties']['object'] = array(
 823              'description' => __( 'The type of object originally represented, such as "category", "post", or "attachment".' ),
 824              'context'     => array( 'view', 'edit', 'embed' ),
 825              'type'        => 'string',
 826              'arg_options' => array(
 827                  'sanitize_callback' => 'sanitize_key',
 828              ),
 829          );
 830  
 831          $schema['properties']['object_id'] = array(
 832              'description' => __( 'The database ID of the original object this menu item represents, for example the ID for posts or the term_id for categories.' ),
 833              'context'     => array( 'view', 'edit', 'embed' ),
 834              'type'        => 'integer',
 835              'minimum'     => 0,
 836              'default'     => 0,
 837          );
 838  
 839          $schema['properties']['target'] = array(
 840              'description' => __( 'The target attribute of the link element for this menu item.' ),
 841              'type'        => 'string',
 842              'context'     => array( 'view', 'edit', 'embed' ),
 843              'enum'        => array(
 844                  '_blank',
 845                  '',
 846              ),
 847          );
 848  
 849          $schema['properties']['url'] = array(
 850              'description' => __( 'The URL to which this menu item points.' ),
 851              'type'        => 'string',
 852              'format'      => 'uri',
 853              'context'     => array( 'view', 'edit', 'embed' ),
 854              'arg_options' => array(
 855                  'validate_callback' => static function ( $url ) {
 856                      if ( '' === $url ) {
 857                          return true;
 858                      }
 859  
 860                      if ( sanitize_url( $url ) ) {
 861                          return true;
 862                      }
 863  
 864                      return new WP_Error(
 865                          'rest_invalid_url',
 866                          __( 'Invalid URL.' )
 867                      );
 868                  },
 869              ),
 870          );
 871  
 872          $schema['properties']['xfn'] = array(
 873              'description' => __( 'The XFN relationship expressed in the link of this menu item.' ),
 874              'type'        => 'array',
 875              'items'       => array(
 876                  'type' => 'string',
 877              ),
 878              'context'     => array( 'view', 'edit', 'embed' ),
 879              'arg_options' => array(
 880                  'sanitize_callback' => static function ( $value ) {
 881                      return array_map( 'sanitize_html_class', wp_parse_list( $value ) );
 882                  },
 883              ),
 884          );
 885  
 886          $schema['properties']['invalid'] = array(
 887              'description' => __( 'Whether the menu item represents an object that no longer exists.' ),
 888              'context'     => array( 'view', 'edit', 'embed' ),
 889              'type'        => 'boolean',
 890              'readonly'    => true,
 891          );
 892  
 893          $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) );
 894  
 895          foreach ( $taxonomies as $taxonomy ) {
 896              $base                          = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
 897              $schema['properties'][ $base ] = array(
 898                  /* translators: %s: taxonomy name */
 899                  'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ),
 900                  'type'        => 'array',
 901                  'items'       => array(
 902                      'type' => 'integer',
 903                  ),
 904                  'context'     => array( 'view', 'edit' ),
 905              );
 906  
 907              if ( 'nav_menu' === $taxonomy->name ) {
 908                  $schema['properties'][ $base ]['type'] = 'integer';
 909                  unset( $schema['properties'][ $base ]['items'] );
 910              }
 911          }
 912  
 913          $schema['properties']['meta'] = $this->meta->get_field_schema();
 914  
 915          $schema_links = $this->get_schema_links();
 916  
 917          if ( $schema_links ) {
 918              $schema['links'] = $schema_links;
 919          }
 920  
 921          $this->schema = $schema;
 922  
 923          return $this->add_additional_fields_schema( $this->schema );
 924      }
 925  
 926      /**
 927       * Retrieves the query params for the posts collection.
 928       *
 929       * @since 5.9.0
 930       *
 931       * @return array Collection parameters.
 932       */
 933  	public function get_collection_params() {
 934          $query_params = parent::get_collection_params();
 935  
 936          $query_params['menu_order'] = array(
 937              'description' => __( 'Limit result set to posts with a specific menu_order value.' ),
 938              'type'        => 'integer',
 939          );
 940  
 941          $query_params['order'] = array(
 942              'description' => __( 'Order sort attribute ascending or descending.' ),
 943              'type'        => 'string',
 944              'default'     => 'asc',
 945              'enum'        => array( 'asc', 'desc' ),
 946          );
 947  
 948          $query_params['orderby'] = array(
 949              'description' => __( 'Sort collection by object attribute.' ),
 950              'type'        => 'string',
 951              'default'     => 'menu_order',
 952              'enum'        => array(
 953                  'author',
 954                  'date',
 955                  'id',
 956                  'include',
 957                  'modified',
 958                  'parent',
 959                  'relevance',
 960                  'slug',
 961                  'include_slugs',
 962                  'title',
 963                  'menu_order',
 964              ),
 965          );
 966          // Change default to 100 items.
 967          $query_params['per_page']['default'] = 100;
 968  
 969          return $query_params;
 970      }
 971  
 972      /**
 973       * Determines the allowed query_vars for a get_items() response and prepares
 974       * them for WP_Query.
 975       *
 976       * @since 5.9.0
 977       *
 978       * @param array           $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
 979       * @param WP_REST_Request $request       Optional. Full details about the request.
 980       * @return array Items query arguments.
 981       */
 982  	protected function prepare_items_query( $prepared_args = array(), $request = null ) {
 983          $query_args = parent::prepare_items_query( $prepared_args, $request );
 984  
 985          // Map to proper WP_Query orderby param.
 986          if ( isset( $query_args['orderby'], $request['orderby'] ) ) {
 987              $orderby_mappings = array(
 988                  'id'            => 'ID',
 989                  'include'       => 'post__in',
 990                  'slug'          => 'post_name',
 991                  'include_slugs' => 'post_name__in',
 992                  'menu_order'    => 'menu_order',
 993              );
 994  
 995              if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
 996                  $query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
 997              }
 998          }
 999  
1000          $query_args['update_menu_item_cache'] = true;
1001  
1002          return $query_args;
1003      }
1004  
1005      /**
1006       * Gets the id of the menu that the given menu item belongs to.
1007       *
1008       * @since 5.9.0
1009       *
1010       * @param int $menu_item_id Menu item id.
1011       * @return int
1012       */
1013  	protected function get_menu_id( $menu_item_id ) {
1014          $menu_ids = wp_get_post_terms( $menu_item_id, 'nav_menu', array( 'fields' => 'ids' ) );
1015          $menu_id  = 0;
1016          if ( $menu_ids && ! is_wp_error( $menu_ids ) ) {
1017              $menu_id = array_shift( $menu_ids );
1018          }
1019  
1020          return $menu_id;
1021      }
1022  }


Generated : Thu May 9 08:20:02 2024 Cross-referenced by PHPXref