[API] Một ví dụ nghiên cứu register_rest_route callback && permission_callback (ok)

<?php
add_action('rest_api_init', function () {
  register_rest_route(
    'myplugin/v1',
    '/post',
    array(
      array(
        'methods'             => WP_REST_Server::READABLE,
        'callback'            => 'get_items',
        'permission_callback' => 'get_items_permissions_check',
        'args'                => get_collection_params(),
      )
    )
  );
});
function get_items_permissions_check($request) {
  $post_type = get_post_type_object('post');
  if ('edit' === $request['context'] && !current_user_can($post_type->cap->edit_posts)) {
    return new WP_Error(
      'rest_forbidden_context',
      __('Sorry, you are not allowed to edit posts in this post type.'),
      array('status' => rest_authorization_required_code())
    );
  }
  return true;
}
function create_item_permissions_check($request) {
  if (!empty($request['id'])) {
    return new WP_Error(
      'rest_post_exists',
      __('Cannot create existing post.'),
      array('status' => 400)
    );
  }
  $post_type = get_post_type_object('post');
  if (!empty($request['author']) && get_current_user_id() !== $request['author'] && !current_user_can($post_type->cap->edit_others_posts)) {
    return new WP_Error(
      'rest_cannot_edit_others',
      __('Sorry, you are not allowed to create posts as this user.'),
      array('status' => rest_authorization_required_code())
    );
  }
  if (!empty($request['sticky']) && !current_user_can($post_type->cap->edit_others_posts) && !current_user_can($post_type->cap->publish_posts)) {
    return new WP_Error(
      'rest_cannot_assign_sticky',
      __('Sorry, you are not allowed to make posts sticky.'),
      array('status' => rest_authorization_required_code())
    );
  }
  if (!current_user_can($post_type->cap->create_posts)) {
    return new WP_Error(
      'rest_cannot_create',
      __('Sorry, you are not allowed to create posts as this user.'),
      array('status' => rest_authorization_required_code())
    );
  }
  if (!$this->check_assign_terms_permission($request)) {
    return new WP_Error(
      'rest_cannot_assign_term',
      __('Sorry, you are not allowed to assign the provided terms.'),
      array('status' => rest_authorization_required_code())
    );
  }
  return true;
}
function get_collection_params() {
  $query_params                       = get_collection_params_parent();
  $query_params['context']['default'] = 'view';
  $query_params['after']              = array(
    'description' => __('Limit response to posts published after a given ISO8601 compliant date.'),
    'type'        => 'string',
    'format'      => 'date-time',
  );
  if (post_type_supports('post', 'author')) {
    $query_params['author'] = array(
      'description' => __('Limit result set to posts assigned to specific authors.'),
      'type'        => 'array',
      'items'       => array(
        'type' => 'integer',
      ),
      'default'     => array(),
    );
    $query_params['author_exclude'] = array(
      'description' => __('Ensure result set excludes posts assigned to specific authors.'),
      'type'        => 'array',
      'items'       => array(
        'type' => 'integer',
      ),
      'default'     => array(),
    );
  }
  $query_params['before'] = array(
    'description' => __('Limit response to posts published before a given ISO8601 compliant date.'),
    'type'        => 'string',
    'format'      => 'date-time',
  );
  $query_params['exclude'] = array(
    'description' => __('Ensure result set excludes specific IDs.'),
    'type'        => 'array',
    'items'       => array(
      'type' => 'integer',
    ),
    'default'     => array(),
  );
  $query_params['include'] = array(
    'description' => __('Limit result set to specific IDs.'),
    'type'        => 'array',
    'items'       => array(
      'type' => 'integer',
    ),
    'default'     => array(),
  );
  if ('page' === 'post' || post_type_supports('post', 'page-attributes')) {
    $query_params['menu_order'] = array(
      'description' => __('Limit result set to posts with a specific menu_order value.'),
      'type'        => 'integer',
    );
  }
  $query_params['offset'] = array(
    'description' => __('Offset the result set by a specific number of items.'),
    'type'        => 'integer',
  );
  $query_params['order'] = array(
    'description' => __('Order sort attribute ascending or descending.'),
    'type'        => 'string',
    'default'     => 'desc',
    'enum'        => array('asc', 'desc'),
  );
  $query_params['orderby'] = array(
    'description' => __('Sort collection by object attribute.'),
    'type'        => 'string',
    'default'     => 'date',
    'enum'        => array(
      'author',
      'date',
      'id',
      'include',
      'modified',
      'parent',
      'relevance',
      'slug',
      'include_slugs',
      'title',
    ),
  );
  if ('page' === 'post' || post_type_supports('post', 'page-attributes')) {
    $query_params['orderby']['enum'][] = 'menu_order';
  }
  $post_type = get_post_type_object('post');
  if ($post_type->hierarchical || 'attachment' === 'post') {
    $query_params['parent'] = array(
      'description' => __('Limit result set to items with particular parent IDs.'),
      'type'        => 'array',
      'items'       => array(
        'type' => 'integer',
      ),
      'default'     => array(),
    );
    $query_params['parent_exclude'] = array(
      'description' => __('Limit result set to all items except those of a particular parent ID.'),
      'type'        => 'array',
      'items'       => array(
        'type' => 'integer',
      ),
      'default'     => array(),
    );
  }
  $query_params['slug'] = array(
    'description'       => __('Limit result set to posts with one or more specific slugs.'),
    'type'              => 'array',
    'items'             => array(
      'type' => 'string',
    ),
    'sanitize_callback' => 'wp_parse_slug_list',
  );
  $query_params['status'] = array(
    'default'           => 'publish',
    'description'       => __('Limit result set to posts assigned one or more statuses.'),
    'type'              => 'array',
    'items'             => array(
      'enum' => array_merge(array_keys(get_post_stati()), array('any')),
      'type' => 'string',
    ),
    'sanitize_callback' => 'sanitize_post_statuses',
  );
  $taxonomies = wp_list_filter(get_object_taxonomies('post', 'objects'), array('show_in_rest' => true));
  if (!empty($taxonomies)) {
    $query_params['tax_relation'] = array(
      'description' => __('Limit result set based on relationship between multiple taxonomies.'),
      'type'        => 'string',
      'enum'        => array('AND', 'OR'),
    );
  }
  foreach ($taxonomies as $taxonomy) {
    $base                = !empty($taxonomy->rest_base) ? $taxonomy->rest_base : $taxonomy->name;
    $query_params[$base] = array(
      /* translators: %s: Taxonomy name. */
      'description' => sprintf(__('Limit result set to all items that have the specified term assigned in the %s taxonomy.'), $base),
      'type'        => 'array',
      'items'       => array(
        'type' => 'integer',
      ),
      'default'     => array(),
    );
    $query_params[$base . '_exclude'] = array(
      /* translators: %s: Taxonomy name. */
      'description' => sprintf(__('Limit result set to all items except those that have the specified term assigned in the %s taxonomy.'), $base),
      'type'        => 'array',
      'items'       => array(
        'type' => 'integer',
      ),
      'default'     => array(),
    );
  }
  if ('post' === 'post') {
    $query_params['sticky'] = array(
      'description' => __('Limit result set to items that are sticky.'),
      'type'        => 'boolean',
    );
  }
  return apply_filters("rest_{'post'}_collection_params", $query_params, $post_type);
}
function get_collection_params_parent() {
  return array(
    'context'  => get_context_param(),
    'page'     => array(
      'description'       => __('Current page of the collection.'),
      'type'              => 'integer',
      'default'           => 1,
      'sanitize_callback' => 'absint',
      'validate_callback' => 'rest_validate_request_arg',
      'minimum'           => 1,
    ),
    'per_page' => array(
      'description'       => __('Maximum number of items to be returned in result set.'),
      'type'              => 'integer',
      'default'           => 10,
      'minimum'           => 1,
      'maximum'           => 100,
      'sanitize_callback' => 'absint',
      'validate_callback' => 'rest_validate_request_arg',
    ),
    'search'   => array(
      'description'       => __('Limit results to those matching a string.'),
      'type'              => 'string',
      'sanitize_callback' => 'sanitize_text_field',
      'validate_callback' => 'rest_validate_request_arg',
    ),
  );
}
function get_context_param($args = array()) {
  $param_details = array(
    'description'       => __('Scope under which the request is made; determines fields present in response.'),
    'type'              => 'string',
    'sanitize_callback' => 'sanitize_key',
    'validate_callback' => 'rest_validate_request_arg',
  );
  $schema = get_item_schema();
  if (empty($schema['properties'])) {
    return array_merge($param_details, $args);
  }
  $contexts = array();
  foreach ($schema['properties'] as $attributes) {
    if (!empty($attributes['context'])) {
      $contexts = array_merge($contexts, $attributes['context']);
    }
  }
  if (!empty($contexts)) {
    $param_details['enum'] = array_unique($contexts);
    rsort($param_details['enum']);
  }
  return array_merge($param_details, $args);
}
function get_item_schema() {
  return null;
}
function sanitize_post_statuses($statuses, $request, $parameter) {
  $statuses = wp_parse_slug_list($statuses);
  // The default status is different in WP_REST_Attachments_Controller.
  $attributes     = $request->get_attributes();
  $default_status = $attributes['args']['status']['default'];
  foreach ($statuses as $status) {
    if ($status === $default_status) {
      continue;
    }
    $post_type_obj = get_post_type_object('post');
    if (current_user_can($post_type_obj->cap->edit_posts) || 'private' === $status && current_user_can($post_type_obj->cap->read_private_posts)) {
      $result = rest_validate_request_arg($status, $request, $parameter);
      if (is_wp_error($result)) {
        return $result;
      }
    } else {
      return new WP_Error(
        'rest_forbidden_status',
        __('Status is forbidden.'),
        array('status' => rest_authorization_required_code())
      );
    }
  }
  return $statuses;
}
function get_items($request) {
  // Ensure a search string is set in case the orderby is set to 'relevance'.
  if (!empty($request['orderby']) && 'relevance' === $request['orderby'] && empty($request['search'])) {
    return new WP_Error(
      'rest_no_search_term_defined',
      __('You need to define a search term to order by relevance.'),
      array('status' => 400)
    );
  }
  // Ensure an include parameter is set in case the orderby is set to 'include'.
  if (!empty($request['orderby']) && 'include' === $request['orderby'] && empty($request['include'])) {
    return new WP_Error(
      'rest_orderby_include_missing_include',
      __('You need to define an include parameter to order by include.'),
      array('status' => 400)
    );
  }
  // Retrieve the list of registered collection query parameters.
  $registered = get_collection_params();
  $args       = array();
  /*
   * This array defines mappings between public API query parameters whose
   * values are accepted as-passed, and their internal WP_Query parameter
   * name equivalents (some are the same). Only values which are also
   * present in $registered will be set.
   */
  $parameter_mappings = array(
    'author'         => 'author__in',
    'author_exclude' => 'author__not_in',
    'exclude'        => 'post__not_in',
    'include'        => 'post__in',
    'menu_order'     => 'menu_order',
    'offset'         => 'offset',
    'order'          => 'order',
    'orderby'        => 'orderby',
    'page'           => 'paged',
    'parent'         => 'post_parent__in',
    'parent_exclude' => 'post_parent__not_in',
    'search'         => 's',
    'slug'           => 'post_name__in',
    'status'         => 'post_status',
  );
  /*
   * For each known parameter which is both registered and present in the request,
   * set the parameter's value on the query $args.
   */
  foreach ($parameter_mappings as $api_param => $wp_param) {
    if (isset($registered[$api_param], $request[$api_param])) {
      $args[$wp_param] = $request[$api_param];
    }
  }
  // Check for & assign any parameters which require special handling or setting.
  $args['date_query'] = array();
  // Set before into date query. Date query must be specified as an array of an array.
  if (isset($registered['before'], $request['before'])) {
    $args['date_query'][0]['before'] = $request['before'];
  }
  // Set after into date query. Date query must be specified as an array of an array.
  if (isset($registered['after'], $request['after'])) {
    $args['date_query'][0]['after'] = $request['after'];
  }
  // Ensure our per_page parameter overrides any provided posts_per_page filter.
  if (isset($registered['per_page'])) {
    $args['posts_per_page'] = $request['per_page'];
  }
  if (isset($registered['sticky'], $request['sticky'])) {
    $sticky_posts = get_option('sticky_posts', array());
    if (!is_array($sticky_posts)) {
      $sticky_posts = array();
    }
    if ($request['sticky']) {
      /*
       * As post__in will be used to only get sticky posts,
       * we have to support the case where post__in was already
       * specified.
       */
      $args['post__in'] = $args['post__in'] ? array_intersect($sticky_posts, $args['post__in']) : $sticky_posts;
      /*
       * If we intersected, but there are no post IDs in common,
       * WP_Query won't return "no posts" for post__in = array()
       * so we have to fake it a bit.
       */
      if (!$args['post__in']) {
        $args['post__in'] = array(0);
      }
    } elseif ($sticky_posts) {
      /*
       * As post___not_in will be used to only get posts that
       * are not sticky, we have to support the case where post__not_in
       * was already specified.
       */
      $args['post__not_in'] = array_merge($args['post__not_in'], $sticky_posts);
    }
  }
  // Force the post_type argument, since it's not a user input variable.
  $args['post_type'] = 'post';
  /**
   * Filters the query arguments for a request.
   *
   * Enables adding extra arguments or setting defaults for a post collection request.
   *
   * @since 4.7.0
   *
   * @link https://developer.wordpress.org/reference/classes/wp_query/
   *
   * @param array           $args    Key value array of query var to query value.
   * @param WP_REST_Request $request The request used.
   */
  $args       = apply_filters("rest_{'post'}_query", $args, $request);
  $query_args = prepare_items_query($args, $request);
  $taxonomies = wp_list_filter(get_object_taxonomies('post', 'objects'), array('show_in_rest' => true));
  if (!empty($request['tax_relation'])) {
    $query_args['tax_query'] = array('relation' => $request['tax_relation']);
  }
  foreach ($taxonomies as $taxonomy) {
    $base        = !empty($taxonomy->rest_base) ? $taxonomy->rest_base : $taxonomy->name;
    $tax_exclude = $base . '_exclude';
    if (!empty($request[$base])) {
      $query_args['tax_query'][] = array(
        'taxonomy'         => $taxonomy->name,
        'field'            => 'term_id',
        'terms'            => $request[$base],
        'include_children' => false,
      );
    }
    if (!empty($request[$tax_exclude])) {
      $query_args['tax_query'][] = array(
        'taxonomy'         => $taxonomy->name,
        'field'            => 'term_id',
        'terms'            => $request[$tax_exclude],
        'include_children' => false,
        'operator'         => 'NOT IN',
      );
    }
  }
  $posts_query  = new WP_Query();
  $query_result = $posts_query->query($query_args);
  // Allow access to all password protected posts if the context is edit.
  if ('edit' === $request['context']) {
    add_filter('post_password_required', '__return_false');
  }
  $posts = array();
  foreach ($query_result as $post) {
    if (!check_read_permission($post)) {
      continue;
    }
    $data    = prepare_item_for_response($post, $request);
    $posts[] = prepare_response_for_collection($data);
  }
  // Reset filter.
  if ('edit' === $request['context']) {
    remove_filter('post_password_required', '__return_false');
  }
  $page        = (int) $query_args['paged'];
  $total_posts = $posts_query->found_posts;
  if ($total_posts < 1) {
    // Out-of-bounds, run the query again without LIMIT for total count.
    unset($query_args['paged']);
    $count_query = new WP_Query();
    $count_query->query($query_args);
    $total_posts = $count_query->found_posts;
  }
  $max_pages = ceil($total_posts / (int) $posts_query->query_vars['posts_per_page']);
  if ($page > $max_pages && $total_posts > 0) {
    return new WP_Error(
      'rest_post_invalid_page_number',
      __('The page number requested is larger than the number of pages available.'),
      array('status' => 400)
    );
  }
  $response = rest_ensure_response($posts);
  $response->header('X-WP-Total', (int) $total_posts);
  $response->header('X-WP-TotalPages', (int) $max_pages);
  $request_params = $request->get_query_params();
  $base           = add_query_arg(urlencode_deep($request_params), rest_url(sprintf('%s/%s', 'myplugin/v1', 'post')));
  if ($page > 1) {
    $prev_page = $page - 1;
    if ($prev_page > $max_pages) {
      $prev_page = $max_pages;
    }
    $prev_link = add_query_arg('page', $prev_page, $base);
    $response->link_header('prev', $prev_link);
  }
  if ($max_pages > $page) {
    $next_page = $page + 1;
    $next_link = add_query_arg('page', $next_page, $base);
    $response->link_header('next', $next_link);
  }
  return $response;
}
function prepare_items_query($prepared_args = array(), $request = null) {
  $query_args = array();
  foreach ($prepared_args as $key => $value) {
    /**
     * Filters the query_vars used in get_items() for the constructed query.
     *
     * The dynamic portion of the hook name, `$key`, refers to the query_var key.
     *
     * @since 4.7.0
     *
     * @param string $value The query_var value.
     */
    $query_args[$key] = apply_filters("rest_query_var-{$key}", $value); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
  }
  if ('post' !== 'post' || !isset($query_args['ignore_sticky_posts'])) {
    $query_args['ignore_sticky_posts'] = true;
  }
  // Map to proper WP_Query orderby param.
  if (isset($query_args['orderby']) && isset($request['orderby'])) {
    $orderby_mappings = array(
      'id'            => 'ID',
      'include'       => 'post__in',
      'slug'          => 'post_name',
      'include_slugs' => 'post_name__in',
    );
    if (isset($orderby_mappings[$request['orderby']])) {
      $query_args['orderby'] = $orderby_mappings[$request['orderby']];
    }
  }
  return $query_args;
}
function check_read_permission($post) {
  $post_type = get_post_type_object($post->post_type);
  if (!check_is_post_type_allowed($post_type)) {
    return false;
  }
  // Is the post readable?
  if ('publish' === $post->post_status || current_user_can('read_post', $post->ID)) {
    return true;
  }
  $post_status_obj = get_post_status_object($post->post_status);
  if ($post_status_obj && $post_status_obj->public) {
    return true;
  }
  // Can we read the parent if we're inheriting?
  if ('inherit' === $post->post_status && $post->post_parent > 0) {
    $parent = get_post($post->post_parent);
    if ($parent) {
      return check_read_permission($parent);
    }
  }
  /*
   * If there isn't a parent, but the status is set to inherit, assume
   * it's published (as per get_post_status()).
   */
  if ('inherit' === $post->post_status) {
    return true;
  }
  return false;
}
function check_is_post_type_allowed($post_type) {
  if (!is_object($post_type)) {
    $post_type = get_post_type_object($post_type);
  }
  if (!empty($post_type) && !empty($post_type->show_in_rest)) {
    return true;
  }
  return false;
}
function prepare_item_for_response($post, $request) {
  $GLOBALS['post'] = $post;
  setup_postdata($post);
  $fields = get_fields_for_response($request);
  // Base fields for every post.
  $data = array();
  if (rest_is_field_included('id', $fields)) {
    $data['id'] = $post->ID;
  }
  if (rest_is_field_included('date', $fields)) {
    $data['date'] = $this->prepare_date_response($post->post_date_gmt, $post->post_date);
  }
  if (rest_is_field_included('date_gmt', $fields)) {
    /*
     * For drafts, `post_date_gmt` may not be set, indicating that the date
     * of the draft should be updated each time it is saved (see #38883).
     * In this case, shim the value based on the `post_date` field
     * with the site's timezone offset applied.
     */
    if ('0000-00-00 00:00:00' === $post->post_date_gmt) {
      $post_date_gmt = get_gmt_from_date($post->post_date);
    } else {
      $post_date_gmt = $post->post_date_gmt;
    }
    $data['date_gmt'] = $this->prepare_date_response($post_date_gmt);
  }
  if (rest_is_field_included('guid', $fields)) {
    $data['guid'] = array(
      /** This filter is documented in wp-includes/post-template.php */
      'rendered' => apply_filters('get_the_guid', $post->guid, $post->ID),
      'raw'      => $post->guid,
    );
  }
  if (rest_is_field_included('modified', $fields)) {
    $data['modified'] = $this->prepare_date_response($post->post_modified_gmt, $post->post_modified);
  }
  if (rest_is_field_included('modified_gmt', $fields)) {
    /*
     * For drafts, `post_modified_gmt` may not be set (see `post_date_gmt` comments
     * above). In this case, shim the value based on the `post_modified` field
     * with the site's timezone offset applied.
     */
    if ('0000-00-00 00:00:00' === $post->post_modified_gmt) {
      $post_modified_gmt = gmdate('Y-m-d H:i:s', strtotime($post->post_modified) - (get_option('gmt_offset') * 3600));
    } else {
      $post_modified_gmt = $post->post_modified_gmt;
    }
    $data['modified_gmt'] = $this->prepare_date_response($post_modified_gmt);
  }
  if (rest_is_field_included('password', $fields)) {
    $data['password'] = $post->post_password;
  }
  if (rest_is_field_included('slug', $fields)) {
    $data['slug'] = $post->post_name;
  }
  if (rest_is_field_included('status', $fields)) {
    $data['status'] = $post->post_status;
  }
  if (rest_is_field_included('type', $fields)) {
    $data['type'] = $post->post_type;
  }
  if (rest_is_field_included('link', $fields)) {
    $data['link'] = get_permalink($post->ID);
  }
  if (rest_is_field_included('title', $fields)) {
    $data['title'] = array();
  }
  if (rest_is_field_included('title.raw', $fields)) {
    $data['title']['raw'] = $post->post_title;
  }
  if (rest_is_field_included('title.rendered', $fields)) {
    add_filter('protected_title_format', array($this, 'protected_title_format'));
    $data['title']['rendered'] = get_the_title($post->ID);
    remove_filter('protected_title_format', array($this, 'protected_title_format'));
  }
  $has_password_filter = false;
  if (can_access_password_content($post, $request)) {
    // Allow access to the post, permissions already checked before.
    add_filter('post_password_required', '__return_false');
    $has_password_filter = true;
  }
  if (rest_is_field_included('content', $fields)) {
    $data['content'] = array();
  }
  if (rest_is_field_included('content.raw', $fields)) {
    $data['content']['raw'] = $post->post_content;
  }
  if (rest_is_field_included('content.rendered', $fields)) {
    /** This filter is documented in wp-includes/post-template.php */
    $data['content']['rendered'] = post_password_required($post) ? '' : apply_filters('the_content', $post->post_content);
  }
  if (rest_is_field_included('content.protected', $fields)) {
    $data['content']['protected'] = (bool) $post->post_password;
  }
  if (rest_is_field_included('content.block_version', $fields)) {
    $data['content']['block_version'] = block_version($post->post_content);
  }
  if (rest_is_field_included('excerpt', $fields)) {
    /** This filter is documented in wp-includes/post-template.php */
    $excerpt = apply_filters('get_the_excerpt', $post->post_excerpt, $post);
    /** This filter is documented in wp-includes/post-template.php */
    $excerpt         = apply_filters('the_excerpt', $excerpt);
    $data['excerpt'] = array(
      'raw'       => $post->post_excerpt,
      'rendered'  => post_password_required($post) ? '' : $excerpt,
      'protected' => (bool) $post->post_password,
    );
  }
  if ($has_password_filter) {
    // Reset filter.
    remove_filter('post_password_required', '__return_false');
  }
  if (rest_is_field_included('author', $fields)) {
    $data['author'] = (int) $post->post_author;
  }
  if (rest_is_field_included('featured_media', $fields)) {
    $data['featured_media'] = (int) get_post_thumbnail_id($post->ID);
  }
  if (rest_is_field_included('parent', $fields)) {
    $data['parent'] = (int) $post->post_parent;
  }
  if (rest_is_field_included('menu_order', $fields)) {
    $data['menu_order'] = (int) $post->menu_order;
  }
  if (rest_is_field_included('comment_status', $fields)) {
    $data['comment_status'] = $post->comment_status;
  }
  if (rest_is_field_included('ping_status', $fields)) {
    $data['ping_status'] = $post->ping_status;
  }
  if (rest_is_field_included('sticky', $fields)) {
    $data['sticky'] = is_sticky($post->ID);
  }
  if (rest_is_field_included('template', $fields)) {
    $template = get_page_template_slug($post->ID);
    if ($template) {
      $data['template'] = $template;
    } else {
      $data['template'] = '';
    }
  }
  if (rest_is_field_included('format', $fields)) {
    $data['format'] = get_post_format($post->ID);
    // Fill in blank post format.
    if (empty($data['format'])) {
      $data['format'] = 'standard';
    }
  }
  if (rest_is_field_included('meta', $fields)) {
    $data['meta'] = $this->meta->get_value($post->ID, $request);
  }
  $taxonomies = wp_list_filter(get_object_taxonomies('post', 'objects'), array('show_in_rest' => true));
  foreach ($taxonomies as $taxonomy) {
    $base = !empty($taxonomy->rest_base) ? $taxonomy->rest_base : $taxonomy->name;
    if (rest_is_field_included($base, $fields)) {
      $terms       = get_the_terms($post, $taxonomy->name);
      $data[$base] = $terms ? array_values(wp_list_pluck($terms, 'term_id')) : array();
    }
  }
  $post_type_obj = get_post_type_object($post->post_type);
  if (is_post_type_viewable($post_type_obj) && $post_type_obj->public) {
    $permalink_template_requested = rest_is_field_included('permalink_template', $fields);
    $generated_slug_requested     = rest_is_field_included('generated_slug', $fields);
    if ($permalink_template_requested || $generated_slug_requested) {
      if (!function_exists('get_sample_permalink')) {
        require_once ABSPATH . 'wp-admin/includes/post.php';
      }
      $sample_permalink = get_sample_permalink($post->ID, $post->post_title, '');
      if ($permalink_template_requested) {
        $data['permalink_template'] = $sample_permalink[0];
      }
      if ($generated_slug_requested) {
        $data['generated_slug'] = $sample_permalink[1];
      }
    }
  }
  $context = !empty($request['context']) ? $request['context'] : 'view';
  $data    = add_additional_fields_to_object($data, $request);
  $data    = filter_response_by_context($data, $context);
  // Wrap the data in a response object.
  $response = rest_ensure_response($data);
  $links    = prepare_links($post);
  $response->add_links($links);
  if (!empty($links['self']['href'])) {
    $actions = get_available_actions($post, $request);
    $self    = $links['self']['href'];
    foreach ($actions as $rel) {
      $response->add_link($rel, $self);
    }
  }
  /**
   * Filters the post data for a response.
   *
   * The dynamic portion of the hook name, `'post'`, refers to the post type slug.
   *
   * @since 4.7.0
   *
   * @param WP_REST_Response $response The response object.
   * @param WP_Post          $post     Post object.
   * @param WP_REST_Request  $request  Request object.
   */
  return apply_filters("rest_prepare_{'post'}", $response, $post, $request);
}
function prepare_response_for_collection($response) {
  if (!($response instanceof WP_REST_Response)) {
    return $response;
  }
  $data   = (array) $response->get_data();
  $server = rest_get_server();
  $links  = $server::get_compact_response_links($response);
  if (!empty($links)) {
    $data['_links'] = $links;
  }
  return $data;
}
function get_fields_for_response($request) {
  $schema            = get_item_schema();
  $properties        = isset($schema['properties']) ? $schema['properties'] : array();
  $additional_fields = get_additional_fields();
  foreach ($additional_fields as $field_name => $field_options) {
    // For back-compat, include any field with an empty schema
    // because it won't be present in $this->get_item_schema().
    if (is_null($field_options['schema'])) {
      $properties[$field_name] = $field_options;
    }
  }
  // Exclude fields that specify a different context than the request context.
  $context = $request['context'];
  if ($context) {
    foreach ($properties as $name => $options) {
      if (!empty($options['context']) && !in_array($context, $options['context'], true)) {
        unset($properties[$name]);
      }
    }
  }
  $fields = array_keys($properties);
  if (!isset($request['_fields'])) {
    return $fields;
  }
  $requested_fields = wp_parse_list($request['_fields']);
  if (0 === count($requested_fields)) {
    return $fields;
  }
  // Trim off outside whitespace from the comma delimited list.
  $requested_fields = array_map('trim', $requested_fields);
  // Always persist 'id', because it can be needed for add_additional_fields_to_object().
  if (in_array('id', $fields, true)) {
    $requested_fields[] = 'id';
  }
  // Return the list of all requested fields which appear in the schema.
  return array_reduce(
    $requested_fields,
    function ($response_fields, $field) use ($fields) {
      if (in_array($field, $fields, true)) {
        $response_fields[] = $field;
        return $response_fields;
      }
      // Check for nested fields if $field is not a direct match.
      $nested_fields = explode('.', $field);
      // A nested field is included so long as its top-level property
      // is present in the schema.
      if (in_array($nested_fields[0], $fields, true)) {
        $response_fields[] = $field;
      }
      return $response_fields;
    },
    array()
  );
}
function get_additional_fields($object_type = null) {
  if (!$object_type) {
    $object_type = get_object_type();
  }
  if (!$object_type) {
    return array();
  }
  global $wp_rest_additional_fields;
  if (!$wp_rest_additional_fields || !isset($wp_rest_additional_fields[$object_type])) {
    return array();
  }
  return $wp_rest_additional_fields[$object_type];
}
function get_object_type() {
  $schema = get_item_schema();
  if (!$schema || !isset($schema['title'])) {
    return null;
  }
  return $schema['title'];
}
function can_access_password_content($post, $request) {
  if (empty($post->post_password)) {
    // No filter required.
    return false;
  }
  // Edit context always gets access to password-protected posts.
  if ('edit' === $request['context']) {
    return true;
  }
  // No password, no auth.
  if (empty($request['password'])) {
    return false;
  }
  // Double-check the request password.
  return hash_equals($post->post_password, $request['password']);
}
function add_additional_fields_to_object($prepared, $request) {
  $additional_fields = get_additional_fields();
  $requested_fields  = get_fields_for_response($request);
  foreach ($additional_fields as $field_name => $field_options) {
    if (!$field_options['get_callback']) {
      continue;
    }
    if (!rest_is_field_included($field_name, $requested_fields)) {
      continue;
    }
    $prepared[$field_name] = call_user_func($field_options['get_callback'], $prepared, $field_name, $request, $this->get_object_type());
  }
  return $prepared;
}
function filter_response_by_context($data, $context) {
  $schema = get_item_schema();
  return rest_filter_response_by_context($data, $schema, $context);
}
function prepare_links($post) {
  $base = sprintf('%s/%s', 'myplugin/v1', 'post');
  // Entity meta.
  $links = array(
    'self'       => array(
      'href' => rest_url(trailingslashit($base) . $post->ID),
    ),
    'collection' => array(
      'href' => rest_url($base),
    ),
    'about'      => array(
      'href' => rest_url('myplugin/v1/types/' . 'post'),
    ),
  );
  if ((in_array($post->post_type, array('post', 'page'), true) || post_type_supports($post->post_type, 'author'))
    && !empty($post->post_author)) {
    $links['author'] = array(
      'href'       => rest_url('myplugin/v1/users/' . $post->post_author),
      'embeddable' => true,
    );
  }
  if (in_array($post->post_type, array('post', 'page'), true) || post_type_supports($post->post_type, 'comments')) {
    $replies_url      = rest_url('myplugin/v1/comments');
    $replies_url      = add_query_arg('post', $post->ID, $replies_url);
    $links['replies'] = array(
      'href'       => $replies_url,
      'embeddable' => true,
    );
  }
  if (in_array($post->post_type, array('post', 'page'), true) || post_type_supports($post->post_type, 'revisions')) {
    $revisions                = wp_get_post_revisions($post->ID, array('fields' => 'ids'));
    $revisions_count          = count($revisions);
    $links['version-history'] = array(
      'href'  => rest_url(trailingslashit($base) . $post->ID . '/revisions'),
      'count' => $revisions_count,
    );
    if ($revisions_count > 0) {
      $last_revision                = array_shift($revisions);
      $links['predecessor-version'] = array(
        'href' => rest_url(trailingslashit($base) . $post->ID . '/revisions/' . $last_revision),
        'id'   => $last_revision,
      );
    }
  }
  $post_type_obj = get_post_type_object($post->post_type);
  if ($post_type_obj->hierarchical && !empty($post->post_parent)) {
    $links['up'] = array(
      'href'       => rest_url(trailingslashit($base) . (int) $post->post_parent),
      'embeddable' => true,
    );
  }
  // If we have a featured media, add that.
  $featured_media = get_post_thumbnail_id($post->ID);
  if ($featured_media) {
    $image_url                                = rest_url('myplugin/v1/media/' . $featured_media);
    $links['https://api.w.org/featuredmedia'] = array(
      'href'       => $image_url,
      'embeddable' => true,
    );
  }
  if (!in_array($post->post_type, array('attachment', 'nav_menu_item', 'revision'), true)) {
    $attachments_url                       = rest_url('myplugin/v1/media');
    $attachments_url                       = add_query_arg('parent', $post->ID, $attachments_url);
    $links['https://api.w.org/attachment'] = array(
      'href' => $attachments_url,
    );
  }
  $taxonomies = get_object_taxonomies($post->post_type);
  if (!empty($taxonomies)) {
    $links['https://api.w.org/term'] = array();
    foreach ($taxonomies as $tax) {
      $taxonomy_obj = get_taxonomy($tax);
      // Skip taxonomies that are not public.
      if (empty($taxonomy_obj->show_in_rest)) {
        continue;
      }
      $tax_base  = !empty($taxonomy_obj->rest_base) ? $taxonomy_obj->rest_base : $tax;
      $terms_url = add_query_arg(
        'post',
        $post->ID,
        rest_url('myplugin/v1/' . $tax_base)
      );
      $links['https://api.w.org/term'][] = array(
        'href'       => $terms_url,
        'taxonomy'   => $tax,
        'embeddable' => true,
      );
    }
  }
  return $links;
}
function get_available_actions($post, $request) {
  if ('edit' !== $request['context']) {
    return array();
  }
  $rels      = array();
  $post_type = get_post_type_object($post->post_type);
  if ('attachment' !== 'post' && current_user_can($post_type->cap->publish_posts)) {
    $rels[] = 'https://api.w.org/action-publish';
  }
  if (current_user_can('unfiltered_html')) {
    $rels[] = 'https://api.w.org/action-unfiltered-html';
  }
  if ('post' === $post_type->name) {
    if (current_user_can($post_type->cap->edit_others_posts) && current_user_can($post_type->cap->publish_posts)) {
      $rels[] = 'https://api.w.org/action-sticky';
    }
  }
  if (post_type_supports($post_type->name, 'author')) {
    if (current_user_can($post_type->cap->edit_others_posts)) {
      $rels[] = 'https://api.w.org/action-assign-author';
    }
  }
  $taxonomies = wp_list_filter(get_object_taxonomies('post', 'objects'), array('show_in_rest' => true));
  foreach ($taxonomies as $tax) {
    $tax_base   = !empty($tax->rest_base) ? $tax->rest_base : $tax->name;
    $create_cap = is_taxonomy_hierarchical($tax->name) ? $tax->cap->edit_terms : $tax->cap->assign_terms;
    if (current_user_can($create_cap)) {
      $rels[] = 'https://api.w.org/action-create-' . $tax_base;
    }
    if (current_user_can($tax->cap->assign_terms)) {
      $rels[] = 'https://api.w.org/action-assign-' . $tax_base;
    }
  }
  return $rels;
}
?>

Last updated