post_type = $post_type; $obj = get_post_type_object( $post_type ); $this->rest_base = ! empty( $rest_base ) ? $rest_base : ( ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name ); } /** * Register the routes for the objects of the controller. */ public function register_routes() { register_rest_route( $this->namespace, '/' . $this->rest_base, array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_items' ), 'permission_callback' => array( $this, 'api_permissions_check' ), 'args' => $this->get_collection_params(), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P[\d]+)', array( array( 'methods' => WP_REST_Server::READABLE, 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'api_permissions_check' ), 'args' => array( 'context' => $this->get_context_param( array( 'default' => 'view' ) ), ), ), 'schema' => array( $this, 'get_public_item_schema' ), ) ); } /** * Get the query params for collections of attachments. * * @return array */ public function get_collection_params() { $params = parent::get_collection_params(); $params['context']['default'] = 'view'; $params['after'] = array( 'description' => __( 'Limit response to resources published after a given ISO8601 compliant date.' ), 'type' => 'string', 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); if ( post_type_supports( $this->post_type, 'author' ) ) { $params['author'] = array( 'description' => __( 'Limit result set to posts assigned to specific authors.' ), 'type' => 'array', 'default' => array(), 'items' => array( 'type' => 'integer' ), 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); $params['author_exclude'] = array( 'description' => __( 'Ensure result set excludes posts assigned to specific authors.' ), 'type' => 'array', 'default' => array(), 'items' => array( 'type' => 'integer' ), 'sanitize_callback' => 'wp_parse_id_list', 'validate_callback' => 'rest_validate_request_arg', ); } $params['before'] = array( 'description' => __( 'Limit response to resources published before a given ISO8601 compliant date.' ), 'type' => 'string', 'format' => 'date-time', 'validate_callback' => 'rest_validate_request_arg', ); $params['exclude'] = array( 'description' => __( 'Ensure result set excludes specific ids.' ), 'type' => 'array', 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); $params['include'] = array( 'description' => __( 'Limit result set to specific ids.' ), 'type' => 'array', 'default' => array(), 'sanitize_callback' => 'wp_parse_id_list', ); if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { $params['menu_order'] = array( 'description' => __( 'Limit result set to resources with a specific menu_order value.' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); } $params['offset'] = array( 'description' => __( 'Offset the result set by a specific number of items.' ), 'type' => 'integer', 'sanitize_callback' => 'absint', 'validate_callback' => 'rest_validate_request_arg', ); $params['order'] = array( 'description' => __( 'Order sort attribute ascending or descending.' ), 'type' => 'string', 'default' => 'desc', 'enum' => array( 'asc', 'desc' ), 'validate_callback' => 'rest_validate_request_arg', ); $params['orderby'] = array( 'description' => __( 'Sort collection by object attribute.' ), 'type' => 'string', 'default' => 'date', 'enum' => array( 'date', 'id', 'include', 'title', 'slug', ), 'validate_callback' => 'rest_validate_request_arg', ); if ( 'page' === $this->post_type || post_type_supports( $this->post_type, 'page-attributes' ) ) { $params['orderby']['enum'][] = 'menu_order'; } $post_type_obj = get_post_type_object( $this->post_type ); if ( $post_type_obj->hierarchical || 'attachment' === $this->post_type ) { $params['parent'] = array( 'description' => __( 'Limit result set to those of particular parent ids.' ), 'type' => 'array', 'sanitize_callback' => 'wp_parse_id_list', 'default' => array(), ); $params['parent_exclude'] = array( 'description' => __( 'Limit result set to all items except those of a particular parent id.' ), 'type' => 'array', 'sanitize_callback' => 'wp_parse_id_list', 'default' => array(), ); } $params['slug'] = array( 'description' => __( 'Limit result set to posts with a specific slug.' ), 'type' => 'string', 'validate_callback' => 'rest_validate_request_arg', ); $params['status'] = array( 'default' => 'publish', 'description' => __( 'Limit result set to posts assigned a specific status.' ), 'sanitize_callback' => 'sanitize_key', 'type' => 'string', 'validate_callback' => array( $this, 'validate_user_can_query_private_statuses' ), ); $params['filter'] = array( 'description' => __( 'Use WP Query arguments to modify the response; private query vars require appropriate authorization.' ), ); $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $params[ $base ] = array( 'description' => sprintf( __( 'Limit result set to all items that have the specified term assigned in the %s taxonomy.' ), $base ), 'type' => 'array', 'sanitize_callback' => 'wp_parse_id_list', 'default' => array(), ); } return $params; } /** * Get a collection of posts. * * @param WP_REST_Request $request Full details about the request. * * @return WP_Error|WP_REST_Response */ public function get_items( $request ) { $args = array(); $args['author__in'] = $request['author']; $args['author__not_in'] = $request['author_exclude']; $args['menu_order'] = $request['menu_order']; $args['offset'] = $request['offset']; $args['order'] = $request['order']; $args['orderby'] = $request['orderby']; $args['paged'] = $request['page']; $args['post__in'] = $request['include']; $args['post__not_in'] = $request['exclude']; $args['posts_per_page'] = $request['per_page']; $args['name'] = $request['slug']; $args['post_parent__in'] = $request['parent']; $args['post_parent__not_in'] = $request['parent_exclude']; $args['post_status'] = $request['status']; $args['s'] = $request['search']; if ( isset( $request['categories'] ) && ( empty( $request['categories'] ) || ( count( $request['categories'] ) === 1 && empty( $request['categories'][0] ) ) ) ) { unset( $request['categories'] ); } $args['date_query'] = array(); // Set before into date query. Date query must be specified as an array of an array. if ( isset( $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( $request['after'] ) ) { $args['date_query'][0]['after'] = $request['after']; } if ( is_array( $request['filter'] ) ) { $args = array_merge( $args, $request['filter'] ); unset( $args['filter'] ); } // Force the post_type argument, since it's not a user input variable. $args['post_type'] = $this->post_type; /** * Filter the query arguments for a request. * * Enables adding extra arguments or setting defaults for a post * collection request. * * @see https://developer.wordpress.org/reference/classes/wp_user_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_{$this->post_type}_query", $args, $request ); $query_args = $this->prepare_items_query( $args, $request ); $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; if ( ! empty( $request[ $base ] ) ) { $query_args['tax_query'][] = array( 'taxonomy' => $taxonomy->name, 'field' => 'term_id', 'terms' => $request[ $base ], 'include_children' => false, ); } } $posts_query = new WP_Query(); $query_result = $posts_query->query( $query_args ); $posts = array(); foreach ( $query_result as $post ) { if ( ! $this->check_read_permission( $post ) ) { continue; } $data = $this->prepare_item_for_response( $post, $request ); $posts[] = $this->prepare_response_for_collection( $data ); } $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) $query_args['posts_per_page'] ); $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(); if ( ! empty( $request_params['filter'] ) ) { // Normalize the pagination params. unset( $request_params['filter']['posts_per_page'] ); unset( $request_params['filter']['paged'] ); } $base = add_query_arg( $request_params, rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ) ); 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; } /** * @param $content * * @return mixed|void */ public function get_content( $content ) { $content = apply_filters( 'the_content', $content ); if ( empty( $this->head ) ) { ob_start(); wp_head(); $this->head = ob_get_clean(); } if ( empty( $this->footer ) ) { ob_start(); wp_footer(); $this->footer = ob_get_clean(); } return ' ' . $this->head . '
' . $content . '
' . $this->footer . ' '; } /** * Determine the allowed query_vars for a get_items() response and * prepare for WP_Query. * * @param array $prepared_args * @param WP_REST_Request $request * * @return array $query_args */ protected function prepare_items_query( $prepared_args = array(), $request = null ) { $valid_vars = array_flip( $this->get_allowed_query_vars() ); $query_args = array(); foreach ( $valid_vars as $var => $index ) { if ( isset( $prepared_args[ $var ] ) ) { /** * Filter the query_vars used in `get_items` for the constructed query. * * The dynamic portion of the hook name, $var, refers to the query_var key. * * @param mixed $prepared_args [ $var ] The query_var value. */ $query_args[ $var ] = apply_filters( "rest_query_var-{$var}", $prepared_args[ $var ] ); } } if ( 'post' !== $this->post_type || ! isset( $query_args['ignore_sticky_posts'] ) ) { $query_args['ignore_sticky_posts'] = true; } if ( 'include' === $query_args['orderby'] ) { $query_args['orderby'] = 'post__in'; } return $query_args; } /** * Get all the WP Query vars that are allowed for the API request. * * @return array */ protected function get_allowed_query_vars() { global $wp; /** * Filter the publicly allowed query vars. * * Allows adjusting of the default query vars that are made public. * * @param array Array of allowed WP_Query query vars. */ $valid_vars = apply_filters( 'query_vars', $wp->public_query_vars ); $post_type_obj = get_post_type_object( $this->post_type ); if ( current_user_can( $post_type_obj->cap->edit_posts ) ) { /** * Filter the allowed 'private' query vars for authorized users. * * If the user has the `edit_posts` capability, we also allow use of * private query parameters, which are only undesirable on the * frontend, but are safe for use in query strings. * * To disable anyway, use * `add_filter( 'rest_private_query_vars', '__return_empty_array' );` * * @param array $private_query_vars Array of allowed query vars for authorized users. * } */ $private = apply_filters( 'rest_private_query_vars', $wp->private_query_vars ); $valid_vars = array_merge( $valid_vars, $private ); } // Define our own in addition to WP's normal vars. $rest_valid = array( 'author__in', 'author__not_in', 'ignore_sticky_posts', 'menu_order', 'offset', 'post__in', 'post__not_in', 'post_parent', 'post_parent__in', 'post_parent__not_in', 'posts_per_page', 'date_query', ); $valid_vars = array_merge( $valid_vars, $rest_valid ); /** * Filter allowed query vars for the REST API. * * This filter allows you to add or remove query vars from the final allowed * list for all requests, including unauthenticated ones. To alter the * vars for editors only, {@see rest_private_query_vars}. * * @param array { * Array of allowed WP_Query query vars. * * @param string $allowed_query_var The query var to allow. * } */ $valid_vars = apply_filters( 'rest_query_vars', $valid_vars ); return $valid_vars; } /** * Check if we can read a post. * * Correctly handles posts with the inherit status. * * @param object $post Post object. * * @return boolean Can we read it? */ public function check_read_permission( $post ) { if ( ! empty( $post->post_password ) && ! $this->check_update_permission( $post ) ) { return false; } $post_type = get_post_type_object( $post->post_type ); if ( ! $this->check_is_post_type_allowed( $post_type ) ) { return false; } // Can we read the post? if ( 'publish' === $post->post_status || current_user_can( $post_type->cap->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 ); return $this->check_read_permission( $parent ); } // If we don't have 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; } /** * Check if a given post type should be viewed or managed. * * @param object|string $post_type * * @return boolean Is post type allowed? */ protected 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; } /** * Prepare a single post output for response. * * @param WP_Post $post Post object. * @param WP_REST_Request $request Request object. * * @return WP_REST_Response $data */ public function prepare_item_for_response( $post, $request ) { $GLOBALS['post'] = $post; setup_postdata( $post ); // Base fields for every post. $data = array( 'id' => $post->ID, 'date' => $this->prepare_date_response( $post->post_date_gmt, $post->post_date ), 'date_gmt' => $this->prepare_date_response( $post->post_date_gmt ), 'guid' => array( /** This filter is documented in wp-includes/post-template.php */ 'rendered' => apply_filters( 'get_the_guid', $post->guid ), 'raw' => $post->guid, ), 'modified' => $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified ), 'modified_gmt' => $this->prepare_date_response( $post->post_modified_gmt ), // 'password' => $post->post_password, 'slug' => $post->post_name, 'status' => $post->post_status, 'type' => $post->post_type, 'link' => get_permalink( $post->ID ), ); $schema = $this->get_item_schema(); if ( ! empty( $schema['properties']['title'] ) ) { $data['title'] = array( 'raw' => $post->post_title, 'rendered' => $this->decode_html( get_the_title( $post->ID ) ), ); } if ( ! empty( $schema['properties']['content'] ) ) { if ( ! empty( $post->post_password ) ) { $this->prepare_password_response( $post->post_password ); } if ( method_exists( 'WPBMap', 'addAllMappedShortcodes' ) ) { WPBMap::addAllMappedShortcodes(); } $data['content'] = array( 'raw' => $post->post_content, /** This filter is documented in wp-includes/post-template.php */ 'rendered' => $this->get_content( $post->post_content ), 'css_url' => get_stylesheet_uri(), ); // Don't leave our cookie lying around: https://github.com/WP-API/WP-API/issues/1055. if ( ! empty( $post->post_password ) ) { $_COOKIE[ 'wp-postpass_' . COOKIEHASH ] = ''; } } if ( ! empty( $schema['properties']['excerpt'] ) ) { $data['excerpt'] = array( 'raw' => $post->post_excerpt, 'rendered' => $this->prepare_excerpt_response( $post->post_excerpt ), ); } if ( ! empty( $schema['properties']['author'] ) ) { $data['author'] = $post->post_author; $data['author_name'] = get_the_author_meta( 'display_name', $post->post_author ); $data['avatar'] = get_avatar_url( $post->post_author, 40 ); } if ( ! empty( $schema['properties']['featured_media'] ) ) { $data['featured_media'] = get_the_post_thumbnail_url( $post->ID, 'medium_large' ); } if ( ! empty( $schema['properties']['parent'] ) ) { $data['parent'] = (int) $post->post_parent; } if ( ! empty( $schema['properties']['menu_order'] ) ) { $data['menu_order'] = (int) $post->menu_order; } if ( ! empty( $schema['properties']['comment_status'] ) ) { $data['comment_status'] = $post->comment_status; } if ( ! empty( $schema['properties']['ping_status'] ) ) { $data['ping_status'] = $post->ping_status; } if ( ! empty( $schema['properties']['sticky'] ) ) { $data['sticky'] = is_sticky( $post->ID ); } if ( ! empty( $schema['properties']['template'] ) ) { if ( $template = get_page_template_slug( $post->ID ) ) { $data['template'] = $template; } else { $data['template'] = ''; } } if ( ! empty( $schema['properties']['format'] ) ) { $data['format'] = get_post_format( $post->ID ); // Fill in blank post format. if ( empty( $data['format'] ) ) { $data['format'] = 'standard'; } } $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $terms = get_the_terms( $post, $taxonomy->name ); $data[ $base ] = $terms ? wp_list_pluck( $terms, 'term_id' ) : array(); } $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; $data = $this->add_additional_fields_to_object( $data, $request ); $data = $this->filter_response_by_context( $data, $context ); // Wrap the data in a response object. $response = rest_ensure_response( $data ); $response->add_links( $this->prepare_links( $post ) ); /** * Filter the post data for a response. * * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being * prepared for the response. * * @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_{$this->post_type}", $response, $post, $request ); } /** * Check the post_date_gmt or modified_gmt and prepare any post or * modified date for single post output. * * @param string $date_gmt * @param string|null $date * * @return string|null ISO8601/RFC3339 formatted datetime. */ protected function prepare_date_response( $date_gmt, $date = null ) { // Use the date if passed. if ( isset( $date ) ) { return mysql_to_rfc3339( $date ); } // Return null if $date_gmt is empty/zeros. if ( '0000-00-00 00:00:00' === $date_gmt ) { return null; } // Return the formatted datetime. return mysql_to_rfc3339( $date_gmt ); } /** * Get the Post's schema, conforming to JSON Schema. * * @return array */ public function get_item_schema() { $schema = array( '$schema' => 'http://json-schema.org/draft-04/schema#', 'title' => $this->post_type, 'type' => 'object', /* * Base properties for every Post. */ 'properties' => array( 'date' => array( 'description' => __( "The date the object was published, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit', 'embed' ), ), 'date_gmt' => array( 'description' => __( 'The date the object was published, as GMT.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), ), 'guid' => array( 'description' => __( 'The globally unique identifier for the object.' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'readonly' => true, 'properties' => array( 'raw' => array( 'description' => __( 'GUID for the object, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'GUID for the object, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), ), ), 'id' => array( 'description' => __( 'Unique identifier for the object.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'link' => array( 'description' => __( 'URL to the object.' ), 'type' => 'string', 'format' => 'uri', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), 'modified' => array( 'description' => __( "The date the object was last modified, in the site's timezone." ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'modified_gmt' => array( 'description' => __( 'The date the object was last modified, as GMT.' ), 'type' => 'string', 'format' => 'date-time', 'context' => array( 'view', 'edit' ), 'readonly' => true, ), 'password' => array( 'description' => __( 'A password to protect access to the post.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'slug' => array( 'description' => __( 'An alphanumeric identifier for the object unique to its type.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'arg_options' => array( 'sanitize_callback' => 'sanitize_title', ), ), 'status' => array( 'description' => __( 'A named status for the object.' ), 'type' => 'string', 'enum' => array_keys( get_post_stati( array( 'internal' => false ) ) ), 'context' => array( 'edit' ), ), 'type' => array( 'description' => __( 'Type of Post for the object.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), 'readonly' => true, ), ), ); $post_type_obj = get_post_type_object( $this->post_type ); if ( $post_type_obj->hierarchical ) { $schema['properties']['parent'] = array( 'description' => __( 'The id for the parent of the object.' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ); } $post_type_attributes = array( 'title', 'editor', 'author', 'excerpt', 'thumbnail', 'comments', 'revisions', 'page-attributes', 'post-formats', ); $fixed_schemas = array( 'post' => array( 'title', 'editor', 'author', 'excerpt', 'thumbnail', 'comments', 'revisions', 'post-formats', ), 'page' => array( 'title', 'editor', 'author', 'excerpt', 'thumbnail', 'comments', 'revisions', 'page-attributes', ), 'attachment' => array( 'title', 'author', 'comments', 'revisions', ), ); foreach ( $post_type_attributes as $attribute ) { if ( isset( $fixed_schemas[ $this->post_type ] ) && ! in_array( $attribute, $fixed_schemas[ $this->post_type ] ) ) { continue; } elseif ( ! in_array( $this->post_type, array_keys( $fixed_schemas ) ) && ! post_type_supports( $this->post_type, $attribute ) ) { continue; } switch ( $attribute ) { case 'title': $schema['properties']['title'] = array( 'description' => __( 'The title for the object.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'properties' => array( 'raw' => array( 'description' => __( 'Title for the object, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML title for the object, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), ), ), ); break; case 'editor': $schema['properties']['content'] = array( 'description' => __( 'The content for the object.' ), 'type' => 'object', 'context' => array( 'view', 'edit' ), 'properties' => array( 'raw' => array( 'description' => __( 'Content for the object, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML content for the object, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit' ), ), ), ); break; case 'author': $schema['properties']['author'] = array( 'description' => __( 'The id for the author of the object.' ), 'type' => 'integer', 'context' => array( 'view', 'edit', 'embed' ), ); break; case 'excerpt': $schema['properties']['excerpt'] = array( 'description' => __( 'The excerpt for the object.' ), 'type' => 'object', 'context' => array( 'view', 'edit', 'embed' ), 'properties' => array( 'raw' => array( 'description' => __( 'Excerpt for the object, as it exists in the database.' ), 'type' => 'string', 'context' => array( 'edit' ), ), 'rendered' => array( 'description' => __( 'HTML excerpt for the object, transformed for display.' ), 'type' => 'string', 'context' => array( 'view', 'edit', 'embed' ), ), ), ); break; case 'thumbnail': $schema['properties']['featured_media'] = array( 'description' => __( 'The id of the featured media for the object.' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ); break; case 'comments': $schema['properties']['comment_status'] = array( 'description' => __( 'Whether or not comments are open on the object.' ), 'type' => 'string', 'enum' => array( 'open', 'closed' ), 'context' => array( 'view', 'edit' ), ); $schema['properties']['ping_status'] = array( 'description' => __( 'Whether or not the object can be pinged.' ), 'type' => 'string', 'enum' => array( 'open', 'closed' ), 'context' => array( 'view', 'edit' ), ); break; case 'page-attributes': $schema['properties']['menu_order'] = array( 'description' => __( 'The order of the object in relation to other object of its type.' ), 'type' => 'integer', 'context' => array( 'view', 'edit' ), ); break; case 'post-formats': $schema['properties']['format'] = array( 'description' => __( 'The format for the object.' ), 'type' => 'string', 'enum' => array_values( get_post_format_slugs() ), 'context' => array( 'view', 'edit' ), ); break; } } if ( 'post' === $this->post_type ) { $schema['properties']['sticky'] = array( 'description' => __( 'Whether or not the object should be treated as sticky.' ), 'type' => 'boolean', 'context' => array( 'view', 'edit' ), ); } if ( 'page' === $this->post_type ) { $schema['properties']['template'] = array( 'description' => __( 'The theme file to use to display the object.' ), 'type' => 'string', 'enum' => array_keys( wp_get_theme()->get_page_templates() ), 'context' => array( 'view', 'edit' ), ); } $taxonomies = wp_list_filter( get_object_taxonomies( $this->post_type, 'objects' ), array( 'show_in_rest' => true ) ); foreach ( $taxonomies as $taxonomy ) { $base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name; $schema['properties'][ $base ] = array( 'description' => sprintf( __( 'The terms assigned to the object in the %s taxonomy.' ), $taxonomy->name ), 'type' => 'array', 'context' => array( 'view', 'edit' ), ); } return $this->add_additional_fields_schema( $schema ); } protected function prepare_password_response( $password ) { if ( ! empty( $password ) ) { /** * Fake the correct cookie to fool post_password_required(). * Without this, get_the_content() will give a password form. */ require_once ABSPATH . 'wp-includes/class-phpass.php'; $hasher = new PasswordHash( 8, true ); $value = $hasher->HashPassword( $password ); $_COOKIE[ 'wp-postpass_' . COOKIEHASH ] = wp_slash( $value ); } return $password; } /** * Check the post excerpt and prepare it for single post output. * * @param string $excerpt * * @return string|null $excerpt */ protected function prepare_excerpt_response( $excerpt ) { if ( post_password_required() ) { return __( 'There is no excerpt because this is a protected post.' ); } /** This filter is documented in wp-includes/post-template.php */ $excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $excerpt ) ); if ( empty( $excerpt ) ) { return ''; } return $this->decode_html( $excerpt ); } /** * Prepare links for the request. * * @param WP_Post $post Post object. * * @return array Links for the given post. */ protected function prepare_links( $post ) { $base = sprintf( '/%s/%s', $this->namespace, $this->rest_base ); // Entity meta $links = array( 'self' => array( 'href' => rest_url( trailingslashit( $base ) . $post->ID ), ), 'collection' => array( 'href' => rest_url( $base ), ), 'about' => array( 'href' => rest_url( '/' . $this->namespace . '/types/' . $this->post_type ), ), ); if ( ( in_array( $post->post_type, array( 'post', 'page', ) ) || post_type_supports( $post->post_type, 'author' ) ) && ! empty( $post->post_author ) ) { $links['author'] = array( 'href' => rest_url( '/' . $this->namespace . '/users/' . $post->post_author ), 'embeddable' => true, ); }; if ( in_array( $post->post_type, array( 'post', 'page', ) ) || post_type_supports( $post->post_type, 'comments' ) ) { $replies_url = rest_url( '/' . $this->namespace . '/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', ) ) || post_type_supports( $post->post_type, 'revisions' ) ) { $links['version-history'] = array( 'href' => rest_url( trailingslashit( $base ) . $post->ID . '/revisions' ), ); } $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. if ( $featured_media = get_post_thumbnail_id( $post->ID ) ) { $image_url = rest_url( $this->namespace . '/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' ) ) ) { $attachments_url = rest_url( $this->namespace . '/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( $this->namespace . '/' . $tax_base ) ); $links['https://api.w.org/term'][] = array( 'href' => $terms_url, 'taxonomy' => $tax, 'embeddable' => true, ); } } return $links; } /** * Get a single post. * * @param WP_REST_Request $request Full details about the request. * * @return WP_Error|WP_REST_Response */ public function get_item( $request ) { $id = (int) $request['id']; $post = get_post( $id ); if ( empty( $id ) || empty( $post->ID ) || ( $post->post_type != 'page' && $this->post_type !== $post->post_type ) ) { return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post id.' ), array( 'status' => 404 ) ); } $data = $this->prepare_item_for_response( $post, $request ); $response = rest_ensure_response( $data ); if ( is_post_type_viewable( get_post_type_object( $post->post_type ) ) ) { $response->link_header( 'alternate', get_permalink( $id ), array( 'type' => 'text/html' ) ); } return $response; } /** * Validate whether the user can query private statuses * * @param mixed $value * @param WP_REST_Request $request * @param string $parameter * * @return WP_Error|boolean */ public function validate_user_can_query_private_statuses( $value, $request, $parameter ) { if ( 'publish' === $value ) { return true; } $post_type_obj = get_post_type_object( $this->post_type ); if ( current_user_can( $post_type_obj->cap->edit_posts ) ) { return true; } return new WP_Error( 'rest_forbidden_status', __( 'Status is forbidden' ), array( 'status' => rest_authorization_required_code() ) ); } /** * Determine validity and normalize provided status param. * * @param string $post_status * @param object $post_type * * @return WP_Error|string $post_status */ protected function handle_status_param( $post_status, $post_type ) { switch ( $post_status ) { case 'draft': case 'pending': break; case 'private': if ( ! current_user_can( $post_type->cap->publish_posts ) ) { return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to create private posts in this post type' ), array( 'status' => rest_authorization_required_code() ) ); } break; case 'publish': case 'future': if ( ! current_user_can( $post_type->cap->publish_posts ) ) { return new WP_Error( 'rest_cannot_publish', __( 'Sorry, you are not allowed to publish posts in this post type' ), array( 'status' => rest_authorization_required_code() ) ); } break; default: if ( ! get_post_status_object( $post_status ) ) { $post_status = 'draft'; } break; } return $post_status; } }