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(), '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(), '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']; $args['post_type'] = $request['post_type']; if ( ! empty( $args['post_type'] ) ) { $this->post_type = $args['post_type']; } $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. */ $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; } /** * 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 we can edit a post. * * @param object $post Post object. * * @return boolean Can we edit it? */ protected function check_update_permission( $post ) { $post_type = get_post_type_object( $post->post_type ); if ( ! $this->check_is_post_type_allowed( $post_type ) ) { return false; } return current_user_can( $post_type->cap->edit_post, $post->ID ); } /** * 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 ) ) { 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, 'label' => $post->post_title ); return $data; } /** * 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 ) || $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; } /** * Set the template for a page. * * @param string $template * @param integer $post_id */ public function handle_template( $template, $post_id ) { if ( in_array( $template, array_keys( wp_get_theme()->get_page_templates( get_post( $post_id ) ) ) ) ) { update_post_meta( $post_id, '_wp_page_template', $template ); } else { update_post_meta( $post_id, '_wp_page_template', '' ); } } /** * 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() ) ); } /** * 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 $excerpt; } /** * 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 ); } 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; } /** * Prepare a single post for create or update. * * @param WP_REST_Request $request Request object. * * @return WP_Error|stdClass $prepared_post Post object. */ protected function prepare_item_for_database( $request ) { $prepared_post = new stdClass; // ID. if ( isset( $request['id'] ) ) { $prepared_post->ID = absint( $request['id'] ); } $schema = $this->get_item_schema(); // Post title. if ( ! empty( $schema['properties']['title'] ) && isset( $request['title'] ) ) { if ( is_string( $request['title'] ) ) { $prepared_post->post_title = wp_filter_post_kses( $request['title'] ); } elseif ( ! empty( $request['title']['raw'] ) ) { $prepared_post->post_title = wp_filter_post_kses( $request['title']['raw'] ); } } // Post content. if ( ! empty( $schema['properties']['content'] ) && isset( $request['content'] ) ) { if ( is_string( $request['content'] ) ) { $prepared_post->post_content = wp_filter_post_kses( $request['content'] ); } elseif ( isset( $request['content']['raw'] ) ) { $prepared_post->post_content = wp_filter_post_kses( $request['content']['raw'] ); } } // Post excerpt. if ( ! empty( $schema['properties']['excerpt'] ) && isset( $request['excerpt'] ) ) { if ( is_string( $request['excerpt'] ) ) { $prepared_post->post_excerpt = wp_filter_post_kses( $request['excerpt'] ); } elseif ( isset( $request['excerpt']['raw'] ) ) { $prepared_post->post_excerpt = wp_filter_post_kses( $request['excerpt']['raw'] ); } } // Post type. if ( empty( $request['id'] ) ) { // Creating new post, use default type for the controller. $prepared_post->post_type = $this->post_type; } else { // Updating a post, use previous type. $prepared_post->post_type = get_post_type( $request['id'] ); } $post_type = get_post_type_object( $prepared_post->post_type ); // Post status. if ( isset( $request['status'] ) ) { $status = $this->handle_status_param( $request['status'], $post_type ); if ( is_wp_error( $status ) ) { return $status; } $prepared_post->post_status = $status; } // Post date. if ( ! empty( $request['date'] ) ) { $date_data = rest_get_date_with_gmt( $request['date'] ); if ( ! empty( $date_data ) ) { list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; } } elseif ( ! empty( $request['date_gmt'] ) ) { $date_data = rest_get_date_with_gmt( $request['date_gmt'], true ); if ( ! empty( $date_data ) ) { list( $prepared_post->post_date, $prepared_post->post_date_gmt ) = $date_data; } } // Post slug. if ( isset( $request['slug'] ) ) { $prepared_post->post_name = $request['slug']; } // Author if ( ! empty( $schema['properties']['author'] ) && ! empty( $request['author'] ) ) { $post_author = (int) $request['author']; if ( get_current_user_id() !== $post_author ) { $user_obj = get_userdata( $post_author ); if ( ! $user_obj ) { return new WP_Error( 'rest_invalid_author', __( 'Invalid author id.' ), array( 'status' => 400 ) ); } } $prepared_post->post_author = $post_author; } // Post password. if ( isset( $request['password'] ) && '' !== $request['password'] ) { $prepared_post->post_password = $request['password']; if ( ! empty( $schema['properties']['sticky'] ) && ! empty( $request['sticky'] ) ) { return new WP_Error( 'rest_invalid_field', __( 'A post can not be sticky and have a password.' ), array( 'status' => 400 ) ); } if ( ! empty( $prepared_post->ID ) && is_sticky( $prepared_post->ID ) ) { return new WP_Error( 'rest_invalid_field', __( 'A sticky post can not be password protected.' ), array( 'status' => 400 ) ); } } if ( ! empty( $request['sticky'] ) ) { if ( ! empty( $prepared_post->ID ) && post_password_required( $prepared_post->ID ) ) { return new WP_Error( 'rest_invalid_field', __( 'A password protected post can not be set to sticky.' ), array( 'status' => 400 ) ); } } // Parent. if ( ! empty( $schema['properties']['parent'] ) && ! empty( $request['parent'] ) ) { $parent = get_post( (int) $request['parent'] ); if ( empty( $parent ) ) { return new WP_Error( 'rest_post_invalid_id', __( 'Invalid post parent id.' ), array( 'status' => 400 ) ); } $prepared_post->post_parent = (int) $parent->ID; } // Menu order. if ( ! empty( $schema['properties']['menu_order'] ) && isset( $request['menu_order'] ) ) { $prepared_post->menu_order = (int) $request['menu_order']; } // Comment status. if ( ! empty( $schema['properties']['comment_status'] ) && ! empty( $request['comment_status'] ) ) { $prepared_post->comment_status = $request['comment_status']; } // Ping status. if ( ! empty( $schema['properties']['ping_status'] ) && ! empty( $request['ping_status'] ) ) { $prepared_post->ping_status = $request['ping_status']; } /** * Filter a post before it is inserted via the REST API. * * The dynamic portion of the hook name, $this->post_type, refers to post_type of the post being * prepared for insertion. * * @param stdClass $prepared_post An object representing a single post prepared * for inserting or updating the database. * @param WP_REST_Request $request Request object. */ return apply_filters( "rest_pre_insert_{$this->post_type}", $prepared_post, $request ); } /** * 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, ); $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 ); } /** * 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; } /** * Determine the featured media based on a request param. * * @param int $featured_media * @param int $post_id * * @return bool|WP_Error */ protected function handle_featured_media( $featured_media, $post_id ) { $featured_media = (int) $featured_media; if ( $featured_media ) { $result = set_post_thumbnail( $post_id, $featured_media ); if ( $result ) { return true; } else { return new WP_Error( 'rest_invalid_featured_media', __( 'Invalid featured media id.' ), array( 'status' => 400 ) ); } } else { return delete_post_thumbnail( $post_id ); } } /** * Update the post's terms from a REST request. * * @param int $post_id The post ID to update the terms form. * @param WP_REST_Request $request The request object with post and terms data. * * @return null|WP_Error WP_Error on an error assigning any of ther terms. */ protected function handle_terms( $post_id, $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 ( ! isset( $request[ $base ] ) ) { continue; } $terms = array_map( 'absint', $request[ $base ] ); $result = wp_set_object_terms( $post_id, $terms, $taxonomy->name ); if ( is_wp_error( $result ) ) { return $result; } } } /** * Check if we can create a post. * * @param object $post Post object. * * @return boolean Can we create it?. */ protected function check_create_permission( $post ) { $post_type = get_post_type_object( $post->post_type ); if ( ! $this->check_is_post_type_allowed( $post_type ) ) { return false; } return current_user_can( $post_type->cap->create_posts ); } /** * Check if we can delete a post. * * @param object $post Post object. * * @return boolean Can we delete it? */ protected function check_delete_permission( $post ) { $post_type = get_post_type_object( $post->post_type ); if ( ! $this->check_is_post_type_allowed( $post_type ) ) { return false; } return current_user_can( $post_type->cap->delete_post, $post->ID ); } /** * 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( '/appmaker-wp/v2/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( '/appmaker-wp/v2/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( '/appmaker-wp/v2/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( 'appmaker-wp/v2/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( 'appmaker-wp/v2/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( 'appmaker-wp/v2/' . $tax_base ) ); $links['https://api.w.org/term'][] = array( 'href' => $terms_url, 'taxonomy' => $tax, 'embeddable' => true, ); } } return $links; } }