REST API: Introduce the Content API endpoints.

REST API endpoints for your WordPress content. These endpoints provide machine-readable external access to your WordPress site with a clear, standards-driven interface, allowing new and innovative apps for interacting with your site. These endpoints support all of the following:
- Posts: Read and write access to all post data, for all types of post-based data, including pages and media.
- Comments: Read and write access to all comment data. This includes pingbacks and trackbacks.
- Terms: Read and write access to all term data.
- Users: Read and write access to all user data. This includes public access to some data for post authors.
- Meta: Read and write access to metadata for posts, comments, terms, and users, on an opt-in basis from plugins.
- Settings: Read and write access to settings, on an opt-in basis from plugins and core. This enables API management of key site content values that are technically stored in options, such as site title and byline.

Love your REST API, WordPress!  The infrastructure says, "Let's do lunch!" but the content API endpoints say, "You're paying!"

Props rmccue, rachelbaker, danielbachhuber, joehoyle, adamsilverstein, afurculita, ahmadawais, airesvsg, alisspers, antisilent, apokalyptik, artoliukkonen, attitude, boonebgorges, bradyvercher, brianhogg, caseypatrickdriscoll, chopinbach, chredd, christianesperar, chrisvanpatten, claudiolabarbera, claudiosmweb, cmmarslender, codebykat, coderkevin, codfish, codonnell822, daggerhart, danielpunkass, davidbhayes, delphinus, desrosj, dimadin, dotancohen, DrewAPicture, Dudo1985, duncanjbrown, eherman24, eivhyl, eliorivero, elyobo, en-alis, ericandrewlewis, ericpedia, evansobkowicz, fjarrett, frozzare, georgestephanis, greatislander, guavaworks, hideokamoto, hkdobrev, hubdotcom, hurtige, iandunn, ircrash, ironpaperweight, iseulde, Japh, jaredcobb, JDGrimes, jdolan, jdoubleu, jeremyfelt, jimt, jjeaton, jmusal, jnylen0, johanmynhardt, johnbillion, jonathanbardo, jorbin, joshkadis, JPry, jshreve, jtsternberg, JustinSainton, kacperszurek, kadamwhite, kalenjohnson, kellbot, kjbenk, kokarn, krogsgard, kuchenundkakao, kuldipem, kwight, lgedeon, lukepettway, mantismamita, markoheijnen, matrixik, mattheu, mauteri, maxcutler, mayukojpn, michael-arestad, miyauchi, mjbanks, modemlooper, mrbobbybryant, NateWr, nathanrice, netweb, NikV, nullvariable, oskosk, oso96_2000, oxymoron, pcfreak30, pento, peterwilsoncc, Pezzab, phh, pippinsplugins, pjgalbraith, pkevan, pollyplummer, pushred, quasel, QWp6t, schlessera, schrapel, Shelob9, shprink, simonlampen, Soean, solal, tapsboy, tfrommen, tharsheblows, thenbrent, tierra, tlovett1, tnegri, tobych, Toddses, toro_unit, traversal, vanillalounge, vishalkakadiya, wanecek, web2style, webbgaraget, websupporter, westonruter, whyisjake, wonderboymusic, wpsmith, xknown, zyphonic.
Fixes #38373.

git-svn-id: https://develop.svn.wordpress.org/trunk@38832 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
Rachel Baker 2016-10-20 02:54:12 +00:00
parent de3c65804a
commit ede099a704
47 changed files with 21284 additions and 1 deletions

View File

@ -374,7 +374,9 @@ add_action( 'edit_user_created_user', 'wp_send_new_user_notifications', 10, 2 );
// REST API actions.
add_action( 'init', 'rest_api_init' );
add_action( 'rest_api_init', 'rest_api_default_filters', 10, 1 );
add_action( 'rest_api_init', 'rest_api_default_filters', 10, 1 );
add_action( 'rest_api_init', 'register_initial_settings', 10 );
add_action( 'rest_api_init', 'create_initial_rest_routes', 99 );
add_action( 'parse_request', 'rest_api_loaded' );
/**

View File

@ -3429,6 +3429,26 @@ function wp_parse_id_list( $list ) {
return array_unique(array_map('absint', $list));
}
/**
* Clean up an array, comma- or space-separated list of slugs.
*
* @since 4.7.0
*
* @param array|string $list List of slugs.
* @return array Sanitized array of slugs.
*/
function wp_parse_slug_list( $list ) {
if ( ! is_array( $list ) ) {
$list = preg_split( '/[\s,]+/', $list );
}
foreach ( $list as $key => $value ) {
$list[ $key ] = sanitize_title( $value );
}
return array_unique( $list );
}
/**
* Extract a slice of an array, given a list of keys.
*

1337
src/wp-includes/js/wp-api.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1707,6 +1707,115 @@ function set_site_transient( $transient, $value, $expiration = 0 ) {
return $result;
}
/**
* Register default settings available in WordPress.
*
* The settings registered here are primarily useful for the REST API, so this
* does not encompass all settings available in WordPress.
*
* @since 4.7.0
*/
function register_initial_settings() {
register_setting( 'general', 'blogname', array(
'show_in_rest' => array(
'name' => 'title',
),
'type' => 'string',
'description' => __( 'Site title.' ),
) );
register_setting( 'general', 'blogdescription', array(
'show_in_rest' => array(
'name' => 'description',
),
'type' => 'string',
'description' => __( 'Site description.' ),
) );
register_setting( 'general', 'siteurl', array(
'show_in_rest' => array(
'name' => 'url',
'schema' => array(
'format' => 'uri',
),
),
'type' => 'string',
'description' => __( 'Site URL.' ),
) );
register_setting( 'general', 'admin_email', array(
'show_in_rest' => array(
'name' => 'email',
'schema' => array(
'format' => 'email',
),
),
'type' => 'string',
'description' => __( 'This address is used for admin purposes. If you change this we will send you an email at your new address to confirm it. The new address will not become active until confirmed.' ),
) );
register_setting( 'general', 'timezone_string', array(
'show_in_rest' => array(
'name' => 'timezone',
),
'type' => 'string',
'description' => __( 'A city in the same timezone as you.' ),
) );
register_setting( 'general', 'date_format', array(
'show_in_rest' => true,
'type' => 'string',
'description' => __( 'A date format for all date strings.' ),
) );
register_setting( 'general', 'time_format', array(
'show_in_rest' => true,
'type' => 'string',
'description' => __( 'A time format for all time strings.' ),
) );
register_setting( 'general', 'start_of_week', array(
'show_in_rest' => true,
'type' => 'number',
'description' => __( 'A day number of the week that the week should start on.' ),
) );
register_setting( 'general', 'WPLANG', array(
'show_in_rest' => array(
'name' => 'language',
),
'type' => 'string',
'description' => __( 'WordPress locale code.' ),
'default' => 'en_US',
) );
register_setting( 'writing', 'use_smilies', array(
'show_in_rest' => true,
'type' => 'boolean',
'description' => __( 'Convert emoticons like :-) and :-P to graphics on display.' ),
'default' => true,
) );
register_setting( 'writing', 'default_category', array(
'show_in_rest' => true,
'type' => 'number',
'description' => __( 'Default category.' ),
) );
register_setting( 'writing', 'default_post_format', array(
'show_in_rest' => true,
'type' => 'string',
'description' => __( 'Default post format.' ),
) );
register_setting( 'reading', 'posts_per_page', array(
'show_in_rest' => true,
'type' => 'number',
'description' => __( 'Blog pages show at most.' ),
'default' => 10,
) );
}
/**
* Register a setting and its data.
*

View File

@ -33,6 +33,9 @@ function create_initial_post_types() {
'query_var' => false,
'delete_with_user' => true,
'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions', 'post-formats' ),
'show_in_rest' => true,
'rest_base' => 'posts',
'rest_controller_class' => 'WP_REST_Posts_Controller',
) );
register_post_type( 'page', array(
@ -51,6 +54,9 @@ function create_initial_post_types() {
'query_var' => false,
'delete_with_user' => true,
'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields', 'comments', 'revisions' ),
'show_in_rest' => true,
'rest_base' => 'pages',
'rest_controller_class' => 'WP_REST_Posts_Controller',
) );
register_post_type( 'attachment', array(
@ -76,6 +82,9 @@ function create_initial_post_types() {
'show_in_nav_menus' => false,
'delete_with_user' => true,
'supports' => array( 'title', 'author', 'comments' ),
'show_in_rest' => true,
'rest_base' => 'media',
'rest_controller_class' => 'WP_REST_Attachments_Controller',
) );
add_post_type_support( 'attachment:audio', 'thumbnail' );
add_post_type_support( 'attachment:video', 'thumbnail' );

View File

@ -70,6 +70,48 @@ function register_rest_route( $namespace, $route, $args = array(), $override = f
return true;
}
/**
* Registers a new field on an existing WordPress object type.
*
* @since 4.7.0
*
* @global array $wp_rest_additional_fields Holds registered fields, organized
* by object type.
*
* @param string|array $object_type Object(s) the field is being registered
* to, "post"|"term"|"comment" etc.
* @param string $attribute The attribute name.
* @param array $args {
* Optional. An array of arguments used to handle the registered field.
*
* @type string|array|null $get_callback Optional. The callback function used to retrieve the field
* value. Default is 'null', the field will not be returned in
* the response.
* @type string|array|null $update_callback Optional. The callback function used to set and update the
* field value. Default is 'null', the value cannot be set or
* updated.
* @type string|array|null $schema Optional. The callback function used to create the schema for
* this field. Default is 'null', no schema entry will be returned.
* }
*/
function register_rest_field( $object_type, $attribute, $args = array() ) {
$defaults = array(
'get_callback' => null,
'update_callback' => null,
'schema' => null,
);
$args = wp_parse_args( $args, $defaults );
global $wp_rest_additional_fields;
$object_types = (array) $object_type;
foreach ( $object_types as $object_type ) {
$wp_rest_additional_fields[ $object_type ][ $attribute ] = $args;
}
}
/**
* Registers rewrite rules for the API.
*
@ -124,6 +166,71 @@ function rest_api_default_filters() {
add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 );
}
/**
* Registers default REST API routes.
*
* @since 4.7.0
*/
function create_initial_rest_routes() {
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
$class = ! empty( $post_type->rest_controller_class ) ? $post_type->rest_controller_class : 'WP_REST_Posts_Controller';
if ( ! class_exists( $class ) ) {
continue;
}
$controller = new $class( $post_type->name );
if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
continue;
}
$controller->register_routes();
if ( post_type_supports( $post_type->name, 'revisions' ) ) {
$revisions_controller = new WP_REST_Revisions_Controller( $post_type->name );
$revisions_controller->register_routes();
}
}
// Post types.
$controller = new WP_REST_Post_Types_Controller;
$controller->register_routes();
// Post statuses.
$controller = new WP_REST_Post_Statuses_Controller;
$controller->register_routes();
// Taxonomies.
$controller = new WP_REST_Taxonomies_Controller;
$controller->register_routes();
// Terms.
foreach ( get_taxonomies( array( 'show_in_rest' => true ), 'object' ) as $taxonomy ) {
$class = ! empty( $taxonomy->rest_controller_class ) ? $taxonomy->rest_controller_class : 'WP_REST_Terms_Controller';
if ( ! class_exists( $class ) ) {
continue;
}
$controller = new $class( $taxonomy->name );
if ( ! is_subclass_of( $controller, 'WP_REST_Controller' ) ) {
continue;
}
$controller->register_routes();
}
// Users.
$controller = new WP_REST_Users_Controller;
$controller->register_routes();
// Comments.
$controller = new WP_REST_Comments_Controller;
$controller->register_routes();
// Settings.
$controller = new WP_REST_Settings_Controller;
$controller->register_routes();
}
/**
* Loads the REST API.
*
@ -683,3 +790,296 @@ function rest_get_date_with_gmt( $date, $force_utc = false ) {
return array( $local, $utc );
}
/**
* Returns a contextual HTTP error code for authorization failure.
*
* @since 4.7.0
*
* @return integer 401 if the user is not logged in, 403 if the user is logged in.
*/
function rest_authorization_required_code() {
return is_user_logged_in() ? 403 : 401;
}
/**
* Validate a request argument based on details registered to the route.
*
* @since 4.7.0
*
* @param mixed $value
* @param WP_REST_Request $request
* @param string $param
* @return WP_Error|boolean
*/
function rest_validate_request_arg( $value, $request, $param ) {
$attributes = $request->get_attributes();
if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
return true;
}
$args = $attributes['args'][ $param ];
if ( ! empty( $args['enum'] ) ) {
if ( ! in_array( $value, $args['enum'], true ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: list of valid values */ __( '%1$s is not one of %2$s.' ), $param, implode( ', ', $args['enum'] ) ) );
}
}
if ( 'integer' === $args['type'] && ! is_numeric( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'integer' ) );
}
if ( 'boolean' === $args['type'] && ! rest_is_boolean( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $value, 'boolean' ) );
}
if ( 'string' === $args['type'] && ! is_string( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: type name */ __( '%1$s is not of type %2$s.' ), $param, 'string' ) );
}
if ( isset( $args['format'] ) ) {
switch ( $args['format'] ) {
case 'date-time' :
if ( ! rest_parse_date( $value ) ) {
return new WP_Error( 'rest_invalid_date', __( 'The date you provided is invalid.' ) );
}
break;
case 'email' :
if ( ! is_email( $value ) ) {
return new WP_Error( 'rest_invalid_email', __( 'The email address you provided is invalid.' ) );
}
break;
case 'ipv4' :
if ( ! rest_is_ip_address( $value ) ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%s is not a valid IP address.' ), $value ) );
}
break;
}
}
if ( in_array( $args['type'], array( 'numeric', 'integer' ), true ) && ( isset( $args['minimum'] ) || isset( $args['maximum'] ) ) ) {
if ( isset( $args['minimum'] ) && ! isset( $args['maximum'] ) ) {
if ( ! empty( $args['exclusiveMinimum'] ) && $value <= $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (exclusive)' ), $param, $args['minimum'] ) );
} elseif ( empty( $args['exclusiveMinimum'] ) && $value < $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be greater than %2$d (inclusive)' ), $param, $args['minimum'] ) );
}
} elseif ( isset( $args['maximum'] ) && ! isset( $args['minimum'] ) ) {
if ( ! empty( $args['exclusiveMaximum'] ) && $value >= $args['maximum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (exclusive)' ), $param, $args['maximum'] ) );
} elseif ( empty( $args['exclusiveMaximum'] ) && $value > $args['maximum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( __( '%1$s must be less than %2$d (inclusive)' ), $param, $args['maximum'] ) );
}
} elseif ( isset( $args['maximum'] ) && isset( $args['minimum'] ) ) {
if ( ! empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
if ( $value >= $args['maximum'] || $value <= $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (exclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
}
} elseif ( empty( $args['exclusiveMinimum'] ) && ! empty( $args['exclusiveMaximum'] ) ) {
if ( $value >= $args['maximum'] || $value < $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (inclusive) and %3$d (exclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
}
} elseif ( ! empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
if ( $value > $args['maximum'] || $value <= $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (exclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
}
} elseif ( empty( $args['exclusiveMinimum'] ) && empty( $args['exclusiveMaximum'] ) ) {
if ( $value > $args['maximum'] || $value < $args['minimum'] ) {
return new WP_Error( 'rest_invalid_param', sprintf( /* translators: 1: parameter, 2: minimum number, 3: maximum number */ __( '%1$s must be between %2$d (inclusive) and %3$d (inclusive)' ), $param, $args['minimum'], $args['maximum'] ) );
}
}
}
}
return true;
}
/**
* Sanitize a request argument based on details registered to the route.
*
* @since 4.7.0
*
* @param mixed $value
* @param WP_REST_Request $request
* @param string $param
* @return mixed
*/
function rest_sanitize_request_arg( $value, $request, $param ) {
$attributes = $request->get_attributes();
if ( ! isset( $attributes['args'][ $param ] ) || ! is_array( $attributes['args'][ $param ] ) ) {
return $value;
}
$args = $attributes['args'][ $param ];
if ( 'integer' === $args['type'] ) {
return (int) $value;
}
if ( 'boolean' === $args['type'] ) {
return rest_sanitize_boolean( $value );
}
if ( isset( $args['format'] ) ) {
switch ( $args['format'] ) {
case 'date-time' :
return sanitize_text_field( $value );
case 'email' :
/*
* sanitize_email() validates, which would be unexpected
*/
return sanitize_text_field( $value );
case 'uri' :
return esc_url_raw( $value );
case 'ipv4' :
return sanitize_text_field( $value );
}
}
return $value;
}
/**
* Parse a request argument based on details registered to the route.
*
* Runs a validation check and sanitizes the value, primarily to be used via
* the `sanitize_callback` arguments in the endpoint args registration.
*
* @since 4.7.0
*
* @param mixed $value
* @param WP_REST_Request $request
* @param string $param
* @return mixed
*/
function rest_parse_request_arg( $value, $request, $param ) {
$is_valid = rest_validate_request_arg( $value, $request, $param );
if ( is_wp_error( $is_valid ) ) {
return $is_valid;
}
$value = rest_sanitize_request_arg( $value, $request, $param );
return $value;
}
/**
* Determines if a IPv4 address is valid.
*
* Does not handle IPv6 addresses.
*
* @since 4.7.0
*
* @param string $ipv4 IP 32-bit address.
* @return string|false The valid IPv4 address, otherwise false.
*/
function rest_is_ip_address( $ipv4 ) {
$pattern = '/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/';
if ( ! preg_match( $pattern, $ipv4 ) ) {
return false;
}
return $ipv4;
}
/**
* Changes a boolean-like value into the proper boolean value.
*
* @since 4.7.0
*
* @param bool|string|int $value The value being evaluated.
* @return boolean Returns the proper associated boolean value.
*/
function rest_sanitize_boolean( $value ) {
// String values are translated to `true`; make sure 'false' is false.
if ( is_string( $value ) ) {
$value = strtolower( $value );
if ( in_array( $value, array( 'false', '0' ), true ) ) {
$value = false;
}
}
// Everything else will map nicely to boolean.
return (boolean) $value;
}
/**
* Determines if a given value is boolean-like.
*
* @since 4.7.0
*
* @param bool|string $maybe_bool The value being evaluated.
* @return boolean True if a boolean, otherwise false.
*/
function rest_is_boolean( $maybe_bool ) {
if ( is_bool( $maybe_bool ) ) {
return true;
}
if ( is_string( $maybe_bool ) ) {
$maybe_bool = strtolower( $maybe_bool );
$valid_boolean_values = array(
'false',
'true',
'0',
'1',
);
return in_array( $maybe_bool, $valid_boolean_values, true );
}
if ( is_int( $maybe_bool ) ) {
return in_array( $maybe_bool, array( 0, 1 ), true );
}
return false;
}
/**
* Retrieves the avatar urls in various sizes based on a given email address.
*
* @since 4.7.0
*
* @see get_avatar_url()
*
* @param string $email Email address.
* @return array $urls Gravatar url for each size.
*/
function rest_get_avatar_urls( $email ) {
$avatar_sizes = rest_get_avatar_sizes();
$urls = array();
foreach ( $avatar_sizes as $size ) {
$urls[ $size ] = get_avatar_url( $email, array( 'size' => $size ) );
}
return $urls;
}
/**
* Retrieves the pixel sizes for avatars.
*
* @since 4.7.0
*
* @return array List of pixel sizes for avatars. Default `[ 24, 48, 96 ]`.
*/
function rest_get_avatar_sizes() {
/**
* Filter the REST avatar sizes.
*
* Use this filter to adjust the array of sizes returned by the
* `rest_get_avatar_sizes` function.
*
* @since 4.4.0
*
* @param array $sizes An array of int values that are the pixel sizes for avatars.
* Default `[ 24, 48, 96 ]`.
*/
return apply_filters( 'rest_avatar_sizes', array( 24, 48, 96 ) );
}

View File

@ -0,0 +1,607 @@
<?php
class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
/**
* Determine the allowed query_vars for a get_items() response and
* prepare for WP_Query.
*
* @param array $prepared_args Optional. Array of prepared arguments.
* @param WP_REST_Request $request Optional. Request to prepare items for.
* @return array Array of query arguments.
*/
protected function prepare_items_query( $prepared_args = array(), $request = null ) {
$query_args = parent::prepare_items_query( $prepared_args, $request );
if ( empty( $query_args['post_status'] ) || ! in_array( $query_args['post_status'], array( 'inherit', 'private', 'trash' ), true ) ) {
$query_args['post_status'] = 'inherit';
}
$media_types = $this->get_media_types();
if ( ! empty( $request['media_type'] ) && isset( $media_types[ $request['media_type'] ] ) ) {
$query_args['post_mime_type'] = $media_types[ $request['media_type'] ];
}
if ( ! empty( $request['mime_type'] ) ) {
$parts = explode( '/', $request['mime_type'] );
if ( isset( $media_types[ $parts[0] ] ) && in_array( $request['mime_type'], $media_types[ $parts[0] ], true ) ) {
$query_args['post_mime_type'] = $request['mime_type'];
}
}
return $query_args;
}
/**
* Check if a given request has access to create an attachment.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|true Boolean true if the attachment may be created, or a WP_Error if not.
*/
public function create_item_permissions_check( $request ) {
$ret = parent::create_item_permissions_check( $request );
if ( ! $ret || is_wp_error( $ret ) ) {
return $ret;
}
if ( ! current_user_can( 'upload_files' ) ) {
return new WP_Error( 'rest_cannot_create', __( 'Sorry, you are not allowed to upload media on this site.' ), array( 'status' => 400 ) );
}
// Attaching media to a post requires ability to edit said post.
if ( ! empty( $request['post'] ) ) {
$parent = $this->get_post( (int) $request['post'] );
$post_parent_type = get_post_type_object( $parent->post_type );
if ( ! current_user_can( $post_parent_type->cap->edit_post, $request['post'] ) ) {
return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to upload media to this resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
}
return true;
}
/**
* Create a single attachment.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response Response object on success, WP_Error object on failure.
*/
public function create_item( $request ) {
if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) {
return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
}
// Get the file via $_FILES or raw data
$files = $request->get_file_params();
$headers = $request->get_headers();
if ( ! empty( $files ) ) {
$file = $this->upload_from_file( $files, $headers );
} else {
$file = $this->upload_from_data( $request->get_body(), $headers );
}
if ( is_wp_error( $file ) ) {
return $file;
}
$name = basename( $file['file'] );
$name_parts = pathinfo( $name );
$name = trim( substr( $name, 0, -(1 + strlen( $name_parts['extension'] ) ) ) );
$url = $file['url'];
$type = $file['type'];
$file = $file['file'];
// use image exif/iptc data for title and caption defaults if possible
// @codingStandardsIgnoreStart
$image_meta = @wp_read_image_metadata( $file );
// @codingStandardsIgnoreEnd
if ( ! empty( $image_meta ) ) {
if ( empty( $request['title'] ) && trim( $image_meta['title'] ) && ! is_numeric( sanitize_title( $image_meta['title'] ) ) ) {
$request['title'] = $image_meta['title'];
}
if ( empty( $request['caption'] ) && trim( $image_meta['caption'] ) ) {
$request['caption'] = $image_meta['caption'];
}
}
$attachment = $this->prepare_item_for_database( $request );
$attachment->file = $file;
$attachment->post_mime_type = $type;
$attachment->guid = $url;
if ( empty( $attachment->post_title ) ) {
$attachment->post_title = preg_replace( '/\.[^.]+$/', '', basename( $file ) );
}
$id = wp_insert_post( $attachment, true );
if ( is_wp_error( $id ) ) {
if ( 'db_update_error' === $id->get_error_code() ) {
$id->add_data( array( 'status' => 500 ) );
} else {
$id->add_data( array( 'status' => 400 ) );
}
return $id;
}
$attachment = $this->get_post( $id );
// Include admin functions to get access to wp_generate_attachment_metadata().
require_once ABSPATH . 'wp-admin/includes/admin.php';
wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $file ) );
if ( isset( $request['alt_text'] ) ) {
update_post_meta( $id, '_wp_attachment_image_alt', sanitize_text_field( $request['alt_text'] ) );
}
$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $attachment, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $id ) ) );
/**
* Fires after a single attachment is created or updated via the REST API.
*
* @param object $attachment Inserted attachment.
* @param WP_REST_Request $request The request sent to the API.
* @param boolean $creating True when creating an attachment, false when updating.
*/
do_action( 'rest_insert_attachment', $attachment, $request, true );
return $response;
}
/**
* Update a single post.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response Response object on success, WP_Error object on failure.
*/
public function update_item( $request ) {
if ( ! empty( $request['post'] ) && in_array( get_post_type( $request['post'] ), array( 'revision', 'attachment' ), true ) ) {
return new WP_Error( 'rest_invalid_param', __( 'Invalid parent type.' ), array( 'status' => 400 ) );
}
$response = parent::update_item( $request );
if ( is_wp_error( $response ) ) {
return $response;
}
$response = rest_ensure_response( $response );
$data = $response->get_data();
if ( isset( $request['alt_text'] ) ) {
update_post_meta( $data['id'], '_wp_attachment_image_alt', $request['alt_text'] );
}
$attachment = $this->get_post( $request['id'] );
$fields_update = $this->update_additional_fields_for_object( $attachment, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $attachment, $request );
$response = rest_ensure_response( $response );
/* This action is documented in lib/endpoints/class-wp-rest-attachments-controller.php */
do_action( 'rest_insert_attachment', $data, $request, false );
return $response;
}
/**
* Prepare a single attachment for create or update.
*
* @param WP_REST_Request $request Request object.
* @return WP_Error|stdClass $prepared_attachment Post object.
*/
protected function prepare_item_for_database( $request ) {
$prepared_attachment = parent::prepare_item_for_database( $request );
if ( isset( $request['caption'] ) ) {
$prepared_attachment->post_excerpt = $request['caption'];
}
if ( isset( $request['description'] ) ) {
$prepared_attachment->post_content = $request['description'];
}
if ( isset( $request['post'] ) ) {
$prepared_attachment->post_parent = (int) $request['post'];
}
return $prepared_attachment;
}
/**
* Prepare a single attachment output for response.
*
* @param WP_Post $post Post object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response Response object.
*/
public function prepare_item_for_response( $post, $request ) {
$response = parent::prepare_item_for_response( $post, $request );
$data = $response->get_data();
$data['alt_text'] = get_post_meta( $post->ID, '_wp_attachment_image_alt', true );
$data['caption'] = $post->post_excerpt;
$data['description'] = $post->post_content;
$data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file';
$data['mime_type'] = $post->post_mime_type;
$data['media_details'] = wp_get_attachment_metadata( $post->ID );
$data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null;
$data['source_url'] = wp_get_attachment_url( $post->ID );
// Ensure empty details is an empty object.
if ( empty( $data['media_details'] ) ) {
$data['media_details'] = new stdClass;
} elseif ( ! empty( $data['media_details']['sizes'] ) ) {
foreach ( $data['media_details']['sizes'] as $size => &$size_data ) {
if ( isset( $size_data['mime-type'] ) ) {
$size_data['mime_type'] = $size_data['mime-type'];
unset( $size_data['mime-type'] );
}
// Use the same method image_downsize() does.
$image_src = wp_get_attachment_image_src( $post->ID, $size );
if ( ! $image_src ) {
continue;
}
$size_data['source_url'] = $image_src[0];
}
$full_src = wp_get_attachment_image_src( $post->ID, 'full' );
if ( ! empty( $full_src ) ) {
$data['media_details']['sizes']['full'] = array(
'file' => wp_basename( $full_src[0] ),
'width' => $full_src[1],
'height' => $full_src[2],
'mime_type' => $post->post_mime_type,
'source_url' => $full_src[0],
);
}
} else {
$data['media_details']['sizes'] = new stdClass;
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$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 an attachment returned from the API.
*
* Allows modification of the attachment right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param WP_Post $post The original attachment post.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'rest_prepare_attachment', $response, $post, $request );
}
/**
* Get the Attachment's schema, conforming to JSON Schema.
*
* @return array Item schema as an array.
*/
public function get_item_schema() {
$schema = parent::get_item_schema();
$schema['properties']['alt_text'] = array(
'description' => __( 'Alternative text to display when resource is not displayed.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
);
$schema['properties']['caption'] = array(
'description' => __( 'The caption for the resource.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_filter_post_kses',
),
);
$schema['properties']['description'] = array(
'description' => __( 'The description for the resource.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_filter_post_kses',
),
);
$schema['properties']['media_type'] = array(
'description' => __( 'Type of resource.' ),
'type' => 'string',
'enum' => array( 'image', 'file' ),
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
);
$schema['properties']['mime_type'] = array(
'description' => __( 'MIME type of resource.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
);
$schema['properties']['media_details'] = array(
'description' => __( 'Details about the resource file, specific to its type.' ),
'type' => 'object',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
);
$schema['properties']['post'] = array(
'description' => __( 'The id for the associated post of the resource.' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
);
$schema['properties']['source_url'] = array(
'description' => __( 'URL to the original resource file.' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
);
return $schema;
}
/**
* Handle an upload via raw POST data.
*
* @param array $data Supplied file data.
* @param array $headers HTTP headers from the request.
* @return array|WP_Error Data from {@see wp_handle_sideload()}.
*/
protected function upload_from_data( $data, $headers ) {
if ( empty( $data ) ) {
return new WP_Error( 'rest_upload_no_data', __( 'No data supplied.' ), array( 'status' => 400 ) );
}
if ( empty( $headers['content_type'] ) ) {
return new WP_Error( 'rest_upload_no_content_type', __( 'No Content-Type supplied.' ), array( 'status' => 400 ) );
}
if ( empty( $headers['content_disposition'] ) ) {
return new WP_Error( 'rest_upload_no_content_disposition', __( 'No Content-Disposition supplied.' ), array( 'status' => 400 ) );
}
$filename = self::get_filename_from_disposition( $headers['content_disposition'] );
if ( empty( $filename ) ) {
return new WP_Error( 'rest_upload_invalid_disposition', __( 'Invalid Content-Disposition supplied. Content-Disposition needs to be formatted as `attachment; filename="image.png"` or similar.' ), array( 'status' => 400 ) );
}
if ( ! empty( $headers['content_md5'] ) ) {
$content_md5 = array_shift( $headers['content_md5'] );
$expected = trim( $content_md5 );
$actual = md5( $data );
if ( $expected !== $actual ) {
return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected.' ), array( 'status' => 412 ) );
}
}
// Get the content-type.
$type = array_shift( $headers['content_type'] );
/** Include admin functions to get access to wp_tempnam() and wp_handle_sideload() */
require_once ABSPATH . 'wp-admin/includes/admin.php';
// Save the file.
$tmpfname = wp_tempnam( $filename );
$fp = fopen( $tmpfname, 'w+' );
if ( ! $fp ) {
return new WP_Error( 'rest_upload_file_error', __( 'Could not open file handle.' ), array( 'status' => 500 ) );
}
fwrite( $fp, $data );
fclose( $fp );
// Now, sideload it in.
$file_data = array(
'error' => null,
'tmp_name' => $tmpfname,
'name' => $filename,
'type' => $type,
);
$overrides = array(
'test_form' => false,
);
$sideloaded = wp_handle_sideload( $file_data, $overrides );
if ( isset( $sideloaded['error'] ) ) {
// @codingStandardsIgnoreStart
@unlink( $tmpfname );
// @codingStandardsIgnoreEnd
return new WP_Error( 'rest_upload_sideload_error', $sideloaded['error'], array( 'status' => 500 ) );
}
return $sideloaded;
}
/**
* Parse filename from a Content-Disposition header value.
*
* As per RFC6266:
*
* content-disposition = "Content-Disposition" ":"
* disposition-type *( ";" disposition-parm )
*
* disposition-type = "inline" | "attachment" | disp-ext-type
* ; case-insensitive
* disp-ext-type = token
*
* disposition-parm = filename-parm | disp-ext-parm
*
* filename-parm = "filename" "=" value
* | "filename*" "=" ext-value
*
* disp-ext-parm = token "=" value
* | ext-token "=" ext-value
* ext-token = <the characters in token, followed by "*">
*
* @see http://tools.ietf.org/html/rfc2388
* @see http://tools.ietf.org/html/rfc6266
*
* @param string[] $disposition_header List of Content-Disposition header values.
* @return string|null Filename if available, or null if not found.
*/
public static function get_filename_from_disposition( $disposition_header ) {
// Get the filename.
$filename = null;
foreach ( $disposition_header as $value ) {
$value = trim( $value );
if ( strpos( $value, ';' ) === false ) {
continue;
}
list( $type, $attr_parts ) = explode( ';', $value, 2 );
$attr_parts = explode( ';', $attr_parts );
$attributes = array();
foreach ( $attr_parts as $part ) {
if ( strpos( $part, '=' ) === false ) {
continue;
}
list( $key, $value ) = explode( '=', $part, 2 );
$attributes[ trim( $key ) ] = trim( $value );
}
if ( empty( $attributes['filename'] ) ) {
continue;
}
$filename = trim( $attributes['filename'] );
// Unquote quoted filename, but after trimming.
if ( substr( $filename, 0, 1 ) === '"' && substr( $filename, -1, 1 ) === '"' ) {
$filename = substr( $filename, 1, -1 );
}
}
return $filename;
}
/**
* Get the query params for collections of attachments.
*
* @return array Query parameters for the attachment collection as an array.
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['status']['default'] = 'inherit';
$params['status']['enum'] = array( 'inherit', 'private', 'trash' );
$media_types = $this->get_media_types();
$params['media_type'] = array(
'default' => null,
'description' => __( 'Limit result set to attachments of a particular media type.' ),
'type' => 'string',
'enum' => array_keys( $media_types ),
'validate_callback' => 'rest_validate_request_arg',
);
$params['mime_type'] = array(
'default' => null,
'description' => __( 'Limit result set to attachments of a particular MIME type.' ),
'type' => 'string',
);
return $params;
}
/**
* Validate whether the user can query private statuses
*
* @param mixed $value Status value.
* @param WP_REST_Request $request Request object.
* @param string $parameter Additional parameter to pass to validation.
* @return WP_Error|boolean Boolean true if the user may query, WP_Error if not.
*/
public function validate_user_can_query_private_statuses( $value, $request, $parameter ) {
if ( 'inherit' === $value ) {
return true;
}
return parent::validate_user_can_query_private_statuses( $value, $request, $parameter );
}
/**
* Handle an upload via multipart/form-data ($_FILES).
*
* @param array $files Data from $_FILES.
* @param array $headers HTTP headers from the request.
* @return array|WP_Error Data from {@see wp_handle_upload()}.
*/
protected function upload_from_file( $files, $headers ) {
if ( empty( $files ) ) {
return new WP_Error( 'rest_upload_no_data', __( 'No data supplied.' ), array( 'status' => 400 ) );
}
// Verify hash, if given.
if ( ! empty( $headers['content_md5'] ) ) {
$content_md5 = array_shift( $headers['content_md5'] );
$expected = trim( $content_md5 );
$actual = md5_file( $files['file']['tmp_name'] );
if ( $expected !== $actual ) {
return new WP_Error( 'rest_upload_hash_mismatch', __( 'Content hash did not match expected.' ), array( 'status' => 412 ) );
}
}
// Pass off to WP to handle the actual upload.
$overrides = array(
'test_form' => false,
);
// Bypasses is_uploaded_file() when running unit tests.
if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) {
$overrides['action'] = 'wp_handle_mock_upload';
}
// Include admin functions to get access to wp_handle_upload().
require_once ABSPATH . 'wp-admin/includes/admin.php';
$file = wp_handle_upload( $files['file'], $overrides );
if ( isset( $file['error'] ) ) {
return new WP_Error( 'rest_upload_unknown_error', $file['error'], array( 'status' => 500 ) );
}
return $file;
}
/**
* Get the supported media types.
*
* Media types are considered the MIME type category.
*
* @return array
*/
protected function get_media_types() {
$media_types = array();
foreach ( get_allowed_mime_types() as $mime_type ) {
$parts = explode( '/', $mime_type );
if ( ! isset( $media_types[ $parts[0] ] ) ) {
$media_types[ $parts[0] ] = array();
}
$media_types[ $parts[0] ][] = $mime_type;
}
return $media_types;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,533 @@
<?php
abstract class WP_REST_Controller {
/**
* The namespace of this controller's route.
*
* @var string
*/
protected $namespace;
/**
* The base of this controller's route.
*
* @var string
*/
protected $rest_base;
/**
* Register the routes for the objects of the controller.
*/
public function register_routes() {
_doing_it_wrong( 'WP_REST_Controller::register_routes', __( 'The register_routes() method must be overridden' ), 'WPAPI-2.0' );
}
/**
* Check if a given request has access to get items.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Get a collection of items.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Check if a given request has access to get a specific item.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Get one item from the collection.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_item( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Check if a given request has access to create items.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|boolean
*/
public function create_item_permissions_check( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Create one item from the collection.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Check if a given request has access to update a specific item.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|boolean
*/
public function update_item_permissions_check( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Update one item from the collection.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Check if a given request has access to delete a specific item.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|boolean
*/
public function delete_item_permissions_check( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Delete one item from the collection.
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Prepare the item for create or update operation.
*
* @param WP_REST_Request $request Request object.
* @return WP_Error|object $prepared_item
*/
protected function prepare_item_for_database( $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Prepare the item for the REST response.
*
* @param mixed $item WordPress representation of the item.
* @param WP_REST_Request $request Request object.
* @return WP_Error|WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
}
/**
* Prepare a response for inserting into a collection.
*
* @param WP_REST_Response $response Response object.
* @return array Response data, ready for insertion into collection data.
*/
public function prepare_response_for_collection( $response ) {
if ( ! ( $response instanceof WP_REST_Response ) ) {
return $response;
}
$data = (array) $response->get_data();
$server = rest_get_server();
if ( method_exists( $server, 'get_compact_response_links' ) ) {
$links = call_user_func( array( $server, 'get_compact_response_links' ), $response );
} else {
$links = call_user_func( array( $server, 'get_response_links' ), $response );
}
if ( ! empty( $links ) ) {
$data['_links'] = $links;
}
return $data;
}
/**
* Filter a response based on the context defined in the schema.
*
* @param array $data
* @param string $context
* @return array
*/
public function filter_response_by_context( $data, $context ) {
$schema = $this->get_item_schema();
foreach ( $data as $key => $value ) {
if ( empty( $schema['properties'][ $key ] ) || empty( $schema['properties'][ $key ]['context'] ) ) {
continue;
}
if ( ! in_array( $context, $schema['properties'][ $key ]['context'], true ) ) {
unset( $data[ $key ] );
continue;
}
if ( 'object' === $schema['properties'][ $key ]['type'] && ! empty( $schema['properties'][ $key ]['properties'] ) ) {
foreach ( $schema['properties'][ $key ]['properties'] as $attribute => $details ) {
if ( empty( $details['context'] ) ) {
continue;
}
if ( ! in_array( $context, $details['context'], true ) ) {
if ( isset( $data[ $key ][ $attribute ] ) ) {
unset( $data[ $key ][ $attribute ] );
}
}
}
}
}
return $data;
}
/**
* Get the item's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
return $this->add_additional_fields_schema( array() );
}
/**
* Get the item's schema for display / public consumption purposes.
*
* @return array
*/
public function get_public_item_schema() {
$schema = $this->get_item_schema();
foreach ( $schema['properties'] as &$property ) {
unset( $property['arg_options'] );
}
return $schema;
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->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',
),
);
}
/**
* Get the magical context param.
*
* Ensures consistent description between endpoints, and populates enum from schema.
*
* @param array $args
* @return array
*/
public 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 = $this->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 );
}
/**
* Add the values from additional fields to a data object.
*
* @param array $object
* @param WP_REST_Request $request
* @return array modified object with additional fields.
*/
protected function add_additional_fields_to_object( $object, $request ) {
$additional_fields = $this->get_additional_fields();
foreach ( $additional_fields as $field_name => $field_options ) {
if ( ! $field_options['get_callback'] ) {
continue;
}
$object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
}
return $object;
}
/**
* Update the values of additional fields added to a data object.
*
* @param array $object
* @param WP_REST_Request $request
* @return bool|WP_Error True on success, WP_Error object if a field cannot be updated.
*/
protected function update_additional_fields_for_object( $object, $request ) {
$additional_fields = $this->get_additional_fields();
foreach ( $additional_fields as $field_name => $field_options ) {
if ( ! $field_options['update_callback'] ) {
continue;
}
// Don't run the update callbacks if the data wasn't passed in the request.
if ( ! isset( $request[ $field_name ] ) ) {
continue;
}
$result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
if ( is_wp_error( $result ) ) {
return $result;
}
}
return true;
}
/**
* Add the schema from additional fields to an schema array.
*
* The type of object is inferred from the passed schema.
*
* @param array $schema Schema array.
* @return array Modified Schema array.
*/
protected function add_additional_fields_schema( $schema ) {
if ( empty( $schema['title'] ) ) {
return $schema;
}
/**
* Can't use $this->get_object_type otherwise we cause an inf loop.
*/
$object_type = $schema['title'];
$additional_fields = $this->get_additional_fields( $object_type );
foreach ( $additional_fields as $field_name => $field_options ) {
if ( ! $field_options['schema'] ) {
continue;
}
$schema['properties'][ $field_name ] = $field_options['schema'];
}
return $schema;
}
/**
* Get all the registered additional fields for a given object-type.
*
* @param string $object_type
* @return array
*/
protected function get_additional_fields( $object_type = null ) {
if ( ! $object_type ) {
$object_type = $this->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 ];
}
/**
* Get the object type this controller is responsible for managing.
*
* @return string
*/
protected function get_object_type() {
$schema = $this->get_item_schema();
if ( ! $schema || ! isset( $schema['title'] ) ) {
return null;
}
return $schema['title'];
}
/**
* Get an array of endpoint arguments from the item schema for the controller.
*
* @param string $method HTTP method of the request. The arguments
* for `CREATABLE` requests are checked for required
* values and may fall-back to a given default, this
* is not done on `EDITABLE` requests. Default is
* WP_REST_Server::CREATABLE.
* @return array $endpoint_args
*/
public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
$schema = $this->get_item_schema();
$schema_properties = ! empty( $schema['properties'] ) ? $schema['properties'] : array();
$endpoint_args = array();
foreach ( $schema_properties as $field_id => $params ) {
// Arguments specified as `readonly` are not allowed to be set.
if ( ! empty( $params['readonly'] ) ) {
continue;
}
$endpoint_args[ $field_id ] = array(
'validate_callback' => 'rest_validate_request_arg',
'sanitize_callback' => 'rest_sanitize_request_arg',
);
if ( isset( $params['description'] ) ) {
$endpoint_args[ $field_id ]['description'] = $params['description'];
}
if ( WP_REST_Server::CREATABLE === $method && isset( $params['default'] ) ) {
$endpoint_args[ $field_id ]['default'] = $params['default'];
}
if ( WP_REST_Server::CREATABLE === $method && ! empty( $params['required'] ) ) {
$endpoint_args[ $field_id ]['required'] = true;
}
foreach ( array( 'type', 'format', 'enum' ) as $schema_prop ) {
if ( isset( $params[ $schema_prop ] ) ) {
$endpoint_args[ $field_id ][ $schema_prop ] = $params[ $schema_prop ];
}
}
// Merge in any options provided by the schema property.
if ( isset( $params['arg_options'] ) ) {
// Only use required / default from arg_options on CREATABLE endpoints.
if ( WP_REST_Server::CREATABLE !== $method ) {
$params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) );
}
$endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] );
}
}
return $endpoint_args;
}
/**
* Retrieves post data given a post ID or post object.
*
* This is a subset of the functionality of the `get_post()` function, with
* the additional functionality of having `the_post` action done on the
* resultant post object. This is done so that plugins may manipulate the
* post that is used in the REST API.
*
* @see get_post()
* @global WP_Query $wp_query
*
* @param int|WP_Post $post Post ID or post object. Defaults to global $post.
* @return WP_Post|null A `WP_Post` object when successful.
*/
public function get_post( $post ) {
$post_obj = get_post( $post );
/**
* Filter the post.
*
* Allows plugins to filter the post object as returned by `\WP_REST_Controller::get_post()`.
*
* @param WP_Post|null $post_obj The post object as returned by `get_post()`.
* @param int|WP_Post $post The original value used to obtain the post object.
*/
$post = apply_filters( 'rest_the_post', $post_obj, $post );
return $post;
}
/**
* Sanitize the slug value.
*
* @internal We can't use {@see sanitize_title} directly, as the second
* parameter is the fallback title, which would end up being set to the
* request object.
* @see https://github.com/WP-API/WP-API/issues/1585
*
* @todo Remove this in favour of https://core.trac.wordpress.org/ticket/34659
*
* @param string $slug Slug value passed in request.
* @return string Sanitized value for the slug.
*/
public function sanitize_slug( $slug ) {
return sanitize_title( $slug );
}
}

View File

@ -0,0 +1,244 @@
<?php
class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'statuses';
}
/**
* 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, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<status>[\w-]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check whether a given request has permission to read post statuses.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( 'edit' === $request['context'] ) {
$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
foreach ( $types as $type ) {
if ( current_user_can( $type->cap->edit_posts ) ) {
return true;
}
}
return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all post statuses, depending on user context
*
* @param WP_REST_Request $request
* @return array|WP_Error
*/
public function get_items( $request ) {
$data = array();
$statuses = get_post_stati( array( 'internal' => false ), 'object' );
$statuses['trash'] = get_post_status_object( 'trash' );
foreach ( $statuses as $slug => $obj ) {
$ret = $this->check_read_permission( $obj );
if ( ! $ret ) {
continue;
}
$status = $this->prepare_item_for_response( $obj, $request );
$data[ $obj->name ] = $this->prepare_response_for_collection( $status );
}
return rest_ensure_response( $data );
}
/**
* Check if a given request has access to read a post status.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
$status = get_post_status_object( $request['status'] );
if ( empty( $status ) ) {
return new WP_Error( 'rest_status_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) );
}
$check = $this->check_read_permission( $status );
if ( ! $check ) {
return new WP_Error( 'rest_cannot_read_status', __( 'Cannot view resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Check whether a given post status should be visible
*
* @param object $status
* @return boolean
*/
protected function check_read_permission( $status ) {
if ( true === $status->public ) {
return true;
}
if ( false === $status->internal || 'trash' === $status->name ) {
$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
foreach ( $types as $type ) {
if ( current_user_can( $type->cap->edit_posts ) ) {
return true;
}
}
}
return false;
}
/**
* Get a specific post status
*
* @param WP_REST_Request $request
* @return array|WP_Error
*/
public function get_item( $request ) {
$obj = get_post_status_object( $request['status'] );
if ( empty( $obj ) ) {
return new WP_Error( 'rest_status_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) );
}
$data = $this->prepare_item_for_response( $obj, $request );
return rest_ensure_response( $data );
}
/**
* Prepare a post status object for serialization
*
* @param stdClass $status Post status data
* @param WP_REST_Request $request
* @return WP_REST_Response Post status data
*/
public function prepare_item_for_response( $status, $request ) {
$data = array(
'name' => $status->label,
'private' => (bool) $status->private,
'protected' => (bool) $status->protected,
'public' => (bool) $status->public,
'queryable' => (bool) $status->publicly_queryable,
'show_in_list' => (bool) $status->show_in_admin_all_list,
'slug' => $status->name,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
if ( 'publish' === $status->name ) {
$response->add_link( 'archives', rest_url( 'wp/v2/posts' ) );
} else {
$response->add_link( 'archives', add_query_arg( 'status', $status->name, rest_url( 'wp/v2/posts' ) ) );
}
/**
* Filter a status returned from the API.
*
* Allows modification of the status data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $status The original status object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'rest_prepare_status', $response, $status, $request );
}
/**
* Get the Post status' schema, conforming to JSON Schema
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'status',
'type' => 'object',
'properties' => array(
'name' => array(
'description' => __( 'The title for the resource.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'private' => array(
'description' => __( 'Whether posts with this resource should be private.' ),
'type' => 'boolean',
'context' => array( 'edit' ),
'readonly' => true,
),
'protected' => array(
'description' => __( 'Whether posts with this resource should be protected.' ),
'type' => 'boolean',
'context' => array( 'edit' ),
'readonly' => true,
),
'public' => array(
'description' => __( 'Whether posts of this resource should be shown in the front end of the site.' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'queryable' => array(
'description' => __( 'Whether posts with this resource should be publicly-queryable.' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'show_in_list' => array(
'description' => __( 'Whether to include posts in the edit listing for their post type.' ),
'type' => 'boolean',
'context' => array( 'edit' ),
'readonly' => true,
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}

View File

@ -0,0 +1,202 @@
<?php
class WP_REST_Post_Types_Controller extends WP_REST_Controller {
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'types';
}
/**
* 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, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<type>[\w-]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check whether a given request has permission to read types.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( 'edit' === $request['context'] ) {
foreach ( get_post_types( array(), 'object' ) as $post_type ) {
if ( ! empty( $post_type->show_in_rest ) && current_user_can( $post_type->cap->edit_posts ) ) {
return true;
}
}
return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all public post types
*
* @param WP_REST_Request $request
* @return array|WP_Error
*/
public function get_items( $request ) {
$data = array();
foreach ( get_post_types( array(), 'object' ) as $obj ) {
if ( empty( $obj->show_in_rest ) || ( 'edit' === $request['context'] && ! current_user_can( $obj->cap->edit_posts ) ) ) {
continue;
}
$post_type = $this->prepare_item_for_response( $obj, $request );
$data[ $obj->name ] = $this->prepare_response_for_collection( $post_type );
}
return rest_ensure_response( $data );
}
/**
* Get a specific post type
*
* @param WP_REST_Request $request
* @return array|WP_Error
*/
public function get_item( $request ) {
$obj = get_post_type_object( $request['type'] );
if ( empty( $obj ) ) {
return new WP_Error( 'rest_type_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) );
}
if ( empty( $obj->show_in_rest ) ) {
return new WP_Error( 'rest_cannot_read_type', __( 'Cannot view resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
if ( 'edit' === $request['context'] && ! current_user_can( $obj->cap->edit_posts ) ) {
return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage this resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
$data = $this->prepare_item_for_response( $obj, $request );
return rest_ensure_response( $data );
}
/**
* Prepare a post type object for serialization
*
* @param stdClass $post_type Post type data
* @param WP_REST_Request $request
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $post_type, $request ) {
$data = array(
'capabilities' => $post_type->cap,
'description' => $post_type->description,
'hierarchical' => $post_type->hierarchical,
'labels' => $post_type->labels,
'name' => $post_type->label,
'slug' => $post_type->name,
);
$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 );
$base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
$response->add_links( array(
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
'https://api.w.org/items' => array(
'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ),
),
) );
/**
* Filter a post type returned from the API.
*
* Allows modification of the post type data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $item The original post type object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'rest_prepare_post_type', $response, $post_type, $request );
}
/**
* Get the Post type's schema, conforming to JSON Schema
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'type',
'type' => 'object',
'properties' => array(
'capabilities' => array(
'description' => __( 'All capabilities used by the resource.' ),
'type' => 'array',
'context' => array( 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'A human-readable description of the resource.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'hierarchical' => array(
'description' => __( 'Whether or not the resource should have children.' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'labels' => array(
'description' => __( 'Human-readable labels for the resource for various contexts.' ),
'type' => 'object',
'context' => array( 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'The title for the resource.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,428 @@
<?php
class WP_REST_Revisions_Controller extends WP_REST_Controller {
private $parent_post_type;
private $parent_controller;
private $parent_base;
public function __construct( $parent_post_type ) {
$this->parent_post_type = $parent_post_type;
$this->parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
$this->namespace = 'wp/v2';
$this->rest_base = 'revisions';
$post_type_object = get_post_type_object( $parent_post_type );
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
}
/**
* Register routes for revisions based on post types supporting revisions
*
* @access public
*/
public function register_routes() {
register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base, array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
));
}
/**
* Check if a given request has access to get revisions
*
* @access public
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
$parent = $this->get_post( $request['parent'] );
if ( ! $parent ) {
return true;
}
$parent_post_type_obj = get_post_type_object( $parent->post_type );
if ( ! current_user_can( $parent_post_type_obj->cap->edit_post, $parent->ID ) ) {
return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get a collection of revisions
*
* @access public
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$parent = $this->get_post( $request['parent'] );
if ( ! $request['parent'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent id.' ), array( 'status' => 404 ) );
}
$revisions = wp_get_post_revisions( $request['parent'] );
$response = array();
foreach ( $revisions as $revision ) {
$data = $this->prepare_item_for_response( $revision, $request );
$response[] = $this->prepare_response_for_collection( $data );
}
return rest_ensure_response( $response );
}
/**
* Check if a given request has access to get a specific revision
*
* @access public
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request );
}
/**
* Get one revision from the collection
*
* @access public
*
* @param WP_REST_Request $request Full data about the request.
* @return WP_Error|array
*/
public function get_item( $request ) {
$parent = $this->get_post( $request['parent'] );
if ( ! $request['parent'] || ! $parent || $this->parent_post_type !== $parent->post_type ) {
return new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent id.' ), array( 'status' => 404 ) );
}
$revision = $this->get_post( $request['id'] );
if ( ! $revision || 'revision' !== $revision->post_type ) {
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision id.' ), array( 'status' => 404 ) );
}
$response = $this->prepare_item_for_response( $revision, $request );
return rest_ensure_response( $response );
}
/**
* Check if a given request has access to delete a revision
*
* @access public
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function delete_item_permissions_check( $request ) {
$response = $this->get_items_permissions_check( $request );
if ( ! $response || is_wp_error( $response ) ) {
return $response;
}
$post = $this->get_post( $request['id'] );
if ( ! $post ) {
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision id.' ), array( 'status' => 404 ) );
}
$post_type = get_post_type_object( 'revision' );
return current_user_can( $post_type->cap->delete_post, $post->ID );
}
/**
* Delete a single revision
*
* @access public
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function delete_item( $request ) {
$result = wp_delete_post( $request['id'], true );
/**
* Fires after a revision is deleted via the REST API.
*
* @param (mixed) $result The revision object (if it was deleted or moved to the trash successfully)
* or false (failure). If the revision was moved to to the trash, $result represents
* its new state; if it was deleted, $result represents its state before deletion.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'rest_delete_revision', $result, $request );
if ( $result ) {
return true;
} else {
return new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
}
}
/**
* Prepare the revision for the REST response
*
* @access public
*
* @param WP_Post $post Post revision object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $post, $request ) {
$schema = $this->get_item_schema();
$data = array();
if ( ! empty( $schema['properties']['author'] ) ) {
$data['author'] = $post->post_author;
}
if ( ! empty( $schema['properties']['date'] ) ) {
$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
}
if ( ! empty( $schema['properties']['date_gmt'] ) ) {
$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
}
if ( ! empty( $schema['properties']['id'] ) ) {
$data['id'] = $post->ID;
}
if ( ! empty( $schema['properties']['modified'] ) ) {
$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
}
if ( ! empty( $schema['properties']['modified_gmt'] ) ) {
$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
}
if ( ! empty( $schema['properties']['parent'] ) ) {
$data['parent'] = (int) $post->post_parent;
}
if ( ! empty( $schema['properties']['slug'] ) ) {
$data['slug'] = $post->post_name;
}
if ( ! empty( $schema['properties']['guid'] ) ) {
$data['guid'] = array(
/** This filter is documented in wp-includes/post-template.php */
'rendered' => apply_filters( 'get_the_guid', $post->guid ),
'raw' => $post->guid,
);
}
if ( ! empty( $schema['properties']['title'] ) ) {
$data['title'] = array(
'raw' => $post->post_title,
'rendered' => get_the_title( $post->ID ),
);
}
if ( ! empty( $schema['properties']['content'] ) ) {
$data['content'] = array(
'raw' => $post->post_content,
/** This filter is documented in wp-includes/post-template.php */
'rendered' => apply_filters( 'the_content', $post->post_content ),
);
}
if ( ! empty( $schema['properties']['excerpt'] ) ) {
$data['excerpt'] = array(
'raw' => $post->post_excerpt,
'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ),
);
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
if ( ! empty( $data['parent'] ) ) {
$response->add_link( 'parent', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) );
}
/**
* Filter a revision returned from the API.
*
* Allows modification of the revision right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param WP_Post $post The original revision object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'rest_prepare_revision', $response, $post, $request );
}
/**
* Check the post_date_gmt or modified_gmt and prepare any post or
* modified date for single post output.
*
* @access protected
*
* @param string $date_gmt GMT publication time.
* @param string|null $date Optional, default is null. Local publication time.
* @return string|null ISO8601/RFC3339 formatted datetime.
*/
protected function prepare_date_response( $date_gmt, $date = null ) {
if ( '0000-00-00 00:00:00' === $date_gmt ) {
return null;
}
if ( isset( $date ) ) {
return mysql_to_rfc3339( $date );
}
return mysql_to_rfc3339( $date_gmt );
}
/**
* Get the revision's schema, conforming to JSON Schema
*
* @access public
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => "{$this->parent_post_type}-revision",
'type' => 'object',
/*
* Base properties for every Revision
*/
'properties' => array(
'author' => array(
'description' => __( 'The id for the author of the object.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
),
'date' => array(
'description' => __( 'The date the object was published.' ),
'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' => __( 'GUID for the object, as it exists in the database.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
),
'id' => array(
'description' => __( 'Unique identifier for the object.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
),
'modified' => array(
'description' => __( 'The date the object was last modified.' ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'modified_gmt' => array(
'description' => __( 'The date the object was last modified, as GMT.' ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view', 'edit' ),
),
'parent' => array(
'description' => __( 'The id for the parent of the object.' ),
'type' => 'integer',
'context' => array( 'view', 'edit', 'embed' ),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the object unique to its type.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
),
),
);
$parent_schema = $this->parent_controller->get_item_schema();
if ( ! empty( $parent_schema['properties']['title'] ) ) {
$schema['properties']['title'] = $parent_schema['properties']['title'];
}
if ( ! empty( $parent_schema['properties']['content'] ) ) {
$schema['properties']['content'] = $parent_schema['properties']['content'];
}
if ( ! empty( $parent_schema['properties']['excerpt'] ) ) {
$schema['properties']['excerpt'] = $parent_schema['properties']['excerpt'];
}
if ( ! empty( $parent_schema['properties']['guid'] ) ) {
$schema['properties']['guid'] = $parent_schema['properties']['guid'];
}
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections
*
* @access public
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
/**
* Check the post excerpt and prepare it for single post output.
*
* @access protected
*
* @param string $excerpt The post excerpt.
* @param WP_Post $post Post revision object.
* @return string|null $excerpt
*/
protected function prepare_excerpt_response( $excerpt, $post ) {
/** This filter is documented in wp-includes/post-template.php */
$excerpt = apply_filters( 'the_excerpt', $excerpt, $post );
if ( empty( $excerpt ) ) {
return '';
}
return $excerpt;
}
}

View File

@ -0,0 +1,220 @@
<?php
/**
* Manage a WordPress site's settings.
*/
class WP_REST_Settings_Controller extends WP_REST_Controller {
protected $rest_base = 'settings';
protected $namespace = 'wp/v2';
/**
* 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_item' ),
'args' => array(),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check if a given request has access to read and manage settings.
*
* @param WP_REST_Request $request Full details about the request.
* @return boolean
*/
public function get_item_permissions_check( $request ) {
return current_user_can( 'manage_options' );
}
/**
* Get the settings.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|array
*/
public function get_item( $request ) {
$options = $this->get_registered_options();
$response = array();
foreach ( $options as $name => $args ) {
/**
* Filters the value of a setting recognized by the REST API.
*
* Allow hijacking the setting value and overriding the built-in behavior by returning a
* non-null value. The returned value will be presented as the setting value instead.
*
* @since 4.7.0
*
* @param mixed $result Value to use for the requested setting. Can be a scalar
* matching the registered schema for the setting, or null to
* follow the default `get_option` behavior.
* @param string $name Setting name (as shown in REST API responses).
* @param array $args Arguments passed to `register_setting()` for this setting.
*/
$response[ $name ] = apply_filters( 'rest_pre_get_setting', null, $name, $args );
if ( is_null( $response[ $name ] ) ) {
// Default to a null value as "null" in the response means "not set".
$response[ $name ] = get_option( $args['option_name'], $args['schema']['default'] );
}
// Because get_option() is lossy, we have to
// cast values to the type they are registered with.
$response[ $name ] = $this->prepare_value( $response[ $name ], $args['schema'] );
}
return $response;
}
/**
* Prepare a value for output based off a schema array.
*
* @param mixed $value
* @param array $schema
* @return mixed
*/
protected function prepare_value( $value, $schema ) {
switch ( $schema['type'] ) {
case 'string':
return (string) $value;
case 'number':
return (float) $value;
case 'boolean':
return (bool) $value;
default:
return null;
}
}
/**
* Update settings for the settings object.
*
* @param WP_REST_Request $request Full detail about the request.
* @return WP_Error|array
*/
public function update_item( $request ) {
$options = $this->get_registered_options();
$params = $request->get_params();
foreach ( $options as $name => $args ) {
if ( ! array_key_exists( $name, $params ) ) {
continue;
}
/**
* Filters whether to preempt a setting value update.
*
* Allow hijacking the setting update logic and overriding the built-in behavior by
* returning true.
*
* @since 4.7.0
*
* @param boolean $result Whether to override the default behavior for updating the
* value of a setting.
* @param string $name Setting name (as shown in REST API responses).
* @param mixed $value Updated setting value.
* @param array $args Arguments passed to `register_setting()` for this setting.
*/
$updated = apply_filters( 'rest_pre_update_setting', false, $name, $request[ $name ], $args );
if ( $updated ) {
continue;
}
// A null value means reset the option, which is essentially deleting it
// from the database and then relying on the default value.
if ( is_null( $request[ $name ] ) ) {
delete_option( $args['option_name'] );
} else {
update_option( $args['option_name'], $request[ $name ] );
}
}
return $this->get_item( $request );
}
/**
* Get all the registered options for the Settings API
*
* @return array
*/
protected function get_registered_options() {
$rest_options = array();
foreach ( get_registered_settings() as $name => $args ) {
if ( empty( $args['show_in_rest'] ) ) {
continue;
}
$rest_args = array();
if ( is_array( $args['show_in_rest'] ) ) {
$rest_args = $args['show_in_rest'];
}
$defaults = array(
'name' => ! empty( $rest_args['name'] ) ? $rest_args['name'] : $name,
'schema' => array(),
);
$rest_args = array_merge( $defaults, $rest_args );
$default_schema = array(
'type' => empty( $args['type'] ) ? null : $args['type'],
'description' => empty( $args['description'] ) ? '' : $args['description'],
'default' => isset( $args['default'] ) ? $args['default'] : null,
);
$rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
$rest_args['option_name'] = $name;
// Skip over settings that don't have a defined type in the schema.
if ( empty( $rest_args['schema']['type'] ) ) {
continue;
}
// Whitelist the supported types for settings, as we don't want invalid types
// to be updated with arbitrary values that we can't do decent sanitizing for.
if ( ! in_array( $rest_args['schema']['type'], array( 'number', 'string', 'boolean' ), true ) ) {
continue;
}
$rest_options[ $rest_args['name'] ] = $rest_args;
}
return $rest_options;
}
/**
* Get the site setting schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$options = $this->get_registered_options();
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'settings',
'type' => 'object',
'properties' => array(),
);
foreach ( $options as $option_name => $option ) {
$schema['properties'][ $option_name ] = $option['schema'];
}
return $this->add_additional_fields_schema( $schema );
}
}

View File

@ -0,0 +1,261 @@
<?php
class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'taxonomies';
}
/**
* 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, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<taxonomy>[\w-]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Check whether a given request has permission to read taxonomies.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( 'edit' === $request['context'] ) {
if ( ! empty( $request['type'] ) ) {
$taxonomies = get_object_taxonomies( $request['type'], 'objects' );
} else {
$taxonomies = get_taxonomies( '', 'objects' );
}
foreach ( $taxonomies as $taxonomy ) {
if ( ! empty( $taxonomy->show_in_rest ) && current_user_can( $taxonomy->cap->manage_terms ) ) {
return true;
}
}
return new WP_Error( 'rest_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all public taxonomies
*
* @param WP_REST_Request $request
* @return array
*/
public function get_items( $request ) {
// Retrieve the list of registered collection query parameters.
$registered = $this->get_collection_params();
if ( isset( $registered['type'] ) && ! empty( $request['type'] ) ) {
$taxonomies = get_object_taxonomies( $request['type'], 'objects' );
} else {
$taxonomies = get_taxonomies( '', 'objects' );
}
$data = array();
foreach ( $taxonomies as $tax_type => $value ) {
if ( empty( $value->show_in_rest ) || ( 'edit' === $request['context'] && ! current_user_can( $value->cap->manage_terms ) ) ) {
continue;
}
$tax = $this->prepare_item_for_response( $value, $request );
$tax = $this->prepare_response_for_collection( $tax );
$data[ $tax_type ] = $tax;
}
if ( empty( $data ) ) {
// Response should still be returned as a JSON object when it is empty.
$data = (object) $data;
}
return rest_ensure_response( $data );
}
/**
* Check if a given request has access a taxonomy
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
$tax_obj = get_taxonomy( $request['taxonomy'] );
if ( $tax_obj ) {
if ( empty( $tax_obj->show_in_rest ) ) {
return false;
}
if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->manage_terms ) ) {
return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage this resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
}
return true;
}
/**
* Get a specific taxonomy
*
* @param WP_REST_Request $request
* @return array|WP_Error
*/
public function get_item( $request ) {
$tax_obj = get_taxonomy( $request['taxonomy'] );
if ( empty( $tax_obj ) ) {
return new WP_Error( 'rest_taxonomy_invalid', __( 'Invalid resource.' ), array( 'status' => 404 ) );
}
$data = $this->prepare_item_for_response( $tax_obj, $request );
return rest_ensure_response( $data );
}
/**
* Prepare a taxonomy object for serialization
*
* @param stdClass $taxonomy Taxonomy data
* @param WP_REST_Request $request
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $taxonomy, $request ) {
$data = array(
'name' => $taxonomy->label,
'slug' => $taxonomy->name,
'capabilities' => $taxonomy->cap,
'description' => $taxonomy->description,
'labels' => $taxonomy->labels,
'types' => $taxonomy->object_type,
'show_cloud' => $taxonomy->show_tagcloud,
'hierarchical' => $taxonomy->hierarchical,
);
$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 );
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
$response->add_links( array(
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
'https://api.w.org/items' => array(
'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ),
),
) );
/**
* Filter a taxonomy returned from the API.
*
* Allows modification of the taxonomy data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $item The original taxonomy object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'rest_prepare_taxonomy', $response, $taxonomy, $request );
}
/**
* Get the taxonomy's schema, conforming to JSON Schema
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'taxonomy',
'type' => 'object',
'properties' => array(
'capabilities' => array(
'description' => __( 'All capabilities used by the resource.' ),
'type' => 'array',
'context' => array( 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'A human-readable description of the resource.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'hierarchical' => array(
'description' => __( 'Whether or not the resource should have children.' ),
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'labels' => array(
'description' => __( 'Human-readable labels for the resource for various contexts.' ),
'type' => 'object',
'context' => array( 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'The title for the resource.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.' ),
'type' => 'string',
'context' => array( 'view', 'edit', 'embed' ),
'readonly' => true,
),
'show_cloud' => array(
'description' => __( 'Whether or not the term cloud should be displayed.' ),
'type' => 'boolean',
'context' => array( 'edit' ),
'readonly' => true,
),
'types' => array(
'description' => __( 'Types associated with resource.' ),
'type' => 'array',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections
*
* @return array
*/
public function get_collection_params() {
$new_params = array();
$new_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
$new_params['type'] = array(
'description' => __( 'Limit results to resources associated with a specific post type.' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
return $new_params;
}
}

View File

@ -0,0 +1,907 @@
<?php
/**
* Access terms associated with a taxonomy.
*/
class WP_REST_Terms_Controller extends WP_REST_Controller {
/**
* Taxonomy key.
*
* @access protected
* @var string
*/
protected $taxonomy;
/**
* Instance of a term meta fields object.
*
* @access protected
* @var WP_REST_Term_Meta_Fields
*/
protected $meta;
/**
* Column to have the terms be sorted by.
*
* @access protected
* @var string
*/
protected $sort_column;
/**
* Number of terms that were found.
*
* @access protected
* @var int
*/
protected $total_terms;
/**
* Constructor.
*
* @param string $taxonomy Taxonomy key.
*/
public function __construct( $taxonomy ) {
$this->taxonomy = $taxonomy;
$this->namespace = 'wp/v2';
$tax_obj = get_taxonomy( $taxonomy );
$this->rest_base = ! empty( $tax_obj->rest_base ) ? $tax_obj->rest_base : $tax_obj->name;
$this->meta = new WP_REST_Term_Meta_Fields( $taxonomy );
}
/**
* Registers 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, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'description' => __( 'Required to be true, as resource does not support trashing.' ),
),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
}
/**
* Checks if a request has access to read terms in the specified taxonomy.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
$tax_obj = get_taxonomy( $this->taxonomy );
if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
return false;
}
if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Gets terms associated with a taxonomy.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Response|WP_Error
*/
public function get_items( $request ) {
// Retrieve the list of registered collection query parameters.
$registered = $this->get_collection_params();
// 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(
'exclude' => 'exclude',
'include' => 'include',
'order' => 'order',
'orderby' => 'orderby',
'post' => 'post',
'hide_empty' => 'hide_empty',
'per_page' => 'number',
'search' => 'search',
'slug' => 'slug',
);
$prepared_args = array();
// For each known parameter which is both registered and present in the request,
// set the parameter's value on the query $prepared_args.
foreach ( $parameter_mappings as $api_param => $wp_param ) {
if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
$prepared_args[ $wp_param ] = $request[ $api_param ];
}
}
if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) {
$prepared_args['offset'] = $request['offset'];
} else {
$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
}
$taxonomy_obj = get_taxonomy( $this->taxonomy );
if ( $taxonomy_obj->hierarchical && isset( $registered['parent'], $request['parent'] ) ) {
if ( 0 === $request['parent'] ) {
// Only query top-level terms.
$prepared_args['parent'] = 0;
} else {
if ( $request['parent'] ) {
$prepared_args['parent'] = $request['parent'];
}
}
}
/**
* Filters the query arguments before passing them to get_terms().
*
* Enables adding extra arguments or setting defaults for a terms
* collection request.
*
* @see https://developer.wordpress.org/reference/functions/get_terms/
*
* @param array $prepared_args Array of arguments to be
* passed to get_terms().
* @param WP_REST_Request $request The current request.
*/
$prepared_args = apply_filters( "rest_{$this->taxonomy}_query", $prepared_args, $request );
if ( ! empty( $prepared_args['post'] ) ) {
$query_result = $this->get_terms_for_post( $prepared_args );
$total_terms = $this->total_terms;
} else {
$query_result = get_terms( $this->taxonomy, $prepared_args );
$count_args = $prepared_args;
unset( $count_args['number'], $count_args['offset'] );
$total_terms = wp_count_terms( $this->taxonomy, $count_args );
// wp_count_terms can return a falsy value when the term has no children
if ( ! $total_terms ) {
$total_terms = 0;
}
}
$response = array();
foreach ( $query_result as $term ) {
$data = $this->prepare_item_for_response( $term, $request );
$response[] = $this->prepare_response_for_collection( $data );
}
$response = rest_ensure_response( $response );
// Store pagination values for headers.
$per_page = (int) $prepared_args['number'];
$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
$response->header( 'X-WP-Total', (int) $total_terms );
$max_pages = ceil( $total_terms / $per_page );
$response->header( 'X-WP-TotalPages', (int) $max_pages );
$base = add_query_arg( $request->get_query_params(), rest_url( $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;
}
/**
* Gets the terms attached to a post.
*
* This is an alternative to get_terms() that uses get_the_terms()
* instead, which hits the object cache. There are a few things not
* supported, notably `include`, `exclude`. In `self::get_items()` these
* are instead treated as a full query.
*
* @param array $prepared_args Arguments for get_terms().
* @return array List of term objects. (Total count in `$this->total_terms`)
*/
protected function get_terms_for_post( $prepared_args ) {
$query_result = get_the_terms( $prepared_args['post'], $this->taxonomy );
if ( empty( $query_result ) ) {
$this->total_terms = 0;
return array();
}
/*
* get_items() verifies that we don't have `include` set, and default
* ordering is by `name`.
*/
if ( ! in_array( $prepared_args['orderby'], array( 'name', 'none', 'include' ), true ) ) {
switch ( $prepared_args['orderby'] ) {
case 'id':
$this->sort_column = 'term_id';
break;
case 'slug':
case 'term_group':
case 'description':
case 'count':
$this->sort_column = $prepared_args['orderby'];
break;
}
usort( $query_result, array( $this, 'compare_terms' ) );
}
if ( strtolower( $prepared_args['order'] ) !== 'asc' ) {
$query_result = array_reverse( $query_result );
}
// Pagination.
$this->total_terms = count( $query_result );
$query_result = array_slice( $query_result, $prepared_args['offset'], $prepared_args['number'] );
return $query_result;
}
/**
* Comparison function for sorting terms by a column.
*
* Uses `$this->sort_column` to determine field to sort by.
*
* @access protected
*
* @param stdClass $left Term object.
* @param stdClass $right Term object.
* @return int <0 if left is higher "priority" than right, 0 if equal, >0 if right is higher "priority" than left.
*/
protected function compare_terms( $left, $right ) {
$col = $this->sort_column;
$left_val = $left->$col;
$right_val = $right->$col;
if ( is_int( $left_val ) && is_int( $right_val ) ) {
return $left_val - $right_val;
}
return strcmp( $left_val, $right_val );
}
/**
* Checks if a request has access to read the specified term.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
$tax_obj = get_taxonomy( $this->taxonomy );
if ( ! $tax_obj || ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
return false;
}
if ( 'edit' === $request['context'] && ! current_user_can( $tax_obj->cap->edit_terms ) ) {
return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Gets a single term from a taxonomy.
*
* @param WP_REST_Request $request Full details about the request
* @return WP_REST_Request|WP_Error
*/
public function get_item( $request ) {
$term = get_term( (int) $request['id'], $this->taxonomy );
if ( ! $term || $term->taxonomy !== $this->taxonomy ) {
return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
}
if ( is_wp_error( $term ) ) {
return $term;
}
$response = $this->prepare_item_for_response( $term, $request );
return rest_ensure_response( $response );
}
/**
* Checks if a request has access to create a term.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function create_item_permissions_check( $request ) {
if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
return false;
}
$taxonomy_obj = get_taxonomy( $this->taxonomy );
if ( ! current_user_can( $taxonomy_obj->cap->manage_terms ) ) {
return new WP_Error( 'rest_cannot_create', __( 'Sorry, you cannot create new resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Creates a single term in a taxonomy.
*
* @param WP_REST_Request $request Full details about the request
* @return WP_REST_Request|WP_Error
*/
public function create_item( $request ) {
if ( isset( $request['parent'] ) ) {
if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
}
$parent = get_term( (int) $request['parent'], $this->taxonomy );
if ( ! $parent ) {
return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 400 ) );
}
}
$prepared_term = $this->prepare_item_for_database( $request );
$term = wp_insert_term( $prepared_term->name, $this->taxonomy, $prepared_term );
if ( is_wp_error( $term ) ) {
/*
* If we're going to inform the client that the term already exists,
* give them the identifier for future use.
*/
if ( $term_id = $term->get_error_data( 'term_exists' ) ) {
$existing_term = get_term( $term_id, $this->taxonomy );
$term->add_data( $existing_term->term_id, 'term_exists' );
}
return $term;
}
$term = get_term( $term['term_id'], $this->taxonomy );
/**
* Fires after a single term is created or updated via the REST API.
*
* @param WP_Term $term Inserted Term object.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating term, false when updating.
*/
do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
$schema = $this->get_item_schema();
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
$meta_update = $this->meta->update_value( $request['meta'], (int) $request['id'] );
if ( is_wp_error( $meta_update ) ) {
return $meta_update;
}
}
$fields_update = $this->update_additional_fields_for_object( $term, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
$request->set_param( 'context', 'view' );
$response = $this->prepare_item_for_response( $term, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) );
return $response;
}
/**
* Checks if a request has access to update the specified term.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function update_item_permissions_check( $request ) {
if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
return false;
}
$term = get_term( (int) $request['id'], $this->taxonomy );
if ( ! $term ) {
return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
}
$taxonomy_obj = get_taxonomy( $this->taxonomy );
if ( ! current_user_can( $taxonomy_obj->cap->edit_terms ) ) {
return new WP_Error( 'rest_cannot_update', __( 'Sorry, you cannot update resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Updates a single term from a taxonomy.
*
* @param WP_REST_Request $request Full details about the request
* @return WP_REST_Request|WP_Error
*/
public function update_item( $request ) {
if ( isset( $request['parent'] ) ) {
if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Can not set resource parent, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
}
$parent = get_term( (int) $request['parent'], $this->taxonomy );
if ( ! $parent ) {
return new WP_Error( 'rest_term_invalid', __( "Parent resource doesn't exist." ), array( 'status' => 400 ) );
}
}
$prepared_term = $this->prepare_item_for_database( $request );
$term = get_term( (int) $request['id'], $this->taxonomy );
// Only update the term if we haz something to update.
if ( ! empty( $prepared_term ) ) {
$update = wp_update_term( $term->term_id, $term->taxonomy, (array) $prepared_term );
if ( is_wp_error( $update ) ) {
return $update;
}
}
$term = get_term( (int) $request['id'], $this->taxonomy );
/* This action is documented in lib/endpoints/class-wp-rest-terms-controller.php */
do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
$schema = $this->get_item_schema();
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
$meta_update = $this->meta->update_value( $request['meta'], (int) $request['id'] );
if ( is_wp_error( $meta_update ) ) {
return $meta_update;
}
}
$fields_update = $this->update_additional_fields_for_object( $term, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
$request->set_param( 'context', 'view' );
$response = $this->prepare_item_for_response( $term, $request );
return rest_ensure_response( $response );
}
/**
* Checks if a request has access to delete the specified term.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function delete_item_permissions_check( $request ) {
if ( ! $this->check_is_taxonomy_allowed( $this->taxonomy ) ) {
return false;
}
$term = get_term( (int) $request['id'], $this->taxonomy );
if ( ! $term ) {
return new WP_Error( 'rest_term_invalid', __( "Resource doesn't exist." ), array( 'status' => 404 ) );
}
$taxonomy_obj = get_taxonomy( $this->taxonomy );
if ( ! current_user_can( $taxonomy_obj->cap->delete_terms ) ) {
return new WP_Error( 'rest_cannot_delete', __( 'Sorry, you cannot delete resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Deletes a single term from a taxonomy.
*
* @param WP_REST_Request $request Full details about the request
* @return WP_REST_Response|WP_Error
*/
public function delete_item( $request ) {
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for this resource type.
if ( ! $force ) {
return new WP_Error( 'rest_trash_not_supported', __( 'Resource does not support trashing.' ), array( 'status' => 501 ) );
}
$term = get_term( (int) $request['id'], $this->taxonomy );
$request->set_param( 'context', 'view' );
$response = $this->prepare_item_for_response( $term, $request );
$retval = wp_delete_term( $term->term_id, $term->taxonomy );
if ( ! $retval ) {
return new WP_Error( 'rest_cannot_delete', __( 'The resource cannot be deleted.' ), array( 'status' => 500 ) );
}
/**
* Fires after a single term is deleted via the REST API.
*
* @param WP_Term $term The deleted term.
* @param WP_REST_Response $response The response data.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request );
return $response;
}
/**
* Prepares a single term for create or update.
*
* @param WP_REST_Request $request Request object.
* @return object $prepared_term Term object.
*/
public function prepare_item_for_database( $request ) {
$prepared_term = new stdClass;
$schema = $this->get_item_schema();
if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
$prepared_term->name = $request['name'];
}
if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) {
$prepared_term->slug = $request['slug'];
}
if ( isset( $request['taxonomy'] ) && ! empty( $schema['properties']['taxonomy'] ) ) {
$prepared_term->taxonomy = $request['taxonomy'];
}
if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) {
$prepared_term->description = $request['description'];
}
if ( isset( $request['parent'] ) && ! empty( $schema['properties']['parent'] ) ) {
$parent_term_id = 0;
$parent_term = get_term( (int) $request['parent'], $this->taxonomy );
if ( $parent_term ) {
$parent_term_id = $parent_term->term_id;
}
$prepared_term->parent = $parent_term_id;
}
/**
* Filters term data before inserting term via the REST API.
*
* @param object $prepared_term Term object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( "rest_pre_insert_{$this->taxonomy}", $prepared_term, $request );
}
/**
* Prepares a single term output for response.
*
* @param obj $item Term object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response
*/
public function prepare_item_for_response( $item, $request ) {
$schema = $this->get_item_schema();
$data = array();
if ( ! empty( $schema['properties']['id'] ) ) {
$data['id'] = (int) $item->term_id;
}
if ( ! empty( $schema['properties']['count'] ) ) {
$data['count'] = (int) $item->count;
}
if ( ! empty( $schema['properties']['description'] ) ) {
$data['description'] = $item->description;
}
if ( ! empty( $schema['properties']['link'] ) ) {
$data['link'] = get_term_link( $item );
}
if ( ! empty( $schema['properties']['name'] ) ) {
$data['name'] = $item->name;
}
if ( ! empty( $schema['properties']['slug'] ) ) {
$data['slug'] = $item->slug;
}
if ( ! empty( $schema['properties']['taxonomy'] ) ) {
$data['taxonomy'] = $item->taxonomy;
}
if ( ! empty( $schema['properties']['parent'] ) ) {
$data['parent'] = (int) $item->parent;
}
if ( ! empty( $schema['properties']['meta'] ) ) {
$data['meta'] = $this->meta->get_value( $item->term_id, $request );
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item ) );
/**
* Filters a term item returned from the API.
*
* Allows modification of the term data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $item The original term object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $item, $request );
}
/**
* Prepares links for the request.
*
* @param object $term Term object.
* @return array Links for the given term.
*/
protected function prepare_links( $term ) {
$base = $this->namespace . '/' . $this->rest_base;
$links = array(
'self' => array(
'href' => rest_url( trailingslashit( $base ) . $term->term_id ),
),
'collection' => array(
'href' => rest_url( $base ),
),
'about' => array(
'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $this->taxonomy ) ),
),
);
if ( $term->parent ) {
$parent_term = get_term( (int) $term->parent, $term->taxonomy );
if ( $parent_term ) {
$links['up'] = array(
'href' => rest_url( trailingslashit( $base ) . $parent_term->term_id ),
'embeddable' => true,
);
}
}
$taxonomy_obj = get_taxonomy( $term->taxonomy );
if ( empty( $taxonomy_obj->object_type ) ) {
return $links;
}
$post_type_links = array();
foreach ( $taxonomy_obj->object_type as $type ) {
$post_type_object = get_post_type_object( $type );
if ( empty( $post_type_object->show_in_rest ) ) {
continue;
}
$rest_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
$post_type_links[] = array(
'href' => add_query_arg( $this->rest_base, $term->term_id, rest_url( sprintf( 'wp/v2/%s', $rest_base ) ) ),
);
}
if ( ! empty( $post_type_links ) ) {
$links['https://api.w.org/post_type'] = $post_type_links;
}
return $links;
}
/**
* Gets the term's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy,
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.' ),
'type' => 'integer',
'context' => array( 'view', 'embed', 'edit' ),
'readonly' => true,
),
'count' => array(
'description' => __( 'Number of published posts for the resource.' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'HTML description of the resource.' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_filter_post_kses',
),
),
'link' => array(
'description' => __( 'URL to the resource.' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'view', 'embed', 'edit' ),
'readonly' => true,
),
'name' => array(
'description' => __( 'HTML title for the resource.' ),
'type' => 'string',
'context' => array( 'view', 'embed', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
'required' => true,
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource unique to its type.' ),
'type' => 'string',
'context' => array( 'view', 'embed', 'edit' ),
'arg_options' => array(
'sanitize_callback' => array( $this, 'sanitize_slug' ),
),
),
'taxonomy' => array(
'description' => __( 'Type attribution for the resource.' ),
'type' => 'string',
'enum' => array_keys( get_taxonomies() ),
'context' => array( 'view', 'embed', 'edit' ),
'readonly' => true,
),
),
);
$taxonomy = get_taxonomy( $this->taxonomy );
if ( $taxonomy->hierarchical ) {
$schema['properties']['parent'] = array(
'description' => __( 'The id for the parent of the resource.' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
);
}
$schema['properties']['meta'] = $this->meta->get_field_schema();
return $this->add_additional_fields_schema( $schema );
}
/**
* Gets the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$query_params = parent::get_collection_params();
$taxonomy = get_taxonomy( $this->taxonomy );
$query_params['context']['default'] = 'view';
$query_params['exclude'] = array(
'description' => __( 'Ensure result set excludes specific ids.' ),
'type' => 'array',
'default' => array(),
'sanitize_callback' => 'wp_parse_id_list',
);
$query_params['include'] = array(
'description' => __( 'Limit result set to specific ids.' ),
'type' => 'array',
'default' => array(),
'sanitize_callback' => 'wp_parse_id_list',
);
if ( ! $taxonomy->hierarchical ) {
$query_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',
);
}
$query_params['order'] = array(
'description' => __( 'Order sort attribute ascending or descending.' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_key',
'default' => 'asc',
'enum' => array(
'asc',
'desc',
),
'validate_callback' => 'rest_validate_request_arg',
);
$query_params['orderby'] = array(
'description' => __( 'Sort collection by resource attribute.' ),
'type' => 'string',
'sanitize_callback' => 'sanitize_key',
'default' => 'name',
'enum' => array(
'id',
'include',
'name',
'slug',
'term_group',
'description',
'count',
),
'validate_callback' => 'rest_validate_request_arg',
);
$query_params['hide_empty'] = array(
'description' => __( 'Whether to hide resources not assigned to any posts.' ),
'type' => 'boolean',
'default' => false,
'validate_callback' => 'rest_validate_request_arg',
);
if ( $taxonomy->hierarchical ) {
$query_params['parent'] = array(
'description' => __( 'Limit result set to resources assigned to a specific parent.' ),
'type' => 'integer',
'sanitize_callback' => 'absint',
'validate_callback' => 'rest_validate_request_arg',
);
}
$query_params['post'] = array(
'description' => __( 'Limit result set to resources assigned to a specific post.' ),
'type' => 'integer',
'default' => null,
'validate_callback' => 'rest_validate_request_arg',
);
$query_params['slug'] = array(
'description' => __( 'Limit result set to resources with a specific slug.' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
return $query_params;
}
/**
* Checks that the taxonomy is valid.
*
* @param string $taxonomy Taxonomy to check.
* @return WP_Error|boolean
*/
protected function check_is_taxonomy_allowed( $taxonomy ) {
$taxonomy_obj = get_taxonomy( $taxonomy );
if ( $taxonomy_obj && ! empty( $taxonomy_obj->show_in_rest ) ) {
return true;
}
return false;
}
}

View File

@ -0,0 +1,990 @@
<?php
/**
* Access users
*/
class WP_REST_Users_Controller extends WP_REST_Controller {
/**
* Instance of a user meta fields object.
*
* @access protected
* @var WP_REST_User_Meta_Fields
*/
protected $meta;
public function __construct() {
$this->namespace = 'wp/v2';
$this->rest_base = 'users';
$this->meta = new WP_REST_User_Meta_Fields();
}
/**
* 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, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
array(
'methods' => WP_REST_Server::CREATABLE,
'callback' => array( $this, 'create_item' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/(?P<id>[\d]+)', array(
array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
'args' => array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
),
),
array(
'methods' => WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_item' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
),
array(
'methods' => WP_REST_Server::DELETABLE,
'callback' => array( $this, 'delete_item' ),
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
'args' => array(
'force' => array(
'default' => false,
'description' => __( 'Required to be true, as resource does not support trashing.' ),
),
'reassign' => array(),
),
),
'schema' => array( $this, 'get_public_item_schema' ),
) );
register_rest_route( $this->namespace, '/' . $this->rest_base . '/me', array(
'methods' => WP_REST_Server::READABLE,
'callback' => array( $this, 'get_current_item' ),
'args' => array(
'context' => array(),
),
'schema' => array( $this, 'get_public_item_schema' ),
));
}
/**
* Permissions check for getting all users.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
// Check if roles is specified in GET request and if user can list users.
if ( ! empty( $request['roles'] ) && ! current_user_can( 'list_users' ) ) {
return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot filter by role.' ), array( 'status' => rest_authorization_required_code() ) );
}
if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
}
if ( in_array( $request['orderby'], array( 'email', 'registered_date' ), true ) && ! current_user_can( 'list_users' ) ) {
return new WP_Error( 'rest_forbidden_orderby', __( 'Sorry, you cannot order by this parameter.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all users
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
// Retrieve the list of registered collection query parameters.
$registered = $this->get_collection_params();
// 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(
'exclude' => 'exclude',
'include' => 'include',
'order' => 'order',
'per_page' => 'number',
'search' => 'search',
'roles' => 'role__in',
);
$prepared_args = array();
// For each known parameter which is both registered and present in the request,
// set the parameter's value on the query $prepared_args.
foreach ( $parameter_mappings as $api_param => $wp_param ) {
if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
$prepared_args[ $wp_param ] = $request[ $api_param ];
}
}
if ( isset( $registered['offset'] ) && ! empty( $request['offset'] ) ) {
$prepared_args['offset'] = $request['offset'];
} else {
$prepared_args['offset'] = ( $request['page'] - 1 ) * $prepared_args['number'];
}
if ( isset( $registered['orderby'] ) ) {
$orderby_possibles = array(
'id' => 'ID',
'include' => 'include',
'name' => 'display_name',
'registered_date' => 'registered',
'slug' => 'user_nicename',
'email' => 'user_email',
'url' => 'user_url',
);
$prepared_args['orderby'] = $orderby_possibles[ $request['orderby'] ];
}
if ( ! current_user_can( 'list_users' ) ) {
$prepared_args['has_published_posts'] = true;
}
if ( ! empty( $prepared_args['search'] ) ) {
$prepared_args['search'] = '*' . $prepared_args['search'] . '*';
}
if ( isset( $registered['slug'] ) && ! empty( $request['slug'] ) ) {
$prepared_args['search'] = $request['slug'];
$prepared_args['search_columns'] = array( 'user_nicename' );
}
/**
* Filter arguments, before passing to WP_User_Query, when querying users via the REST API.
*
* @see https://developer.wordpress.org/reference/classes/wp_user_query/
*
* @param array $prepared_args Array of arguments for WP_User_Query.
* @param WP_REST_Request $request The current request.
*/
$prepared_args = apply_filters( 'rest_user_query', $prepared_args, $request );
$query = new WP_User_Query( $prepared_args );
$users = array();
foreach ( $query->results as $user ) {
$data = $this->prepare_item_for_response( $user, $request );
$users[] = $this->prepare_response_for_collection( $data );
}
$response = rest_ensure_response( $users );
// Store pagination values for headers then unset for count query.
$per_page = (int) $prepared_args['number'];
$page = ceil( ( ( (int) $prepared_args['offset'] ) / $per_page ) + 1 );
$prepared_args['fields'] = 'ID';
$total_users = $query->get_total();
if ( $total_users < 1 ) {
// Out-of-bounds, run the query again without LIMIT for total count
unset( $prepared_args['number'], $prepared_args['offset'] );
$count_query = new WP_User_Query( $prepared_args );
$total_users = $count_query->get_total();
}
$response->header( 'X-WP-Total', (int) $total_users );
$max_pages = ceil( $total_users / $per_page );
$response->header( 'X-WP-TotalPages', (int) $max_pages );
$base = add_query_arg( $request->get_query_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;
}
/**
* Check if a given request has access to read a user
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
$id = (int) $request['id'];
$user = get_userdata( $id );
$types = get_post_types( array( 'show_in_rest' => true ), 'names' );
if ( empty( $id ) || empty( $user->ID ) ) {
return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 404 ) );
}
if ( get_current_user_id() === $id ) {
return true;
}
if ( 'edit' === $request['context'] && ! current_user_can( 'list_users' ) ) {
return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this resource with edit context.' ), array( 'status' => rest_authorization_required_code() ) );
} elseif ( ! count_user_posts( $id, $types ) && ! current_user_can( 'edit_user', $id ) && ! current_user_can( 'list_users' ) ) {
return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you cannot view this resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get a single user
*
* @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'];
$user = get_userdata( $id );
if ( empty( $id ) || empty( $user->ID ) ) {
return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 404 ) );
}
$user = $this->prepare_item_for_response( $user, $request );
$response = rest_ensure_response( $user );
return $response;
}
/**
* Get the current user
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_current_item( $request ) {
$current_user_id = get_current_user_id();
if ( empty( $current_user_id ) ) {
return new WP_Error( 'rest_not_logged_in', __( 'You are not currently logged in.' ), array( 'status' => 401 ) );
}
$user = wp_get_current_user();
$response = $this->prepare_item_for_response( $user, $request );
$response = rest_ensure_response( $response );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $current_user_id ) ) );
$response->set_status( 302 );
return $response;
}
/**
* Check if a given request has access create users
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function create_item_permissions_check( $request ) {
if ( ! current_user_can( 'create_users' ) ) {
return new WP_Error( 'rest_cannot_create_user', __( 'Sorry, you are not allowed to create resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Create a single user
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function create_item( $request ) {
if ( ! empty( $request['id'] ) ) {
return new WP_Error( 'rest_user_exists', __( 'Cannot create existing resource.' ), array( 'status' => 400 ) );
}
$schema = $this->get_item_schema();
if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) {
$check_permission = $this->check_role_update( $request['id'], $request['roles'] );
if ( is_wp_error( $check_permission ) ) {
return $check_permission;
}
}
$user = $this->prepare_item_for_database( $request );
if ( is_multisite() ) {
$ret = wpmu_validate_user_signup( $user->user_login, $user->user_email );
if ( is_wp_error( $ret['errors'] ) && ! empty( $ret['errors']->errors ) ) {
return $ret['errors'];
}
}
if ( is_multisite() ) {
$user_id = wpmu_create_user( $user->user_login, $user->user_pass, $user->user_email );
if ( ! $user_id ) {
return new WP_Error( 'rest_user_create', __( 'Error creating new resource.' ), array( 'status' => 500 ) );
}
$user->ID = $user_id;
$user_id = wp_update_user( $user );
if ( is_wp_error( $user_id ) ) {
return $user_id;
}
} else {
$user_id = wp_insert_user( $user );
if ( is_wp_error( $user_id ) ) {
return $user_id;
}
}
$user = get_user_by( 'id', $user_id );
if ( ! empty( $request['roles'] ) && ! empty( $schema['properties']['roles'] ) ) {
array_map( array( $user, 'add_role' ), $request['roles'] );
}
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
$meta_update = $this->meta->update_value( $request['meta'], $user_id );
if ( is_wp_error( $meta_update ) ) {
return $meta_update;
}
}
$fields_update = $this->update_additional_fields_for_object( $user, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
/**
* Fires after a user is created or updated via the REST API.
*
* @param WP_User $user Data used to create the user.
* @param WP_REST_Request $request Request object.
* @param boolean $creating True when creating user, false when updating user.
*/
do_action( 'rest_insert_user', $user, $request, true );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $user, $request );
$response = rest_ensure_response( $response );
$response->set_status( 201 );
$response->header( 'Location', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user_id ) ) );
return $response;
}
/**
* Check if a given request has access update a user
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function update_item_permissions_check( $request ) {
$id = (int) $request['id'];
if ( ! current_user_can( 'edit_user', $id ) ) {
return new WP_Error( 'rest_cannot_edit', __( 'Sorry, you are not allowed to edit resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
if ( ! empty( $request['roles'] ) && ! current_user_can( 'edit_users' ) ) {
return new WP_Error( 'rest_cannot_edit_roles', __( 'Sorry, you are not allowed to edit roles of this resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Update a single user
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function update_item( $request ) {
$id = (int) $request['id'];
$user = get_userdata( $id );
if ( ! $user ) {
return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 404 ) );
}
if ( email_exists( $request['email'] ) && $request['email'] !== $user->user_email ) {
return new WP_Error( 'rest_user_invalid_email', __( 'Email address is invalid.' ), array( 'status' => 400 ) );
}
if ( ! empty( $request['username'] ) && $request['username'] !== $user->user_login ) {
return new WP_Error( 'rest_user_invalid_argument', __( "Username isn't editable." ), array( 'status' => 400 ) );
}
if ( ! empty( $request['slug'] ) && $request['slug'] !== $user->user_nicename && get_user_by( 'slug', $request['slug'] ) ) {
return new WP_Error( 'rest_user_invalid_slug', __( 'Slug is invalid.' ), array( 'status' => 400 ) );
}
if ( ! empty( $request['roles'] ) ) {
$check_permission = $this->check_role_update( $id, $request['roles'] );
if ( is_wp_error( $check_permission ) ) {
return $check_permission;
}
}
$user = $this->prepare_item_for_database( $request );
// Ensure we're operating on the same user we already checked
$user->ID = $id;
$user_id = wp_update_user( $user );
if ( is_wp_error( $user_id ) ) {
return $user_id;
}
$user = get_user_by( 'id', $id );
if ( ! empty( $request['roles'] ) ) {
array_map( array( $user, 'add_role' ), $request['roles'] );
}
$schema = $this->get_item_schema();
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
$meta_update = $this->meta->update_value( $request['meta'], $id );
if ( is_wp_error( $meta_update ) ) {
return $meta_update;
}
}
$fields_update = $this->update_additional_fields_for_object( $user, $request );
if ( is_wp_error( $fields_update ) ) {
return $fields_update;
}
/* This action is documented in lib/endpoints/class-wp-rest-users-controller.php */
do_action( 'rest_insert_user', $user, $request, false );
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $user, $request );
$response = rest_ensure_response( $response );
return $response;
}
/**
* Check if a given request has access delete a user
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function delete_item_permissions_check( $request ) {
$id = (int) $request['id'];
if ( ! current_user_can( 'delete_user', $id ) ) {
return new WP_Error( 'rest_user_cannot_delete', __( 'Sorry, you are not allowed to delete this resource.' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Delete a single user
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function delete_item( $request ) {
$id = (int) $request['id'];
$reassign = isset( $request['reassign'] ) ? absint( $request['reassign'] ) : null;
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
// We don't support trashing for this type, error out
if ( ! $force ) {
return new WP_Error( 'rest_trash_not_supported', __( 'Users do not support trashing.' ), array( 'status' => 501 ) );
}
$user = get_userdata( $id );
if ( ! $user ) {
return new WP_Error( 'rest_user_invalid_id', __( 'Invalid resource id.' ), array( 'status' => 404 ) );
}
if ( ! empty( $reassign ) ) {
if ( $reassign === $id || ! get_userdata( $reassign ) ) {
return new WP_Error( 'rest_user_invalid_reassign', __( 'Invalid resource id for reassignment.' ), array( 'status' => 400 ) );
}
}
$request->set_param( 'context', 'edit' );
$response = $this->prepare_item_for_response( $user, $request );
/** Include admin user functions to get access to wp_delete_user() */
require_once ABSPATH . 'wp-admin/includes/user.php';
$result = wp_delete_user( $id, $reassign );
if ( ! $result ) {
return new WP_Error( 'rest_cannot_delete', __( 'The resource cannot be deleted.' ), array( 'status' => 500 ) );
}
/**
* Fires after a user is deleted via the REST API.
*
* @param WP_User $user The user data.
* @param WP_REST_Response $response The response returned from the API.
* @param WP_REST_Request $request The request sent to the API.
*/
do_action( 'rest_delete_user', $user, $response, $request );
return $response;
}
/**
* Prepare a single user output for response
*
* @param object $user User object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $user, $request ) {
$data = array();
$schema = $this->get_item_schema();
if ( ! empty( $schema['properties']['id'] ) ) {
$data['id'] = $user->ID;
}
if ( ! empty( $schema['properties']['username'] ) ) {
$data['username'] = $user->user_login;
}
if ( ! empty( $schema['properties']['name'] ) ) {
$data['name'] = $user->display_name;
}
if ( ! empty( $schema['properties']['first_name'] ) ) {
$data['first_name'] = $user->first_name;
}
if ( ! empty( $schema['properties']['last_name'] ) ) {
$data['last_name'] = $user->last_name;
}
if ( ! empty( $schema['properties']['email'] ) ) {
$data['email'] = $user->user_email;
}
if ( ! empty( $schema['properties']['url'] ) ) {
$data['url'] = $user->user_url;
}
if ( ! empty( $schema['properties']['description'] ) ) {
$data['description'] = $user->description;
}
if ( ! empty( $schema['properties']['link'] ) ) {
$data['link'] = get_author_posts_url( $user->ID, $user->user_nicename );
}
if ( ! empty( $schema['properties']['nickname'] ) ) {
$data['nickname'] = $user->nickname;
}
if ( ! empty( $schema['properties']['slug'] ) ) {
$data['slug'] = $user->user_nicename;
}
if ( ! empty( $schema['properties']['roles'] ) ) {
// Defensively call array_values() to ensure an array is returned.
$data['roles'] = array_values( $user->roles );
}
if ( ! empty( $schema['properties']['registered_date'] ) ) {
$data['registered_date'] = date( 'c', strtotime( $user->user_registered ) );
}
if ( ! empty( $schema['properties']['capabilities'] ) ) {
$data['capabilities'] = (object) $user->allcaps;
}
if ( ! empty( $schema['properties']['extra_capabilities'] ) ) {
$data['extra_capabilities'] = (object) $user->caps;
}
if ( ! empty( $schema['properties']['avatar_urls'] ) ) {
$data['avatar_urls'] = rest_get_avatar_urls( $user->user_email );
}
if ( ! empty( $schema['properties']['meta'] ) ) {
$data['meta'] = $this->meta->get_value( $user->ID, $request );
}
$context = ! empty( $request['context'] ) ? $request['context'] : 'embed';
$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( $user ) );
/**
* Filter user data returned from the REST API.
*
* @param WP_REST_Response $response The response object.
* @param object $user User object used to create response.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'rest_prepare_user', $response, $user, $request );
}
/**
* Prepare links for the request.
*
* @param WP_Post $user User object.
* @return array Links for the given user.
*/
protected function prepare_links( $user ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->rest_base, $user->ID ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Prepare a single user for create or update
*
* @param WP_REST_Request $request Request object.
* @return object $prepared_user User object.
*/
protected function prepare_item_for_database( $request ) {
$prepared_user = new stdClass;
$schema = $this->get_item_schema();
// required arguments.
if ( isset( $request['email'] ) && ! empty( $schema['properties']['email'] ) ) {
$prepared_user->user_email = $request['email'];
}
if ( isset( $request['username'] ) && ! empty( $schema['properties']['username'] ) ) {
$prepared_user->user_login = $request['username'];
}
if ( isset( $request['password'] ) && ! empty( $schema['properties']['password'] ) ) {
$prepared_user->user_pass = $request['password'];
}
// optional arguments.
if ( isset( $request['id'] ) ) {
$prepared_user->ID = absint( $request['id'] );
}
if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
$prepared_user->display_name = $request['name'];
}
if ( isset( $request['first_name'] ) && ! empty( $schema['properties']['first_name'] ) ) {
$prepared_user->first_name = $request['first_name'];
}
if ( isset( $request['last_name'] ) && ! empty( $schema['properties']['last_name'] ) ) {
$prepared_user->last_name = $request['last_name'];
}
if ( isset( $request['nickname'] ) && ! empty( $schema['properties']['nickname'] ) ) {
$prepared_user->nickname = $request['nickname'];
}
if ( isset( $request['slug'] ) && ! empty( $schema['properties']['slug'] ) ) {
$prepared_user->user_nicename = $request['slug'];
}
if ( isset( $request['description'] ) && ! empty( $schema['properties']['description'] ) ) {
$prepared_user->description = $request['description'];
}
if ( isset( $request['url'] ) && ! empty( $schema['properties']['url'] ) ) {
$prepared_user->user_url = $request['url'];
}
// setting roles will be handled outside of this function.
if ( isset( $request['roles'] ) ) {
$prepared_user->role = false;
}
/**
* Filter user data before inserting user via the REST API.
*
* @param object $prepared_user User object.
* @param WP_REST_Request $request Request object.
*/
return apply_filters( 'rest_pre_insert_user', $prepared_user, $request );
}
/**
* Determine if the current user is allowed to make the desired roles change.
*
* @param integer $user_id User ID.
* @param array $roles New user roles.
* @return WP_Error|boolean
*/
protected function check_role_update( $user_id, $roles ) {
global $wp_roles;
foreach ( $roles as $role ) {
if ( ! isset( $wp_roles->role_objects[ $role ] ) ) {
return new WP_Error( 'rest_user_invalid_role', sprintf( __( 'The role %s does not exist.' ), $role ), array( 'status' => 400 ) );
}
$potential_role = $wp_roles->role_objects[ $role ];
// Don't let anyone with 'edit_users' (admins) edit their own role to something without it.
// Multisite super admins can freely edit their blog roles -- they possess all caps.
if ( ! ( is_multisite() && current_user_can( 'manage_sites' ) ) && get_current_user_id() === $user_id && ! $potential_role->has_cap( 'edit_users' ) ) {
return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give resource that role.' ), array( 'status' => rest_authorization_required_code() ) );
}
// The new role must be editable by the logged-in user.
/** Include admin functions to get access to get_editable_roles() */
require_once ABSPATH . 'wp-admin/includes/admin.php';
$editable_roles = get_editable_roles();
if ( empty( $editable_roles[ $role ] ) ) {
return new WP_Error( 'rest_user_invalid_role', __( 'You cannot give resource that role.' ), array( 'status' => 403 ) );
}
}
return true;
}
/**
* Get the User's schema, conforming to JSON Schema
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'user',
'type' => 'object',
'properties' => array(
'id' => array(
'description' => __( 'Unique identifier for the resource.' ),
'type' => 'integer',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'username' => array(
'description' => __( 'Login name for the resource.' ),
'type' => 'string',
'context' => array( 'edit' ),
'required' => true,
'arg_options' => array(
'sanitize_callback' => 'sanitize_user',
),
),
'name' => array(
'description' => __( 'Display name for the resource.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'first_name' => array(
'description' => __( 'First name for the resource.' ),
'type' => 'string',
'context' => array( 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'last_name' => array(
'description' => __( 'Last name for the resource.' ),
'type' => 'string',
'context' => array( 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'email' => array(
'description' => __( 'The email address for the resource.' ),
'type' => 'string',
'format' => 'email',
'context' => array( 'edit' ),
'required' => true,
),
'url' => array(
'description' => __( 'URL of the resource.' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'embed', 'view', 'edit' ),
),
'description' => array(
'description' => __( 'Description of the resource.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'wp_filter_post_kses',
),
),
'link' => array(
'description' => __( 'Author URL to the resource.' ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
),
'nickname' => array(
'description' => __( 'The nickname for the resource.' ),
'type' => 'string',
'context' => array( 'edit' ),
'arg_options' => array(
'sanitize_callback' => 'sanitize_text_field',
),
),
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.' ),
'type' => 'string',
'context' => array( 'embed', 'view', 'edit' ),
'arg_options' => array(
'sanitize_callback' => array( $this, 'sanitize_slug' ),
),
),
'registered_date' => array(
'description' => __( 'Registration date for the resource.' ),
'type' => 'string',
'format' => 'date-time',
'context' => array( 'edit' ),
'readonly' => true,
),
'roles' => array(
'description' => __( 'Roles assigned to the resource.' ),
'type' => 'array',
'context' => array( 'edit' ),
),
'password' => array(
'description' => __( 'Password for the resource (never included).' ),
'type' => 'string',
'context' => array(), // Password is never displayed
'required' => true,
),
'capabilities' => array(
'description' => __( 'All capabilities assigned to the resource.' ),
'type' => 'object',
'context' => array( 'edit' ),
'readonly' => true,
),
'extra_capabilities' => array(
'description' => __( 'Any extra capabilities assigned to the resource.' ),
'type' => 'object',
'context' => array( 'edit' ),
'readonly' => true,
),
),
);
if ( get_option( 'show_avatars' ) ) {
$avatar_properties = array();
$avatar_sizes = rest_get_avatar_sizes();
foreach ( $avatar_sizes as $size ) {
$avatar_properties[ $size ] = array(
'description' => sprintf( __( 'Avatar URL with image size of %d pixels.' ), $size ),
'type' => 'string',
'format' => 'uri',
'context' => array( 'embed', 'view', 'edit' ),
);
}
$schema['properties']['avatar_urls'] = array(
'description' => __( 'Avatar URLs for the resource.' ),
'type' => 'object',
'context' => array( 'embed', 'view', 'edit' ),
'readonly' => true,
'properties' => $avatar_properties,
);
}
$schema['properties']['meta'] = $this->meta->get_field_schema();
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections
*
* @return array
*/
public function get_collection_params() {
$query_params = parent::get_collection_params();
$query_params['context']['default'] = 'view';
$query_params['exclude'] = array(
'description' => __( 'Ensure result set excludes specific ids.' ),
'type' => 'array',
'default' => array(),
'sanitize_callback' => 'wp_parse_id_list',
);
$query_params['include'] = array(
'description' => __( 'Limit result set to specific ids.' ),
'type' => 'array',
'default' => array(),
'sanitize_callback' => 'wp_parse_id_list',
);
$query_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',
);
$query_params['order'] = array(
'default' => 'asc',
'description' => __( 'Order sort attribute ascending or descending.' ),
'enum' => array( 'asc', 'desc' ),
'sanitize_callback' => 'sanitize_key',
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
$query_params['orderby'] = array(
'default' => 'name',
'description' => __( 'Sort collection by object attribute.' ),
'enum' => array(
'id',
'include',
'name',
'registered_date',
'slug',
'email',
'url',
),
'sanitize_callback' => 'sanitize_key',
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
$query_params['slug'] = array(
'description' => __( 'Limit result set to resources with a specific slug.' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
$query_params['roles'] = array(
'description' => __( 'Limit result set to resources matching at least one specific role provided. Accepts csv list or single role.' ),
'type' => 'array',
'sanitize_callback' => 'wp_parse_slug_list',
);
return $query_params;
}
}

View File

@ -0,0 +1,21 @@
<?php
class WP_REST_Comment_Meta_Fields extends WP_REST_Meta_Fields {
/**
* Get the object type for meta.
*
* @return string
*/
protected function get_meta_type() {
return 'comment';
}
/**
* Get the type for `register_rest_field`.
*
* @return string
*/
public function get_rest_field_type() {
return 'comment';
}
}

View File

@ -0,0 +1,365 @@
<?php
/**
* Manage meta values for an object.
*/
abstract class WP_REST_Meta_Fields {
/**
* Get the object type for meta.
*
* @return string One of 'post', 'comment', 'term', 'user', or anything
* else supported by `_get_meta_table()`.
*/
abstract protected function get_meta_type();
/**
* Get the object type for `register_rest_field`.
*
* @return string Custom post type, 'taxonomy', 'comment', or `user`.
*/
abstract protected function get_rest_field_type();
/**
* Register the meta field.
*/
public function register_field() {
register_rest_field( $this->get_rest_field_type(), 'meta', array(
'get_callback' => array( $this, 'get_value' ),
'update_callback' => array( $this, 'update_value' ),
'schema' => $this->get_field_schema(),
));
}
/**
* Get the `meta` field value.
*
* @param int $object_id Object ID to fetch meta for.
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|object
*/
public function get_value( $object_id, $request ) {
$fields = $this->get_registered_fields();
$response = array();
foreach ( $fields as $name => $args ) {
$all_values = get_metadata( $this->get_meta_type(), $object_id, $name, false );
if ( $args['single'] ) {
if ( empty( $all_values ) ) {
$value = $args['schema']['default'];
} else {
$value = $all_values[0];
}
$value = $this->prepare_value_for_response( $value, $request, $args );
} else {
$value = array();
foreach ( $all_values as $row ) {
$value[] = $this->prepare_value_for_response( $row, $request, $args );
}
}
$response[ $name ] = $value;
}
return (object) $response;
}
/**
* Prepare value for response.
*
* This is required because some native types cannot be stored correctly in
* the database, such as booleans. We need to cast back to the relevant
* type before passing back to JSON.
*
* @param mixed $value Value to prepare.
* @param WP_REST_Request $request Current request object.
* @param array $args Options for the field.
* @return mixed Prepared value.
*/
protected function prepare_value_for_response( $value, $request, $args ) {
if ( ! empty( $args['prepare_callback'] ) ) {
$value = call_user_func( $args['prepare_callback'], $value, $request, $args );
}
return $value;
}
/**
* Update meta values.
*
* @param WP_REST_Request $request Full details about the request.
* @param int $object_id Object ID to fetch meta for.
* @return WP_Error|null Error if one occurs, null on success.
*/
public function update_value( $request, $object_id ) {
$fields = $this->get_registered_fields();
foreach ( $fields as $name => $args ) {
if ( ! array_key_exists( $name, $request ) ) {
continue;
}
// A null value means reset the field, which is essentially deleting it
// from the database and then relying on the default value.
if ( is_null( $request[ $name ] ) ) {
$result = $this->delete_meta_value( $object_id, $name );
} elseif ( $args['single'] ) {
$result = $this->update_meta_value( $object_id, $name, $request[ $name ] );
} else {
$result = $this->update_multi_meta_value( $object_id, $name, $request[ $name ] );
}
if ( is_wp_error( $result ) ) {
return $result;
}
}
return null;
}
/**
* Delete meta value for an object.
*
* @param int $object_id Object ID the field belongs to.
* @param string $name Key for the field.
* @return bool|WP_Error True if meta field is deleted, error otherwise.
*/
protected function delete_meta_value( $object_id, $name ) {
if ( ! current_user_can( 'delete_post_meta', $object_id, $name ) ) {
return new WP_Error(
'rest_cannot_delete',
sprintf( __( 'You do not have permission to edit the %s custom field.' ), $name ),
array( 'key' => $name, 'status' => rest_authorization_required_code() )
);
}
if ( ! delete_metadata( $this->get_meta_type(), $object_id, wp_slash( $name ) ) ) {
return new WP_Error(
'rest_meta_database_error',
__( 'Could not delete meta value from database.' ),
array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR )
);
}
return true;
}
/**
* Update multiple meta values for an object.
*
* Alters the list of values in the database to match the list of provided values.
*
* @param int $object_id Object ID to update.
* @param string $name Key for the custom field.
* @param array $values List of values to update to.
* @return bool|WP_Error True if meta fields are updated, error otherwise.
*/
protected function update_multi_meta_value( $object_id, $name, $values ) {
if ( ! current_user_can( 'edit_post_meta', $object_id, $name ) ) {
return new WP_Error(
'rest_cannot_update',
sprintf( __( 'You do not have permission to edit the %s custom field.' ), $name ),
array( 'key' => $name, 'status' => rest_authorization_required_code() )
);
}
$current = get_metadata( $this->get_meta_type(), $object_id, $name, false );
$to_remove = $current;
$to_add = $values;
foreach ( $to_add as $add_key => $value ) {
$remove_keys = array_keys( $to_remove, $value, true );
if ( empty( $remove_keys ) ) {
continue;
}
if ( count( $remove_keys ) > 1 ) {
// To remove, we need to remove first, then add, so don't touch.
continue;
}
$remove_key = $remove_keys[0];
unset( $to_remove[ $remove_key ] );
unset( $to_add[ $add_key ] );
}
// `delete_metadata` removes _all_ instances of the value, so only call
// once.
$to_remove = array_unique( $to_remove );
foreach ( $to_remove as $value ) {
if ( ! delete_metadata( $this->get_meta_type(), $object_id, wp_slash( $name ), wp_slash( $value ) ) ) {
return new WP_Error(
'rest_meta_database_error',
__( 'Could not update meta value in database.' ),
array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR )
);
}
}
foreach ( $to_add as $value ) {
if ( ! add_metadata( $this->get_meta_type(), $object_id, wp_slash( $name ), wp_slash( $value ) ) ) {
return new WP_Error(
'rest_meta_database_error',
__( 'Could not update meta value in database.' ),
array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR )
);
}
}
return true;
}
/**
* Update meta value for an object.
*
* @param int $object_id Object ID to update.
* @param string $name Key for the custom field.
* @param mixed $value Updated value.
* @return bool|WP_Error True if meta field is updated, error otherwise.
*/
protected function update_meta_value( $object_id, $name, $value ) {
if ( ! current_user_can( 'edit_post_meta', $object_id, $name ) ) {
return new WP_Error(
'rest_cannot_update',
sprintf( __( 'You do not have permission to edit the %s custom field.' ), $name ),
array( 'key' => $name, 'status' => rest_authorization_required_code() )
);
}
$meta_type = $this->get_meta_type();
$meta_key = wp_slash( $name );
$meta_value = wp_slash( $value );
// Do the exact same check for a duplicate value as in update_metadata() to avoid update_metadata() returning false.
$old_value = get_metadata( $meta_type, $object_id, $meta_key );
if ( 1 === count( $old_value ) ) {
if ( $old_value[0] === $meta_value ) {
return true;
}
}
if ( ! update_metadata( $meta_type, $object_id, $meta_key, $meta_value ) ) {
return new WP_Error(
'rest_meta_database_error',
__( 'Could not update meta value in database.' ),
array( 'key' => $name, 'status' => WP_Http::INTERNAL_SERVER_ERROR )
);
}
return true;
}
/**
* Get all the registered meta fields.
*
* @return array
*/
protected function get_registered_fields() {
$registered = array();
foreach ( get_registered_meta_keys( $this->get_meta_type() ) as $name => $args ) {
if ( empty( $args['show_in_rest'] ) ) {
continue;
}
$rest_args = array();
if ( is_array( $args['show_in_rest'] ) ) {
$rest_args = $args['show_in_rest'];
}
$default_args = array(
'name' => $name,
'single' => $args['single'],
'schema' => array(),
'prepare_callback' => array( $this, 'prepare_value' ),
);
$default_schema = array(
'type' => null,
'description' => empty( $args['description'] ) ? '' : $args['description'],
'default' => isset( $args['default'] ) ? $args['default'] : null,
);
$rest_args = array_merge( $default_args, $rest_args );
$rest_args['schema'] = array_merge( $default_schema, $rest_args['schema'] );
if ( empty( $rest_args['schema']['type'] ) ) {
// Skip over meta fields that don't have a defined type.
if ( empty( $args['type'] ) ) {
continue;
}
if ( $rest_args['single'] ) {
$rest_args['schema']['type'] = $args['type'];
} else {
$rest_args['schema']['type'] = 'array';
$rest_args['schema']['items'] = array(
'type' => $args['type'],
);
}
}
$registered[ $rest_args['name'] ] = $rest_args;
} // End foreach().
return $registered;
}
/**
* Get the object's `meta` schema, conforming to JSON Schema.
*
* @return array
*/
public function get_field_schema() {
$fields = $this->get_registered_fields();
$schema = array(
'description' => __( 'Meta fields.' ),
'type' => 'object',
'context' => array( 'view', 'edit' ),
'properties' => array(),
);
foreach ( $fields as $key => $args ) {
$schema['properties'][ $key ] = $args['schema'];
}
return $schema;
}
/**
* Prepare a meta value for output.
*
* Default preparation for meta fields. Override by passing the
* `prepare_callback` in your `show_in_rest` options.
*
* @param mixed $value Meta value from the database.
* @param WP_REST_Request $request Request object.
* @param array $args REST-specific options for the meta key.
* @return mixed Value prepared for output.
*/
public static function prepare_value( $value, $request, $args ) {
$type = $args['schema']['type'];
// For multi-value fields, check the item type instead.
if ( 'array' === $type && ! empty( $args['schema']['items']['type'] ) ) {
$type = $args['schema']['items']['type'];
}
switch ( $type ) {
case 'string':
$value = (string) $value;
break;
case 'number':
$value = (float) $value;
break;
case 'boolean':
$value = (bool) $value;
break;
}
// Don't allow objects to be output.
if ( is_object( $value ) && ! ( $value instanceof JsonSerializable ) ) {
return null;
}
return $value;
}
}

View File

@ -0,0 +1,37 @@
<?php
class WP_REST_Post_Meta_Fields extends WP_REST_Meta_Fields {
/**
* Post type to register fields for.
*
* @var string
*/
protected $post_type;
/**
* Constructor.
*
* @param string $post_type Post type to register fields for.
*/
public function __construct( $post_type ) {
$this->post_type = $post_type;
}
/**
* Get the object type for meta.
*
* @return string
*/
protected function get_meta_type() {
return 'post';
}
/**
* Get the type for `register_rest_field`.
*
* @return string Custom post type slug.
*/
public function get_rest_field_type() {
return $this->post_type;
}
}

View File

@ -0,0 +1,39 @@
<?php
/**
* Manage meta values for terms.
*/
class WP_REST_Term_Meta_Fields extends WP_REST_Meta_Fields {
/**
* Taxonomy to register fields for.
*
* @var string
*/
protected $taxonomy;
/**
* Constructor.
*
* @param string $taxonomy Taxonomy to register fields for.
*/
public function __construct( $taxonomy ) {
$this->taxonomy = $taxonomy;
}
/**
* Get the object type for meta.
*
* @return string
*/
protected function get_meta_type() {
return 'term';
}
/**
* Get the type for `register_rest_field`.
*
* @return string
*/
public function get_rest_field_type() {
return 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy;
}
}

View File

@ -0,0 +1,21 @@
<?php
class WP_REST_User_Meta_Fields extends WP_REST_Meta_Fields {
/**
* Get the object type for meta.
*
* @return string
*/
protected function get_meta_type() {
return 'user';
}
/**
* Get the type for `register_rest_field`.
*
* @return string
*/
public function get_rest_field_type() {
return 'user';
}
}

View File

@ -499,6 +499,13 @@ function wp_default_scripts( &$scripts ) {
$scripts->add( 'media-audiovideo', "/wp-includes/js/media-audiovideo$suffix.js", array( 'media-editor' ), false, 1 );
$scripts->add( 'mce-view', "/wp-includes/js/mce-view$suffix.js", array( 'shortcode', 'jquery', 'media-views', 'media-audiovideo' ), false, 1 );
$scripts->add( 'wp-api', "/wp-includes/js/wp-api$suffix.js", array( 'jquery', 'backbone', 'underscore' ), false, 1 );
did_action( 'init' ) && $scripts->localize( 'wp-api', 'wpApiSettings', array(
'root' => esc_url_raw( get_rest_url() ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'versionString' => 'wp/v2/',
) );
if ( is_admin() ) {
$scripts->add( 'admin-tags', "/wp-admin/js/tags$suffix.js", array( 'jquery', 'wp-ajax-response' ), false, 1 );
did_action( 'init' ) && $scripts->localize( 'admin-tags', 'tagsl10n', array(

View File

@ -67,6 +67,9 @@ function create_initial_taxonomies() {
'delete_terms' => 'delete_categories',
'assign_terms' => 'assign_categories',
),
'show_in_rest' => true,
'rest_base' => 'categories',
'rest_controller_class' => 'WP_REST_Terms_Controller',
) );
register_taxonomy( 'post_tag', 'post', array(
@ -83,6 +86,9 @@ function create_initial_taxonomies() {
'delete_terms' => 'delete_post_tags',
'assign_terms' => 'assign_post_tags',
),
'show_in_rest' => true,
'rest_base' => 'tags',
'rest_controller_class' => 'WP_REST_Terms_Controller',
) );
register_taxonomy( 'nav_menu', 'nav_menu_item', array(

View File

@ -218,6 +218,22 @@ require( ABSPATH . WPINC . '/rest-api.php' );
require( ABSPATH . WPINC . '/rest-api/class-wp-rest-server.php' );
require( ABSPATH . WPINC . '/rest-api/class-wp-rest-response.php' );
require( ABSPATH . WPINC . '/rest-api/class-wp-rest-request.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-posts-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-attachments-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-types-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-post-statuses-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-revisions-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-taxonomies-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-terms-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-users-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-comments-controller.php' );
require( ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-settings-controller.php' );
require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php' );
require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php' );
require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php' );
require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-term-meta-fields.php' );
require( ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-user-meta-fields.php' );
$GLOBALS['wp_embed'] = new WP_Embed();

View File

@ -0,0 +1,579 @@
<!DOCTYPE html>
<html lang="en" class="">
<head prefix="og: http://ogp.me/ns# fb: http://ogp.me/ns/fb# object: http://ogp.me/ns/object# article: http://ogp.me/ns/article# profile: http://ogp.me/ns/profile#">
<meta charset='utf-8'>
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github-c1d91683d54d54239f36043630b76fe2b39dbc4fc91cfbd38ef9f3743e56c851.css" media="all" rel="stylesheet" />
<link crossorigin="anonymous" href="https://assets-cdn.github.com/assets/github2-6b792c972e1d61f45ce186e5e65d3410c90cf78d94776abff9512f5417eb2bac.css" media="all" rel="stylesheet" />
<link as="script" href="https://assets-cdn.github.com/assets/frameworks-ee521b8e9facac68ff27e93fc3ae0f8ed811d7bf9e434e84f4b9ea227780b084.js" rel="preload" />
<link as="script" href="https://assets-cdn.github.com/assets/github-863d0e4c2905010278cfd87e9c7c738e812530db73c36c274a185354977a2e41.js" rel="preload" />
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta http-equiv="Content-Language" content="en">
<meta name="viewport" content="width=1020">
<title>wp-cli.github.com/codeispoetry.png at master · wp-cli/wp-cli.github.com · GitHub</title>
<link rel="search" type="application/opensearchdescription+xml" href="/opensearch.xml" title="GitHub">
<link rel="fluid-icon" href="https://github.com/fluidicon.png" title="GitHub">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="apple-touch-icon" sizes="57x57" href="/apple-touch-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/apple-touch-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/apple-touch-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/apple-touch-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/apple-touch-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/apple-touch-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/apple-touch-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/apple-touch-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon-180x180.png">
<meta property="fb:app_id" content="1401488693436528">
<meta content="https://avatars1.githubusercontent.com/u/1570774?v=3&amp;s=400" name="twitter:image:src" /><meta content="@github" name="twitter:site" /><meta content="summary" name="twitter:card" /><meta content="wp-cli/wp-cli.github.com" name="twitter:title" /><meta content="wp-cli.github.com - wp-cli.org website" name="twitter:description" />
<meta content="https://avatars1.githubusercontent.com/u/1570774?v=3&amp;s=400" property="og:image" /><meta content="GitHub" property="og:site_name" /><meta content="object" property="og:type" /><meta content="wp-cli/wp-cli.github.com" property="og:title" /><meta content="https://github.com/wp-cli/wp-cli.github.com" property="og:url" /><meta content="wp-cli.github.com - wp-cli.org website" property="og:description" />
<meta name="browser-stats-url" content="https://api.github.com/_private/browser/stats">
<meta name="browser-errors-url" content="https://api.github.com/_private/browser/errors">
<link rel="assets" href="https://assets-cdn.github.com/">
<meta name="pjax-timeout" content="1000">
<meta name="msapplication-TileImage" content="/windows-tile.png">
<meta name="msapplication-TileColor" content="#ffffff">
<meta name="selected-link" value="repo_source" data-pjax-transient>
<meta name="google-site-verification" content="KT5gs8h0wvaagLKAVWq8bbeNwnZZK1r1XQysX3xurLU">
<meta name="google-site-verification" content="ZzhVyEFwb7w3e0-uOTltm8Jsck2F5StVihD0exw2fsA">
<meta name="google-analytics" content="UA-3769691-2">
<meta content="collector.githubapp.com" name="octolytics-host" /><meta content="github" name="octolytics-app-id" /><meta content="32359CA2:3F6A:789832:56BA6B37" name="octolytics-dimension-request_id" />
<meta content="/&lt;user-name&gt;/&lt;repo-name&gt;/blob/show" data-pjax-transient="true" name="analytics-location" />
<meta class="js-ga-set" name="dimension1" content="Logged Out">
<meta name="hostname" content="github.com">
<meta name="user-login" content="">
<meta name="expected-hostname" content="github.com">
<meta name="js-proxy-site-detection-payload" content="YmM2NmFhNmJmZjU3OTZhZjZiMWI2MzA3N2E0M2Y2Zjc1OTM5ODcxYjQ1OWEyYzFkMjg3Y2NkOTI1ODhiMzZmNXx7InJlbW90ZV9hZGRyZXNzIjoiNTAuNTMuMTU2LjE2MiIsInJlcXVlc3RfaWQiOiIzMjM1OUNBMjozRjZBOjc4OTgzMjo1NkJBNkIzNyJ9">
<link rel="mask-icon" href="https://assets-cdn.github.com/pinned-octocat.svg" color="#4078c0">
<link rel="icon" type="image/x-icon" href="https://assets-cdn.github.com/favicon.ico">
<meta content="9ca3979ba13c29867416f58457ff40e521a92049" name="form-nonce" />
<meta http-equiv="x-pjax-version" content="88e74eaed8727c649ee4773743f8723a">
<meta name="description" content="wp-cli.github.com - wp-cli.org website">
<meta name="go-import" content="github.com/wp-cli/wp-cli.github.com git https://github.com/wp-cli/wp-cli.github.com.git">
<meta content="1570774" name="octolytics-dimension-user_id" /><meta content="wp-cli" name="octolytics-dimension-user_login" /><meta content="6535856" name="octolytics-dimension-repository_id" /><meta content="wp-cli/wp-cli.github.com" name="octolytics-dimension-repository_nwo" /><meta content="true" name="octolytics-dimension-repository_public" /><meta content="false" name="octolytics-dimension-repository_is_fork" /><meta content="6535856" name="octolytics-dimension-repository_network_root_id" /><meta content="wp-cli/wp-cli.github.com" name="octolytics-dimension-repository_network_root_nwo" />
<link href="https://github.com/wp-cli/wp-cli.github.com/commits/master.atom" rel="alternate" title="Recent Commits to wp-cli.github.com:master" type="application/atom+xml">
<link rel="canonical" href="https://github.com/wp-cli/wp-cli.github.com/blob/master/behat-data/codeispoetry.png" data-pjax-transient>
</head>
<body class="logged_out env-production vis-public page-blob">
<a href="#start-of-content" tabindex="1" class="accessibility-aid js-skip-to-content">Skip to content</a>
<div class="header header-logged-out" role="banner">
<div class="container clearfix">
<a class="header-logo-wordmark" href="https://github.com/" data-ga-click="(Logged out) Header, go to homepage, icon:logo-wordmark">
<svg aria-hidden="true" class="octicon octicon-logo-github" height="28" role="img" version="1.1" viewBox="0 0 45 16" width="78"><path d="M8.64 5.19H4.88c-0.11 0-0.19 0.08-0.19 0.17v1.84c0 0.09 0.08 0.17 0.19 0.17h1.47v2.3s-0.33 0.11-1.25 0.11c-1.08 0-2.58-0.39-2.58-3.7s1.58-3.73 3.05-3.73c1.27 0 1.81 0.22 2.17 0.33 0.11 0.03 0.2-0.08 0.2-0.17l0.42-1.78c0-0.05-0.02-0.09-0.06-0.14-0.14-0.09-1.02-0.58-3.2-0.58C2.58 0 0 1.06 0 6.2s2.95 5.92 5.44 5.92c2.06 0 3.31-0.89 3.31-0.89 0.05-0.02 0.06-0.09 0.06-0.13V5.36c0-0.09-0.08-0.17-0.19-0.17h0.02zM27.7 0.44h-2.13c-0.09 0-0.17 0.08-0.17 0.17v4.09h-3.31V0.61c0-0.09-0.08-0.17-0.17-0.17h-2.13c-0.09 0-0.17 0.08-0.17 0.17v11.11c0 0.09 0.09 0.17 0.17 0.17h2.13c0.09 0 0.17-0.08 0.17-0.17V6.97h3.31l-0.02 4.75c0 0.09 0.08 0.17 0.17 0.17h2.13c0.09 0 0.17-0.08 0.17-0.17V0.61c0-0.09-0.08-0.17-0.17-0.17h0.02zM11.19 0.69c-0.77 0-1.38 0.61-1.38 1.38s0.61 1.38 1.38 1.38c0.75 0 1.36-0.61 1.36-1.38s-0.61-1.38-1.36-1.38z m1.22 3.55c0-0.09-0.08-0.17-0.17-0.17H10.11c-0.09 0-0.17 0.09-0.17 0.2 0 0 0 6.17 0 7.34 0 0.2 0.13 0.27 0.3 0.27 0 0 0.91 0 1.92 0 0.2 0 0.25-0.09 0.25-0.27 0-0.39 0-7.36 0-7.36v-0.02z m23.52-0.16h-2.09c-0.11 0-0.17 0.08-0.17 0.19v5.44s-0.55 0.39-1.3 0.39-0.97-0.34-0.97-1.09c0-0.73 0-4.75 0-4.75 0-0.09-0.08-0.17-0.17-0.17h-2.14c-0.09 0-0.17 0.08-0.17 0.17 0 0 0 2.91 0 5.11s1.23 2.75 2.92 2.75c1.39 0 2.52-0.77 2.52-0.77s0.05 0.39 0.08 0.45c0.02 0.05 0.09 0.09 0.16 0.09h1.34c0.11 0 0.17-0.08 0.17-0.17l0.02-7.47c0-0.09-0.08-0.17-0.19-0.17z m5.77-0.25c-1.2 0-2.02 0.53-2.02 0.53V0.59c0-0.09-0.08-0.17-0.17-0.17h-2.13c-0.09 0-0.17 0.08-0.17 0.17l-0.02 11.11c0 0.09 0.09 0.17 0.19 0.17h1.48c0.06 0 0.11-0.02 0.14-0.08 0.05-0.06 0.09-0.52 0.09-0.52s0.88 0.83 2.52 0.83c1.94 0 3.05-0.98 3.05-4.41s-1.77-3.88-2.97-3.88z m-0.83 6.27c-0.73-0.02-1.22-0.36-1.22-0.36V6.22s0.48-0.3 1.08-0.34c0.77-0.08 1.5 0.16 1.5 1.97 0 1.91-0.33 2.28-1.36 2.25z m-22.33-0.05c-0.09 0-0.33 0.05-0.58 0.05-0.78 0-1.05-0.36-1.05-0.83s0-3.13 0-3.13h1.59c0.09 0 0.16-0.08 0.16-0.19V4.25c0-0.09-0.08-0.17-0.16-0.17h-1.59V1.97c0-0.08-0.05-0.13-0.14-0.13H14.61c-0.09 0-0.14 0.05-0.14 0.13v2.17s-1.09 0.27-1.16 0.28c-0.08 0.02-0.13 0.09-0.13 0.17v1.36c0 0.11 0.08 0.19 0.17 0.19h1.11s0 1.44 0 3.28c0 2.44 1.7 2.69 2.86 2.69 0.53 0 1.17-0.17 1.27-0.22 0.06-0.02 0.09-0.09 0.09-0.16v-1.5c0-0.11-0.08-0.19-0.17-0.19h0.02z"></path></svg>
</a>
<div class="header-actions" role="navigation">
<a class="btn btn-primary" href="/join?source=header-repo" data-ga-click="(Logged out) Header, clicked Sign up, text:sign-up">Sign up</a>
<a class="btn" href="/login?return_to=%2Fwp-cli%2Fwp-cli.github.com%2Fblob%2Fmaster%2Fbehat-data%2Fcodeispoetry.png" data-ga-click="(Logged out) Header, clicked Sign in, text:sign-in">Sign in</a>
</div>
<div class="site-search repo-scope js-site-search" role="search">
<!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="/wp-cli/wp-cli.github.com/search" class="js-site-search-form" data-global-search-url="/search" data-repo-search-url="/wp-cli/wp-cli.github.com/search" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
<label class="js-chromeless-input-container form-control">
<div class="scope-badge">This repository</div>
<input type="text"
class="js-site-search-focus js-site-search-field is-clearable chromeless-input"
data-hotkey="s"
name="q"
placeholder="Search"
aria-label="Search this repository"
data-global-scope-placeholder="Search GitHub"
data-repo-scope-placeholder="Search"
tabindex="1"
autocapitalize="off">
</label>
</form>
</div>
<ul class="header-nav left" role="navigation">
<li class="header-nav-item">
<a class="header-nav-link" href="/explore" data-ga-click="(Logged out) Header, go to explore, text:explore">Explore</a>
</li>
<li class="header-nav-item">
<a class="header-nav-link" href="/features" data-ga-click="(Logged out) Header, go to features, text:features">Features</a>
</li>
<li class="header-nav-item">
<a class="header-nav-link" href="https://enterprise.github.com/" data-ga-click="(Logged out) Header, go to enterprise, text:enterprise">Enterprise</a>
</li>
<li class="header-nav-item">
<a class="header-nav-link" href="/pricing" data-ga-click="(Logged out) Header, go to pricing, text:pricing">Pricing</a>
</li>
</ul>
</div>
</div>
<div id="start-of-content" class="accessibility-aid"></div>
<div id="js-flash-container">
</div>
<div role="main" class="main-content">
<div itemscope itemtype="http://schema.org/WebPage">
<div id="js-repo-pjax-container" class="context-loader-container js-repo-nav-next" data-pjax-container>
<div class="pagehead repohead instapaper_ignore readability-menu experiment-repo-nav">
<div class="container repohead-details-container">
<ul class="pagehead-actions">
<li>
<a href="/login?return_to=%2Fwp-cli%2Fwp-cli.github.com"
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
aria-label="You must be signed in to watch a repository" rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-eye" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M8.06 2C3 2 0 8 0 8s3 6 8.06 6c4.94 0 7.94-6 7.94-6S13 2 8.06 2z m-0.06 10c-2.2 0-4-1.78-4-4 0-2.2 1.8-4 4-4 2.22 0 4 1.8 4 4 0 2.22-1.78 4-4 4z m2-4c0 1.11-0.89 2-2 2s-2-0.89-2-2 0.89-2 2-2 2 0.89 2 2z"></path></svg>
Watch
</a>
<a class="social-count" href="/wp-cli/wp-cli.github.com/watchers">
13
</a>
</li>
<li>
<a href="/login?return_to=%2Fwp-cli%2Fwp-cli.github.com"
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
aria-label="You must be signed in to star a repository" rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-star" height="16" role="img" version="1.1" viewBox="0 0 14 16" width="14"><path d="M14 6l-4.9-0.64L7 1 4.9 5.36 0 6l3.6 3.26L2.67 14l4.33-2.33 4.33 2.33L10.4 9.26 14 6z"></path></svg>
Star
</a>
<a class="social-count js-social-count" href="/wp-cli/wp-cli.github.com/stargazers">
6
</a>
</li>
<li>
<a href="/login?return_to=%2Fwp-cli%2Fwp-cli.github.com"
class="btn btn-sm btn-with-count tooltipped tooltipped-n"
aria-label="You must be signed in to fork a repository" rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-repo-forked" height="16" role="img" version="1.1" viewBox="0 0 10 16" width="10"><path d="M8 1c-1.11 0-2 0.89-2 2 0 0.73 0.41 1.38 1 1.72v1.28L5 8 3 6v-1.28c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72v1.78l3 3v1.78c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V9.5l3-3V4.72c0.59-0.34 1-0.98 1-1.72 0-1.11-0.89-2-2-2zM2 4.2c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3 10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z m3-10c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z"></path></svg>
Fork
</a>
<a href="/wp-cli/wp-cli.github.com/network" class="social-count">
27
</a>
</li>
</ul>
<h1 itemscope itemtype="http://data-vocabulary.org/Breadcrumb" class="entry-title public ">
<svg aria-hidden="true" class="octicon octicon-repo" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M4 9h-1v-1h1v1z m0-3h-1v1h1v-1z m0-2h-1v1h1v-1z m0-2h-1v1h1v-1z m8-1v12c0 0.55-0.45 1-1 1H6v2l-1.5-1.5-1.5 1.5V14H1c-0.55 0-1-0.45-1-1V1C0 0.45 0.45 0 1 0h10c0.55 0 1 0.45 1 1z m-1 10H1v2h2v-1h3v1h5V11z m0-10H2v9h9V1z"></path></svg>
<span class="author"><a href="/wp-cli" class="url fn" itemprop="url" rel="author"><span itemprop="title">wp-cli</span></a></span><!--
--><span class="path-divider">/</span><!--
--><strong><a href="/wp-cli/wp-cli.github.com" data-pjax="#js-repo-pjax-container">wp-cli.github.com</a></strong>
<span class="page-context-loader">
<img alt="" height="16" src="https://assets-cdn.github.com/images/spinners/octocat-spinner-32.gif" width="16" />
</span>
</h1>
</div>
<div class="container">
<nav class="reponav js-repo-nav js-sidenav-container-pjax js-octicon-loaders"
role="navigation"
data-pjax="#js-repo-pjax-container">
<a href="/wp-cli/wp-cli.github.com" aria-label="Code" aria-selected="true" class="js-selected-navigation-item selected reponav-item" data-hotkey="g c" data-selected-links="repo_source repo_downloads repo_commits repo_releases repo_tags repo_branches /wp-cli/wp-cli.github.com">
<svg aria-hidden="true" class="octicon octicon-code" height="16" role="img" version="1.1" viewBox="0 0 14 16" width="14"><path d="M9.5 3l-1.5 1.5 3.5 3.5L8 11.5l1.5 1.5 4.5-5L9.5 3zM4.5 3L0 8l4.5 5 1.5-1.5L2.5 8l3.5-3.5L4.5 3z"></path></svg>
Code
</a>
<a href="/wp-cli/wp-cli.github.com/pulls" class="js-selected-navigation-item reponav-item" data-hotkey="g p" data-selected-links="repo_pulls /wp-cli/wp-cli.github.com/pulls">
<svg aria-hidden="true" class="octicon octicon-git-pull-request" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M11 11.28c0-1.73 0-6.28 0-6.28-0.03-0.78-0.34-1.47-0.94-2.06s-1.28-0.91-2.06-0.94c0 0-1.02 0-1 0V0L4 3l3 3V4h1c0.27 0.02 0.48 0.11 0.69 0.31s0.3 0.42 0.31 0.69v6.28c-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72z m-1 2.92c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2zM4 3c0-1.11-0.89-2-2-2S0 1.89 0 3c0 0.73 0.41 1.38 1 1.72 0 1.55 0 5.56 0 6.56-0.59 0.34-1 0.98-1 1.72 0 1.11 0.89 2 2 2s2-0.89 2-2c0-0.73-0.41-1.38-1-1.72V4.72c0.59-0.34 1-0.98 1-1.72z m-0.8 10c0 0.66-0.55 1.2-1.2 1.2s-1.2-0.55-1.2-1.2 0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2z m-1.2-8.8c-0.66 0-1.2-0.55-1.2-1.2s0.55-1.2 1.2-1.2 1.2 0.55 1.2 1.2-0.55 1.2-1.2 1.2z"></path></svg>
Pull requests
<span class="counter">3</span>
</a>
<a href="/wp-cli/wp-cli.github.com/pulse" class="js-selected-navigation-item reponav-item" data-selected-links="pulse /wp-cli/wp-cli.github.com/pulse">
<svg aria-hidden="true" class="octicon octicon-pulse" height="16" role="img" version="1.1" viewBox="0 0 14 16" width="14"><path d="M11.5 8L8.8 5.4 6.6 8.5 5.5 1.6 2.38 8H0V10h3.6L4.5 8.2l0.9 5.4L9 8.5l1.6 1.5H14V8H11.5z"></path></svg>
Pulse
</a>
<a href="/wp-cli/wp-cli.github.com/graphs" class="js-selected-navigation-item reponav-item" data-selected-links="repo_graphs repo_contributors /wp-cli/wp-cli.github.com/graphs">
<svg aria-hidden="true" class="octicon octicon-graph" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M16 14v1H0V0h1v14h15z m-11-1H3V8h2v5z m4 0H7V3h2v10z m4 0H11V6h2v7z"></path></svg>
Graphs
</a>
</nav>
</div>
</div>
<div class="container new-discussion-timeline experiment-repo-nav">
<div class="repository-content">
<a href="/wp-cli/wp-cli.github.com/blob/663ab6d46c67329aac052ee829cfced92c98a597/behat-data/codeispoetry.png" class="hidden js-permalink-shortcut" data-hotkey="y">Permalink</a>
<!-- blob contrib key: blob_contributors:v21:03324a4d6ead46e4f13ad85cbdf84ead -->
<div class="file-navigation js-zeroclipboard-container">
<div class="select-menu js-menu-container js-select-menu left">
<button class="btn btn-sm select-menu-button js-menu-target css-truncate" data-hotkey="w"
title="master"
type="button" aria-label="Switch branches or tags" tabindex="0" aria-haspopup="true">
<i>Branch:</i>
<span class="js-select-button css-truncate-target">master</span>
</button>
<div class="select-menu-modal-holder js-menu-content js-navigation-container" data-pjax aria-hidden="true">
<div class="select-menu-modal">
<div class="select-menu-header">
<svg aria-label="Close" class="octicon octicon-x js-menu-close" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z"></path></svg>
<span class="select-menu-title">Switch branches/tags</span>
</div>
<div class="select-menu-filters">
<div class="select-menu-text-filter">
<input type="text" aria-label="Filter branches/tags" id="context-commitish-filter-field" class="js-filterable-field js-navigation-enable" placeholder="Filter branches/tags">
</div>
<div class="select-menu-tabs">
<ul>
<li class="select-menu-tab">
<a href="#" data-tab-filter="branches" data-filter-placeholder="Filter branches/tags" class="js-select-menu-tab" role="tab">Branches</a>
</li>
<li class="select-menu-tab">
<a href="#" data-tab-filter="tags" data-filter-placeholder="Find a tag…" class="js-select-menu-tab" role="tab">Tags</a>
</li>
</ul>
</div>
</div>
<div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="branches" role="menu">
<div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/wp-cli/wp-cli.github.com/blob/add-global-search/behat-data/codeispoetry.png"
data-name="add-global-search"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="add-global-search">
add-global-search
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/wp-cli/wp-cli.github.com/blob/deb-package/behat-data/codeispoetry.png"
data-name="deb-package"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="deb-package">
deb-package
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/wp-cli/wp-cli.github.com/blob/feature/rework-globals-section/behat-data/codeispoetry.png"
data-name="feature/rework-globals-section"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="feature/rework-globals-section">
feature/rework-globals-section
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/wp-cli/wp-cli.github.com/blob/fix-html-command-flag-minus/behat-data/codeispoetry.png"
data-name="fix-html-command-flag-minus"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="fix-html-command-flag-minus">
fix-html-command-flag-minus
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/wp-cli/wp-cli.github.com/blob/highlighting/behat-data/codeispoetry.png"
data-name="highlighting"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="highlighting">
highlighting
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open selected"
href="/wp-cli/wp-cli.github.com/blob/master/behat-data/codeispoetry.png"
data-name="master"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="master">
master
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/wp-cli/wp-cli.github.com/blob/phpdoc/behat-data/codeispoetry.png"
data-name="phpdoc"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="phpdoc">
phpdoc
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/wp-cli/wp-cli.github.com/blob/post-changes-hightlighting/behat-data/codeispoetry.png"
data-name="post-changes-hightlighting"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="post-changes-hightlighting">
post-changes-hightlighting
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/wp-cli/wp-cli.github.com/blob/post-contributing/behat-data/codeispoetry.png"
data-name="post-contributing"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="post-contributing">
post-contributing
</span>
</a>
<a class="select-menu-item js-navigation-item js-navigation-open "
href="/wp-cli/wp-cli.github.com/blob/redesign-2014/behat-data/codeispoetry.png"
data-name="redesign-2014"
data-skip-pjax="true"
rel="nofollow">
<svg aria-hidden="true" class="octicon octicon-check select-menu-item-icon" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M12 5L4 13 0 9l1.5-1.5 2.5 2.5 6.5-6.5 1.5 1.5z"></path></svg>
<span class="select-menu-item-text css-truncate-target" title="redesign-2014">
redesign-2014
</span>
</a>
</div>
<div class="select-menu-no-results">Nothing to show</div>
</div>
<div class="select-menu-list select-menu-tab-bucket js-select-menu-tab-bucket" data-tab-filter="tags">
<div data-filterable-for="context-commitish-filter-field" data-filterable-type="substring">
</div>
<div class="select-menu-no-results">Nothing to show</div>
</div>
</div>
</div>
</div>
<div class="btn-group right">
<a href="/wp-cli/wp-cli.github.com/find/master"
class="js-show-file-finder btn btn-sm"
data-pjax
data-hotkey="t">
Find file
</a>
<button aria-label="Copy file path to clipboard" class="js-zeroclipboard btn btn-sm zeroclipboard-button tooltipped tooltipped-s" data-copied-hint="Copied!" type="button">Copy path</button>
</div>
<div class="breadcrumb js-zeroclipboard-target">
<span class="repo-root js-repo-root"><span itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb"><a href="/wp-cli/wp-cli.github.com" class="" data-branch="master" data-pjax="true" itemscope="url"><span itemprop="title">wp-cli.github.com</span></a></span></span><span class="separator">/</span><span itemscope="" itemtype="http://data-vocabulary.org/Breadcrumb"><a href="/wp-cli/wp-cli.github.com/tree/master/behat-data" class="" data-branch="master" data-pjax="true" itemscope="url"><span itemprop="title">behat-data</span></a></span><span class="separator">/</span><strong class="final-path">codeispoetry.png</strong>
</div>
</div>
<div class="commit-tease">
<span class="right">
<a class="commit-tease-sha" href="/wp-cli/wp-cli.github.com/commit/9a03d83f0b0cfe167a8927cf199f4113cb5bd7f3" data-pjax>
9a03d83
</a>
<time datetime="2016-02-03T13:13:36Z" is="relative-time">Feb 3, 2016</time>
</span>
<div>
<img alt="@danielbachhuber" class="avatar" height="20" src="https://avatars3.githubusercontent.com/u/36432?v=3&amp;s=40" width="20" />
<a href="/danielbachhuber" class="user-mention" rel="contributor">danielbachhuber</a>
<a href="/wp-cli/wp-cli.github.com/commit/9a03d83f0b0cfe167a8927cf199f4113cb5bd7f3" class="message" data-pjax="true" title="Add IPTC data to the test image">Add IPTC data to the test image</a>
</div>
<div class="commit-tease-contributors">
<a class="muted-link contributors-toggle" href="#blob_contributors_box" rel="facebox">
<strong>1</strong>
contributor
</a>
</div>
<div id="blob_contributors_box" style="display:none">
<h2 class="facebox-header" data-facebox-id="facebox-header">Users who have contributed to this file</h2>
<ul class="facebox-user-list" data-facebox-id="facebox-description">
<li class="facebox-user-list-item">
<img alt="@danielbachhuber" height="24" src="https://avatars1.githubusercontent.com/u/36432?v=3&amp;s=48" width="24" />
<a href="/danielbachhuber">danielbachhuber</a>
</li>
</ul>
</div>
</div>
<div class="file">
<div class="file-header">
<div class="file-actions">
<div class="btn-group">
<a href="/wp-cli/wp-cli.github.com/raw/master/behat-data/codeispoetry.png" class="btn btn-sm " id="raw-url">Raw</a>
<a href="/wp-cli/wp-cli.github.com/commits/master/behat-data/codeispoetry.png" class="btn btn-sm " rel="nofollow">History</a>
</div>
<!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="/wp-cli/wp-cli.github.com/delete/master/behat-data/codeispoetry.png" class="inline-form" data-form-nonce="9ca3979ba13c29867416f58457ff40e521a92049" method="post"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /><input name="authenticity_token" type="hidden" value="S96qX2oUX+Gcc45YxV/LuTaZytjKoCSdzjnCuQKuAvtK9j0OCzZK7/lSmLCAbyF+r6NfG1UoWuu7rPzR9+ax+g==" /></div>
<button class="btn-octicon btn-octicon-danger tooltipped tooltipped-nw" type="submit"
aria-label="You must be signed in to make or propose changes" data-disable-with>
<svg aria-hidden="true" class="octicon octicon-trashcan" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M10 2H8c0-0.55-0.45-1-1-1H4c-0.55 0-1 0.45-1 1H1c-0.55 0-1 0.45-1 1v1c0 0.55 0.45 1 1 1v9c0 0.55 0.45 1 1 1h7c0.55 0 1-0.45 1-1V5c0.55 0 1-0.45 1-1v-1c0-0.55-0.45-1-1-1z m-1 12H2V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9z m1-10H1v-1h9v1z"></path></svg>
</button>
</form> </div>
<div class="file-info">
15.6 KB
</div>
</div>
<div class="blob-wrapper data type-text">
<div class="image">
<span class="border-wrap"><img src="/wp-cli/wp-cli.github.com/blob/master/behat-data/codeispoetry.png?raw=true" alt="codeispoetry.png"></span>
</div>
</div>
</div>
<a href="#jump-to-line" rel="facebox[.linejump]" data-hotkey="l" style="display:none">Jump to Line</a>
<div id="jump-to-line" style="display:none">
<!-- </textarea> --><!-- '"` --><form accept-charset="UTF-8" action="" class="js-jump-to-line-form" method="get"><div style="margin:0;padding:0;display:inline"><input name="utf8" type="hidden" value="&#x2713;" /></div>
<input class="linejump-input js-jump-to-line-field" type="text" placeholder="Jump to line&hellip;" aria-label="Jump to line" autofocus>
<button type="submit" class="btn">Go</button>
</form></div>
</div>
<div class="modal-backdrop"></div>
</div>
</div>
</div>
</div>
<div class="container">
<div class="site-footer" role="contentinfo">
<ul class="site-footer-links right">
<li><a href="https://status.github.com/" data-ga-click="Footer, go to status, text:status">Status</a></li>
<li><a href="https://developer.github.com" data-ga-click="Footer, go to api, text:api">API</a></li>
<li><a href="https://training.github.com" data-ga-click="Footer, go to training, text:training">Training</a></li>
<li><a href="https://shop.github.com" data-ga-click="Footer, go to shop, text:shop">Shop</a></li>
<li><a href="https://github.com/blog" data-ga-click="Footer, go to blog, text:blog">Blog</a></li>
<li><a href="https://github.com/about" data-ga-click="Footer, go to about, text:about">About</a></li>
<li><a href="https://github.com/pricing" data-ga-click="Footer, go to pricing, text:pricing">Pricing</a></li>
</ul>
<a href="https://github.com" aria-label="Homepage">
<svg aria-hidden="true" class="octicon octicon-mark-github" height="24" role="img" title="GitHub " version="1.1" viewBox="0 0 16 16" width="24"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59 0.4 0.07 0.55-0.17 0.55-0.38 0-0.19-0.01-0.82-0.01-1.49-2.01 0.37-2.53-0.49-2.69-0.94-0.09-0.23-0.48-0.94-0.82-1.13-0.28-0.15-0.68-0.52-0.01-0.53 0.63-0.01 1.08 0.58 1.23 0.82 0.72 1.21 1.87 0.87 2.33 0.66 0.07-0.52 0.28-0.87 0.51-1.07-1.78-0.2-3.64-0.89-3.64-3.95 0-0.87 0.31-1.59 0.82-2.15-0.08-0.2-0.36-1.02 0.08-2.12 0 0 0.67-0.21 2.2 0.82 0.64-0.18 1.32-0.27 2-0.27 0.68 0 1.36 0.09 2 0.27 1.53-1.04 2.2-0.82 2.2-0.82 0.44 1.1 0.16 1.92 0.08 2.12 0.51 0.56 0.82 1.27 0.82 2.15 0 3.07-1.87 3.75-3.65 3.95 0.29 0.25 0.54 0.73 0.54 1.48 0 1.07-0.01 1.93-0.01 2.2 0 0.21 0.15 0.46 0.55 0.38C13.71 14.53 16 11.53 16 8 16 3.58 12.42 0 8 0z"></path></svg>
</a>
<ul class="site-footer-links">
<li>&copy; 2016 <span title="0.03841s from github-fe118-cp1-prd.iad.github.net">GitHub</span>, Inc.</li>
<li><a href="https://github.com/site/terms" data-ga-click="Footer, go to terms, text:terms">Terms</a></li>
<li><a href="https://github.com/site/privacy" data-ga-click="Footer, go to privacy, text:privacy">Privacy</a></li>
<li><a href="https://github.com/security" data-ga-click="Footer, go to security, text:security">Security</a></li>
<li><a href="https://github.com/contact" data-ga-click="Footer, go to contact, text:contact">Contact</a></li>
<li><a href="https://help.github.com" data-ga-click="Footer, go to help, text:help">Help</a></li>
</ul>
</div>
</div>
<div id="ajax-error-message" class="flash flash-error">
<svg aria-hidden="true" class="octicon octicon-alert" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M15.72 12.5l-6.85-11.98C8.69 0.21 8.36 0.02 8 0.02s-0.69 0.19-0.87 0.5l-6.85 11.98c-0.18 0.31-0.18 0.69 0 1C0.47 13.81 0.8 14 1.15 14h13.7c0.36 0 0.69-0.19 0.86-0.5S15.89 12.81 15.72 12.5zM9 12H7V10h2V12zM9 9H7V5h2V9z"></path></svg>
<button type="button" class="flash-close js-flash-close js-ajax-error-dismiss" aria-label="Dismiss error">
<svg aria-hidden="true" class="octicon octicon-x" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z"></path></svg>
</button>
Something went wrong with that request. Please try again.
</div>
<script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/compat-ef12e71982f00805539829712436829b02370b2e561de18ece6796130cbb9bbe.js"></script>
<script crossorigin="anonymous" src="https://assets-cdn.github.com/assets/frameworks-ee521b8e9facac68ff27e93fc3ae0f8ed811d7bf9e434e84f4b9ea227780b084.js"></script>
<script async="async" crossorigin="anonymous" src="https://assets-cdn.github.com/assets/github-863d0e4c2905010278cfd87e9c7c738e812530db73c36c274a185354977a2e41.js"></script>
<div class="js-stale-session-flash stale-session-flash flash flash-warn flash-banner hidden">
<svg aria-hidden="true" class="octicon octicon-alert" height="16" role="img" version="1.1" viewBox="0 0 16 16" width="16"><path d="M15.72 12.5l-6.85-11.98C8.69 0.21 8.36 0.02 8 0.02s-0.69 0.19-0.87 0.5l-6.85 11.98c-0.18 0.31-0.18 0.69 0 1C0.47 13.81 0.8 14 1.15 14h13.7c0.36 0 0.69-0.19 0.86-0.5S15.89 12.81 15.72 12.5zM9 12H7V10h2V12zM9 9H7V5h2V9z"></path></svg>
<span class="signed-in-tab-flash">You signed in with another tab or window. <a href="">Reload</a> to refresh your session.</span>
<span class="signed-out-tab-flash">You signed out in another tab or window. <a href="">Reload</a> to refresh your session.</span>
</div>
<div class="facebox" id="facebox" style="display:none;">
<div class="facebox-popup">
<div class="facebox-content" role="dialog" aria-labelledby="facebox-header" aria-describedby="facebox-description">
</div>
<button type="button" class="facebox-close js-facebox-close" aria-label="Close modal">
<svg aria-hidden="true" class="octicon octicon-x" height="16" role="img" version="1.1" viewBox="0 0 12 16" width="12"><path d="M7.48 8l3.75 3.75-1.48 1.48-3.75-3.75-3.75 3.75-1.48-1.48 3.75-3.75L0.77 4.25l1.48-1.48 3.75 3.75 3.75-3.75 1.48 1.48-3.75 3.75z"></path></svg>
</button>
</div>
</div>
</body>
</html>

View File

@ -40,6 +40,8 @@ define( 'DISABLE_WP_CRON', true );
define( 'WP_MEMORY_LIMIT', -1 );
define( 'WP_MAX_MEMORY_LIMIT', -1 );
define( 'REST_TESTS_IMPOSSIBLY_HIGH_NUMBER', 99999999 );
$PHP_SELF = $GLOBALS['PHP_SELF'] = $_SERVER['PHP_SELF'] = '/index.php';
// Should we run in multisite mode?
@ -88,6 +90,8 @@ _delete_all_posts();
require dirname( __FILE__ ) . '/testcase.php';
require dirname( __FILE__ ) . '/testcase-rest-api.php';
require dirname( __FILE__ ) . '/testcase-rest-controller.php';
require dirname( __FILE__ ) . '/testcase-rest-post-type-controller.php';
require dirname( __FILE__ ) . '/testcase-xmlrpc.php';
require dirname( __FILE__ ) . '/testcase-ajax.php';
require dirname( __FILE__ ) . '/testcase-canonical.php';

View File

@ -0,0 +1,48 @@
<?php
abstract class WP_Test_REST_Controller_Testcase extends WP_Test_REST_TestCase {
protected $server;
public function setUp() {
parent::setUp();
add_filter( 'rest_url', array( $this, 'filter_rest_url_for_leading_slash' ), 10, 2 );
/** @var WP_REST_Server $wp_rest_server */
global $wp_rest_server;
$this->server = $wp_rest_server = new Spy_REST_Server;
do_action( 'rest_api_init' );
}
public function tearDown() {
parent::tearDown();
remove_filter( 'rest_url', array( $this, 'test_rest_url_for_leading_slash' ), 10, 2 );
/** @var WP_REST_Server $wp_rest_server */
global $wp_rest_server;
$wp_rest_server = null;
}
abstract public function test_register_routes();
abstract public function test_context_param();
abstract public function test_get_items();
abstract public function test_get_item();
abstract public function test_create_item();
abstract public function test_update_item();
abstract public function test_delete_item();
abstract public function test_prepare_item();
abstract public function test_get_item_schema();
public function filter_rest_url_for_leading_slash( $url, $path ) {
// Make sure path for rest_url has a leading slash for proper resolution.
$this->assertTrue( 0 === strpos( $path, '/' ) );
return $url;
}
}

View File

@ -0,0 +1,312 @@
<?php
abstract class WP_Test_REST_Post_Type_Controller_Testcase extends WP_Test_REST_Controller_Testcase {
protected function check_post_data( $post, $data, $context, $links ) {
$post_type_obj = get_post_type_object( $post->post_type );
// Standard fields
$this->assertEquals( $post->ID, $data['id'] );
$this->assertEquals( $post->post_name, $data['slug'] );
$this->assertEquals( get_permalink( $post->ID ), $data['link'] );
if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) {
$this->assertNull( $data['date_gmt'] );
}
$this->assertEquals( mysql_to_rfc3339( $post->post_date ), $data['date'] );
if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) {
$this->assertNull( $data['modified_gmt'] );
}
$this->assertEquals( mysql_to_rfc3339( $post->post_modified ), $data['modified'] );
// author
if ( post_type_supports( $post->post_type, 'author' ) ) {
$this->assertEquals( $post->post_author, $data['author'] );
} else {
$this->assertEmpty( $data['author'] );
}
// post_parent
if ( $post_type_obj->hierarchical ) {
$this->assertArrayHasKey( 'parent', $data );
if ( $post->post_parent ) {
if ( is_int( $data['parent'] ) ) {
$this->assertEquals( $post->post_parent, $data['parent'] );
} else {
$this->assertEquals( $post->post_parent, $data['parent']['id'] );
$this->check_get_post_response( $data['parent'], get_post( $data['parent']['id'] ), 'view-parent' );
}
} else {
$this->assertEmpty( $data['parent'] );
}
} else {
$this->assertFalse( isset( $data['parent'] ) );
}
// page attributes
if ( $post_type_obj->hierarchical && post_type_supports( $post->post_type, 'page-attributes' ) ) {
$this->assertEquals( $post->menu_order, $data['menu_order'] );
} else {
$this->assertFalse( isset( $data['menu_order'] ) );
}
// Comments
if ( post_type_supports( $post->post_type, 'comments' ) ) {
$this->assertEquals( $post->comment_status, $data['comment_status'] );
$this->assertEquals( $post->ping_status, $data['ping_status'] );
} else {
$this->assertFalse( isset( $data['comment_status'] ) );
$this->assertFalse( isset( $data['ping_status'] ) );
}
if ( 'post' === $post->post_type ) {
$this->assertEquals( is_sticky( $post->ID ), $data['sticky'] );
}
if ( 'post' === $post->post_type && 'edit' === $context ) {
$this->assertEquals( $post->post_password, $data['password'] );
}
if ( 'page' === $post->post_type ) {
$this->assertEquals( get_page_template_slug( $post->ID ), $data['template'] );
}
if ( post_type_supports( $post->post_type, 'thumbnail' ) ) {
$this->assertEquals( (int) get_post_thumbnail_id( $post->ID ), $data['featured_media'] );
} else {
$this->assertFalse( isset( $data['featured_media'] ) );
}
// Check post format.
if ( post_type_supports( $post->post_type, 'post-formats' ) ) {
$post_format = get_post_format( $post->ID );
if ( empty( $post_format ) ) {
$this->assertEquals( 'standard', $data['format'] );
} else {
$this->assertEquals( get_post_format( $post->ID ), $data['format'] );
}
} else {
$this->assertFalse( isset( $data['format'] ) );
}
// Check filtered values.
if ( post_type_supports( $post->post_type, 'title' ) ) {
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
$this->assertEquals( get_the_title( $post->ID ), $data['title']['rendered'] );
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
if ( 'edit' === $context ) {
$this->assertEquals( $post->post_title, $data['title']['raw'] );
} else {
$this->assertFalse( isset( $data['title']['raw'] ) );
}
} else {
$this->assertFalse( isset( $data['title'] ) );
}
if ( post_type_supports( $post->post_type, 'editor' ) ) {
// TODO: apply content filter for more accurate testing.
if ( ! $post->post_password ) {
$this->assertEquals( wpautop( $post->post_content ), $data['content']['rendered'] );
}
if ( 'edit' === $context ) {
$this->assertEquals( $post->post_content, $data['content']['raw'] );
} else {
$this->assertFalse( isset( $data['content']['raw'] ) );
}
} else {
$this->assertFalse( isset( $data['content'] ) );
}
if ( post_type_supports( $post->post_type, 'excerpt' ) ) {
if ( empty( $post->post_password ) ) {
// TODO: apply excerpt filter for more accurate testing.
$this->assertEquals( wpautop( $post->post_excerpt ), $data['excerpt']['rendered'] );
} else {
// TODO: better testing for excerpts for password protected posts.
}
if ( 'edit' === $context ) {
$this->assertEquals( $post->post_excerpt, $data['excerpt']['raw'] );
} else {
$this->assertFalse( isset( $data['excerpt']['raw'] ) );
}
} else {
$this->assertFalse( isset( $data['excerpt'] ) );
}
$this->assertEquals( $post->guid, $data['guid']['rendered'] );
if ( 'edit' === $context ) {
$this->assertEquals( $post->guid, $data['guid']['raw'] );
$this->assertEquals( $post->post_status, $data['status'] );
if ( '0000-00-00 00:00:00' === $post->post_date_gmt ) {
$this->assertNull( $data['date_gmt'] );
} else {
$this->assertEquals( mysql_to_rfc3339( $post->post_date_gmt ), $data['date_gmt'] );
}
if ( '0000-00-00 00:00:00' === $post->post_modified_gmt ) {
$this->assertNull( $data['modified_gmt'] );
} else {
$this->assertEquals( mysql_to_rfc3339( $post->post_modified_gmt ), $data['modified_gmt'] );
}
}
$taxonomies = wp_list_filter( get_object_taxonomies( $post->post_type, 'objects' ), array( 'show_in_rest' => true ) );
foreach ( $taxonomies as $taxonomy ) {
$this->assertTrue( isset( $data[ $taxonomy->rest_base ] ) );
$terms = wp_get_object_terms( $post->ID, $taxonomy->name, array( 'fields' => 'ids' ) );
sort( $terms );
sort( $data[ $taxonomy->rest_base ] );
$this->assertEquals( $terms, $data[ $taxonomy->rest_base ] );
}
// test links
if ( $links ) {
$links = test_rest_expand_compact_links( $links );
$post_type = get_post_type_object( $data['type'] );
$this->assertEquals( $links['self'][0]['href'], rest_url( 'wp/v2/' . $post_type->rest_base . '/' . $data['id'] ) );
$this->assertEquals( $links['collection'][0]['href'], rest_url( 'wp/v2/' . $post_type->rest_base ) );
$this->assertEquals( $links['about'][0]['href'], rest_url( 'wp/v2/types/' . $data['type'] ) );
if ( post_type_supports( $post->post_type, 'author' ) && $data['author'] ) {
$this->assertEquals( $links['author'][0]['href'], rest_url( 'wp/v2/users/' . $data['author'] ) );
}
if ( post_type_supports( $post->post_type, 'comments' ) ) {
$this->assertEquals( $links['replies'][0]['href'], add_query_arg( 'post', $data['id'], rest_url( 'wp/v2/comments' ) ) );
}
if ( post_type_supports( $post->post_type, 'revisions' ) ) {
$this->assertEquals( $links['version-history'][0]['href'], rest_url( 'wp/v2/' . $post_type->rest_base . '/' . $data['id'] . '/revisions' ) );
}
if ( $post_type->hierarchical && ! empty( $data['parent'] ) ) {
$this->assertEquals( $links['up'][0]['href'], rest_url( 'wp/v2/' . $post_type->rest_base . '/' . $data['parent'] ) );
}
if ( ! in_array( $data['type'], array( 'attachment', 'nav_menu_item', 'revision' ), true ) ) {
$this->assertEquals( $links['https://api.w.org/attachment'][0]['href'], add_query_arg( 'parent', $data['id'], rest_url( 'wp/v2/media' ) ) );
}
if ( ! empty( $data['featured_media'] ) ) {
$this->assertEquals( $links['https://api.w.org/featuredmedia'][0]['href'], rest_url( 'wp/v2/media/' . $data['featured_media'] ) );
}
$num = 0;
foreach ( $taxonomies as $key => $taxonomy ) {
$this->assertEquals( $taxonomy->name, $links['https://api.w.org/term'][ $num ]['attributes']['taxonomy'] );
$this->assertEquals( add_query_arg( 'post', $data['id'], rest_url( 'wp/v2/' . $taxonomy->rest_base ) ), $links['https://api.w.org/term'][ $num ]['href'] );
$num++;
}
}
}
protected function check_get_posts_response( $response, $context = 'view' ) {
$this->assertNotInstanceOf( 'WP_Error', $response );
$response = rest_ensure_response( $response );
$this->assertEquals( 200, $response->get_status() );
$headers = $response->get_headers();
$this->assertArrayHasKey( 'X-WP-Total', $headers );
$this->assertArrayHasKey( 'X-WP-TotalPages', $headers );
$all_data = $response->get_data();
foreach ( $all_data as $data ) {
$post = get_post( $data['id'] );
// as the links for the post are "response_links" format in the data array we have to pull them
// out and parse them.
$links = $data['_links'];
foreach ( $links as &$links_array ) {
foreach ( $links_array as &$link ) {
$attributes = array_diff_key( $link, array( 'href' => 1, 'name' => 1 ) );
$link = array_diff_key( $link, $attributes );
$link['attributes'] = $attributes;
}
}
$this->check_post_data( $post, $data, $context, $links );
}
}
protected function check_get_post_response( $response, $context = 'view' ) {
$this->assertNotInstanceOf( 'WP_Error', $response );
$response = rest_ensure_response( $response );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$post = get_post( $data['id'] );
$this->check_post_data( $post, $data, $context, $response->get_links() );
}
protected function check_create_post_response( $response ) {
$this->assertNotInstanceOf( 'WP_Error', $response );
$response = rest_ensure_response( $response );
$this->assertEquals( 201, $response->get_status() );
$headers = $response->get_headers();
$this->assertArrayHasKey( 'Location', $headers );
$data = $response->get_data();
$post = get_post( $data['id'] );
$this->check_post_data( $post, $data, 'edit', $response->get_links() );
}
protected function check_update_post_response( $response ) {
$this->assertNotInstanceOf( 'WP_Error', $response );
$response = rest_ensure_response( $response );
$this->assertEquals( 200, $response->get_status() );
$headers = $response->get_headers();
$this->assertArrayNotHasKey( 'Location', $headers );
$data = $response->get_data();
$post = get_post( $data['id'] );
$this->check_post_data( $post, $data, 'edit', $response->get_links() );
}
protected function set_post_data( $args = array() ) {
$defaults = array(
'title' => rand_str(),
'content' => rand_str(),
'excerpt' => rand_str(),
'name' => 'test',
'status' => 'publish',
'author' => get_current_user_id(),
'type' => 'post',
);
return wp_parse_args( $args, $defaults );
}
protected function set_raw_post_data( $args = array() ) {
return wp_parse_args( $args, $this->set_post_data( array(
'title' => array(
'raw' => rand_str(),
),
'content' => array(
'raw' => rand_str(),
),
'excerpt' => array(
'raw' => rand_str(),
),
) ) );
}
/**
* Overwrite the default protected title format.
*
* By default WordPress will show password protected posts with a title of
* "Protected: %s", as the REST API communicates the protected status of a post
* in a machine readable format, we remove the "Protected: " prefix.
*
* @return string
*/
public function protected_title_format() {
return '%s';
}
}

View File

@ -453,3 +453,22 @@ function benchmark_pcre_backtracking( $pattern, $subject, $strategy ) {
return $i;
}
function test_rest_expand_compact_links( $links ) {
if ( empty( $links['curies'] ) ) {
return $links;
}
foreach ( $links as $rel => $links_array ) {
if ( ! strpos( $rel, ':' ) ) {
continue;
}
$name = explode( ':', $rel );
$curie = wp_list_filter( $links['curies'], array( 'name' => $name[0] ) );
$full_uri = str_replace( '{rel}', $name[1], $curie[0]['href'] );
$links[ $full_uri ] = $links_array;
unset( $links[ $rel ] );
}
return $links;
}

View File

@ -26,6 +26,7 @@ class Tests_REST_API extends WP_UnitTestCase {
$this->assertTrue( class_exists( 'WP_REST_Server' ) );
$this->assertTrue( class_exists( 'WP_REST_Request' ) );
$this->assertTrue( class_exists( 'WP_REST_Response' ) );
$this->assertTrue( class_exists( 'WP_REST_Posts_Controller' ) );
}
/**
@ -36,6 +37,35 @@ class Tests_REST_API extends WP_UnitTestCase {
$this->assertEquals( 10, has_action( 'init', 'rest_api_init' ) );
}
public function test_add_extra_api_taxonomy_arguments() {
$taxonomy = get_taxonomy( 'category' );
$this->assertTrue( $taxonomy->show_in_rest );
$this->assertEquals( 'categories', $taxonomy->rest_base );
$this->assertEquals( 'WP_REST_Terms_Controller', $taxonomy->rest_controller_class );
$taxonomy = get_taxonomy( 'post_tag' );
$this->assertTrue( $taxonomy->show_in_rest );
$this->assertEquals( 'tags', $taxonomy->rest_base );
$this->assertEquals( 'WP_REST_Terms_Controller', $taxonomy->rest_controller_class );
}
public function test_add_extra_api_post_type_arguments() {
$post_type = get_post_type_object( 'post' );
$this->assertTrue( $post_type->show_in_rest );
$this->assertEquals( 'posts', $post_type->rest_base );
$this->assertEquals( 'WP_REST_Posts_Controller', $post_type->rest_controller_class );
$post_type = get_post_type_object( 'page' );
$this->assertTrue( $post_type->show_in_rest );
$this->assertEquals( 'pages', $post_type->rest_base );
$this->assertEquals( 'WP_REST_Posts_Controller', $post_type->rest_controller_class );
$post_type = get_post_type_object( 'attachment' );
$this->assertTrue( $post_type->show_in_rest );
$this->assertEquals( 'media', $post_type->rest_base );
$this->assertEquals( 'WP_REST_Attachments_Controller', $post_type->rest_controller_class );
}
/**
* Check that a single route is canonicalized.
*

View File

@ -0,0 +1,862 @@
<?php
/**
* Unit tests covering WP_REST_Attachments_Controller functionality
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Attachments_Controller extends WP_Test_REST_Post_Type_Controller_Testcase {
public function setUp() {
parent::setUp();
$this->editor_id = $this->factory->user->create( array(
'role' => 'editor',
) );
$this->author_id = $this->factory->user->create( array(
'role' => 'author',
) );
$this->contributor_id = $this->factory->user->create( array(
'role' => 'contributor',
) );
// Add an uploader role to test upload capabilities.
add_role( 'uploader', 'File upload role' );
$role = get_role( 'uploader' );
$role->add_cap( 'upload_files' );
$role->add_cap( 'read' );
$role->add_cap( 'level_0' );
$this->uploader_id = $this->factory->user->create( array(
'role' => 'uploader',
) );
$orig_file = DIR_TESTDATA . '/images/canola.jpg';
$this->test_file = '/tmp/canola.jpg';
copy( $orig_file, $this->test_file );
$orig_file2 = DIR_TESTDATA . '/images/codeispoetry.png';
$this->test_file2 = '/tmp/codeispoetry.png';
copy( $orig_file2, $this->test_file2 );
}
public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( '/wp/v2/media', $routes );
$this->assertCount( 2, $routes['/wp/v2/media'] );
$this->assertArrayHasKey( '/wp/v2/media/(?P<id>[\d]+)', $routes );
$this->assertCount( 3, $routes['/wp/v2/media/(?P<id>[\d]+)'] );
}
public static function disposition_provider() {
return array(
// Types
array( 'attachment; filename="foo.jpg"', 'foo.jpg' ),
array( 'inline; filename="foo.jpg"', 'foo.jpg' ),
array( 'form-data; filename="foo.jpg"', 'foo.jpg' ),
// Formatting
array( 'attachment; filename="foo.jpg"', 'foo.jpg' ),
array( 'attachment; filename=foo.jpg', 'foo.jpg' ),
array( 'attachment;filename="foo.jpg"', 'foo.jpg' ),
array( 'attachment;filename=foo.jpg', 'foo.jpg' ),
array( 'attachment; filename = "foo.jpg"', 'foo.jpg' ),
array( 'attachment; filename = foo.jpg', 'foo.jpg' ),
array( "attachment;\tfilename\t=\t\"foo.jpg\"", 'foo.jpg' ),
array( "attachment;\tfilename\t=\tfoo.jpg", 'foo.jpg' ),
array( 'attachment; filename = my foo picture.jpg', 'my foo picture.jpg' ),
// Extensions
array( 'form-data; name="myfile"; filename="foo.jpg"', 'foo.jpg' ),
array( 'form-data; name="myfile"; filename="foo.jpg"; something="else"', 'foo.jpg' ),
array( 'form-data; name=myfile; filename=foo.jpg; something=else', 'foo.jpg' ),
array( 'form-data; name=myfile; filename=my foo.jpg; something=else', 'my foo.jpg' ),
// Invalid
array( 'filename="foo.jpg"', null ),
array( 'filename-foo.jpg', null ),
array( 'foo.jpg', null ),
array( 'unknown; notfilename="foo.jpg"', null ),
);
}
/**
* @dataProvider disposition_provider
*/
public function test_parse_disposition( $header, $expected ) {
$header_list = array( $header );
$parsed = WP_REST_Attachments_Controller::get_filename_from_disposition( $header_list );
$this->assertEquals( $expected, $parsed );
}
public function test_context_param() {
// Collection
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
// Single
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media/' . $attachment_id );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
}
public function test_registered_query_params() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$keys = array_keys( $data['endpoints'][0]['args'] );
sort( $keys );
$this->assertEquals( array(
'after',
'author',
'author_exclude',
'before',
'context',
'exclude',
'filter',
'include',
'media_type',
'mime_type',
'offset',
'order',
'orderby',
'page',
'parent',
'parent_exclude',
'per_page',
'search',
'slug',
'status',
), $keys );
$media_types = array(
'application',
'video',
'image',
'audio',
);
if ( ! is_multisite() ) {
$media_types[] = 'text';
}
$this->assertEqualSets( $media_types, $data['endpoints'][0]['args']['media_type']['enum'] );
}
public function test_get_items() {
wp_set_current_user( 0 );
$id1 = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$draft_post = $this->factory->post->create( array( 'post_status' => 'draft' ) );
$id2 = $this->factory->attachment->create_object( $this->test_file, $draft_post, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$published_post = $this->factory->post->create( array( 'post_status' => 'publish' ) );
$id3 = $this->factory->attachment->create_object( $this->test_file, $published_post, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertCount( 2, $data );
$ids = wp_list_pluck( $data, 'id' );
$this->assertTrue( in_array( $id1, $ids, true ) );
$this->assertFalse( in_array( $id2, $ids, true ) );
$this->assertTrue( in_array( $id3, $ids, true ) );
$this->check_get_posts_response( $response );
}
public function test_get_items_logged_in_editor() {
wp_set_current_user( $this->editor_id );
$id1 = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$draft_post = $this->factory->post->create( array( 'post_status' => 'draft' ) );
$id2 = $this->factory->attachment->create_object( $this->test_file, $draft_post, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$published_post = $this->factory->post->create( array( 'post_status' => 'publish' ) );
$id3 = $this->factory->attachment->create_object( $this->test_file, $published_post, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertCount( 3, $data );
$ids = wp_list_pluck( $data, 'id' );
$this->assertTrue( in_array( $id1, $ids, true ) );
$this->assertTrue( in_array( $id2, $ids, true ) );
$this->assertTrue( in_array( $id3, $ids, true ) );
}
public function test_get_items_media_type() {
$id1 = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( $id1, $data[0]['id'] );
// media_type=video
$request->set_param( 'media_type', 'video' );
$response = $this->server->dispatch( $request );
$this->assertCount( 0, $response->get_data() );
// media_type=image
$request->set_param( 'media_type', 'image' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( $id1, $data[0]['id'] );
}
public function test_get_items_mime_type() {
$id1 = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( $id1, $data[0]['id'] );
// mime_type=image/png
$request->set_param( 'mime_type', 'image/png' );
$response = $this->server->dispatch( $request );
$this->assertCount( 0, $response->get_data() );
// mime_type=image/jpeg
$request->set_param( 'mime_type', 'image/jpeg' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( $id1, $data[0]['id'] );
}
public function test_get_items_parent() {
$post_id = $this->factory->post->create( array( 'post_title' => 'Test Post' ) );
$attachment_id = $this->factory->attachment->create_object( $this->test_file, $post_id, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$attachment_id2 = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
// all attachments
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 2, count( $response->get_data() ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
// attachments without a parent
$request->set_param( 'parent', 0 );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( $attachment_id2, $data[0]['id'] );
// attachments with parent=post_id
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$request->set_param( 'parent', $post_id );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( $attachment_id, $data[0]['id'] );
// attachments with invalid parent
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$request->set_param( 'parent', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 0, count( $data ) );
}
public function test_get_items_invalid_status_param_is_discarded() {
wp_set_current_user( $this->editor_id );
$this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$request->set_param( 'status', 'publish' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertCount( 1, $data );
$this->assertEquals( 'inherit', $data[0]['status'] );
}
public function test_get_items_private_status() {
// Logged out users can't make the request
wp_set_current_user( 0 );
$attachment_id1 = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
'post_status' => 'private',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$request->set_param( 'status', 'private' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
// Properly authorized users can make the request
wp_set_current_user( $this->editor_id );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( $attachment_id1, $data[0]['id'] );
}
public function test_get_items_invalid_date() {
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$request->set_param( 'after', rand_str() );
$request->set_param( 'before', rand_str() );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
}
public function test_get_items_valid_date() {
$id1 = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_date' => '2016-01-15T00:00:00Z',
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$id2 = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_date' => '2016-01-16T00:00:00Z',
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$id3 = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_date' => '2016-01-17T00:00:00Z',
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media' );
$request->set_param( 'after', '2016-01-15T00:00:00Z' );
$request->set_param( 'before', '2016-01-17T00:00:00Z' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertCount( 1, $data );
$this->assertEquals( $id2, $data[0]['id'] );
}
public function test_get_item() {
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
update_post_meta( $attachment_id, '_wp_attachment_image_alt', 'Sample alt text' );
$request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id );
$response = $this->server->dispatch( $request );
$this->check_get_post_response( $response );
$data = $response->get_data();
$this->assertEquals( 'image/jpeg', $data['mime_type'] );
}
public function test_get_item_sizes() {
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
), $this->test_file );
add_image_size( 'rest-api-test', 119, 119, true );
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $this->test_file ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$image_src = wp_get_attachment_image_src( $attachment_id, 'rest-api-test' );
$original_image_src = wp_get_attachment_image_src( $attachment_id, 'full' );
remove_image_size( 'rest-api-test' );
$this->assertEquals( $image_src[0], $data['media_details']['sizes']['rest-api-test']['source_url'] );
$this->assertEquals( 'image/jpeg', $data['media_details']['sizes']['rest-api-test']['mime_type'] );
$this->assertEquals( $original_image_src[0], $data['media_details']['sizes']['full']['source_url'] );
$this->assertEquals( 'image/jpeg', $data['media_details']['sizes']['full']['mime_type'] );
}
public function test_get_item_sizes_with_no_url() {
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
), $this->test_file );
add_image_size( 'rest-api-test', 119, 119, true );
wp_update_attachment_metadata( $attachment_id, wp_generate_attachment_metadata( $attachment_id, $this->test_file ) );
add_filter( 'wp_get_attachment_image_src', '__return_false' );
$request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
remove_filter( 'wp_get_attachment_image_src', '__return_false' );
remove_image_size( 'rest-api-test' );
$this->assertFalse( isset( $data['media_details']['sizes']['rest-api-test']['source_url'] ) );
}
public function test_get_item_private_post() {
wp_set_current_user( 0 );
$draft_post = $this->factory->post->create( array( 'post_status' => 'draft' ) );
$id1 = $this->factory->attachment->create_object( $this->test_file, $draft_post, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $id1 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 403, $response->get_status() );
}
public function test_create_item() {
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_header( 'Content-Type', 'image/jpeg' );
$request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
$request->set_body( file_get_contents( $this->test_file ) );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 201, $response->get_status() );
$this->assertEquals( 'image', $data['media_type'] );
$this->assertEquals( 'A field of amazing canola', $data['title']['rendered'] );
$this->assertEquals( 'The description for the image', $data['caption'] );
}
public function test_create_item_default_filename_title() {
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_file_params( array(
'file' => array(
'file' => file_get_contents( $this->test_file2 ),
'name' => 'codeispoetry.jpg',
'size' => filesize( $this->test_file2 ),
'tmp_name' => $this->test_file2,
),
) );
$request->set_header( 'Content-MD5', md5_file( $this->test_file2 ) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 201, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'codeispoetry', $data['title']['raw'] );
}
public function test_create_item_with_files() {
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_file_params( array(
'file' => array(
'file' => file_get_contents( $this->test_file ),
'name' => 'canola.jpg',
'size' => filesize( $this->test_file ),
'tmp_name' => $this->test_file,
),
) );
$request->set_header( 'Content-MD5', md5_file( $this->test_file ) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 201, $response->get_status() );
}
public function test_create_item_with_upload_files_role() {
wp_set_current_user( $this->uploader_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_file_params( array(
'file' => array(
'file' => file_get_contents( $this->test_file ),
'name' => 'canola.jpg',
'size' => filesize( $this->test_file ),
'tmp_name' => $this->test_file,
),
) );
$request->set_header( 'Content-MD5', md5_file( $this->test_file ) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 201, $response->get_status() );
}
public function test_create_item_empty_body() {
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_upload_no_data', $response, 400 );
}
public function test_create_item_missing_content_type() {
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_body( file_get_contents( $this->test_file ) );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_upload_no_content_type', $response, 400 );
}
public function test_create_item_missing_content_disposition() {
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_header( 'Content-Type', 'image/jpeg' );
$request->set_body( file_get_contents( $this->test_file ) );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_upload_no_content_disposition', $response, 400 );
}
public function test_create_item_bad_md5_header() {
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_header( 'Content-Type', 'image/jpeg' );
$request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
$request->set_header( 'Content-MD5', 'abc123' );
$request->set_body( file_get_contents( $this->test_file ) );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_upload_hash_mismatch', $response, 412 );
}
public function test_create_item_with_files_bad_md5_header() {
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_file_params( array(
'file' => array(
'file' => file_get_contents( $this->test_file ),
'name' => 'canola.jpg',
'size' => filesize( $this->test_file ),
'tmp_name' => $this->test_file,
),
) );
$request->set_header( 'Content-MD5', 'abc123' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_upload_hash_mismatch', $response, 412 );
}
public function test_create_item_invalid_upload_files_capability() {
wp_set_current_user( $this->contributor_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_create', $response, 403 );
}
public function test_create_item_invalid_edit_permissions() {
$post_id = $this->factory->post->create( array( 'post_author' => $this->editor_id ) );
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_param( 'post', $post_id );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_edit', $response, 403 );
}
public function test_create_item_invalid_upload_permissions() {
$post_id = $this->factory->post->create( array( 'post_author' => $this->editor_id ) );
wp_set_current_user( $this->uploader_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_param( 'post', $post_id );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_edit', $response, 403 );
}
public function test_create_item_invalid_post_type() {
$attachment_id = $this->factory->post->create( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_parent' => 0 ) );
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_header( 'Content-Type', 'image/jpeg' );
$request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
$request->set_body( file_get_contents( $this->test_file ) );
$request->set_param( 'post', $attachment_id );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
}
public function test_create_item_alt_text() {
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_header( 'Content-Type', 'image/jpeg' );
$request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
$request->set_body( file_get_contents( $this->test_file ) );
$request->set_param( 'alt_text', 'test alt text' );
$response = $this->server->dispatch( $request );
$attachment = $response->get_data();
$this->assertEquals( 'test alt text', $attachment['alt_text'] );
}
public function test_create_item_unsafe_alt_text() {
wp_set_current_user( $this->author_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/media' );
$request->set_header( 'Content-Type', 'image/jpeg' );
$request->set_header( 'Content-Disposition', 'attachment; filename=canola.jpg' );
$request->set_body( file_get_contents( $this->test_file ) );
$request->set_param( 'alt_text', '<script>alert(document.cookie)</script>' );
$response = $this->server->dispatch( $request );
$attachment = $response->get_data();
$this->assertEquals( '', $attachment['alt_text'] );
}
public function test_update_item() {
wp_set_current_user( $this->editor_id );
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
'post_author' => $this->editor_id,
) );
$request = new WP_REST_Request( 'POST', '/wp/v2/media/' . $attachment_id );
$request->set_param( 'title', 'My title is very cool' );
$request->set_param( 'caption', 'This is a better caption.' );
$request->set_param( 'description', 'Without a description, my attachment is descriptionless.' );
$request->set_param( 'alt_text', 'Alt text is stored outside post schema.' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$attachment = get_post( $data['id'] );
$this->assertEquals( 'My title is very cool', $data['title']['raw'] );
$this->assertEquals( 'My title is very cool', $attachment->post_title );
$this->assertEquals( 'This is a better caption.', $data['caption'] );
$this->assertEquals( 'This is a better caption.', $attachment->post_excerpt );
$this->assertEquals( 'Without a description, my attachment is descriptionless.', $data['description'] );
$this->assertEquals( 'Without a description, my attachment is descriptionless.', $attachment->post_content );
$this->assertEquals( 'Alt text is stored outside post schema.', $data['alt_text'] );
$this->assertEquals( 'Alt text is stored outside post schema.', get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ) );
}
public function test_update_item_parent() {
wp_set_current_user( $this->editor_id );
$original_parent = $this->factory->post->create( array() );
$attachment_id = $this->factory->attachment->create_object( $this->test_file, $original_parent, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
'post_author' => $this->editor_id,
) );
$attachment = get_post( $attachment_id );
$this->assertEquals( $original_parent, $attachment->post_parent );
$new_parent = $this->factory->post->create( array() );
$request = new WP_REST_Request( 'POST', '/wp/v2/media/' . $attachment_id );
$request->set_param( 'post', $new_parent );
$this->server->dispatch( $request );
$attachment = get_post( $attachment_id );
$this->assertEquals( $new_parent, $attachment->post_parent );
}
public function test_update_item_invalid_permissions() {
wp_set_current_user( $this->author_id );
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
'post_author' => $this->editor_id,
) );
$request = new WP_REST_Request( 'POST', '/wp/v2/media/' . $attachment_id );
$request->set_param( 'caption', 'This is a better caption.' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_edit', $response, 403 );
}
public function test_update_item_invalid_post_type() {
$attachment_id = $this->factory->post->create( array( 'post_type' => 'attachment', 'post_status' => 'inherit', 'post_parent' => 0 ) );
wp_set_current_user( $this->editor_id );
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
'post_author' => $this->editor_id,
) );
$request = new WP_REST_Request( 'POST', '/wp/v2/media/' . $attachment_id );
$request->set_param( 'post', $attachment_id );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
}
public function test_delete_item() {
wp_set_current_user( $this->editor_id );
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/media/' . $attachment_id );
$request['force'] = true;
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
}
public function test_delete_item_no_trash() {
wp_set_current_user( $this->editor_id );
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
// Attempt trashing
$request = new WP_REST_Request( 'DELETE', '/wp/v2/media/' . $attachment_id );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_trash_not_supported', $response, 501 );
// Ensure the post still exists
$post = get_post( $attachment_id );
$this->assertNotEmpty( $post );
}
public function test_delete_item_invalid_delete_permissions() {
wp_set_current_user( $this->author_id );
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
'post_author' => $this->editor_id,
) );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/media/' . $attachment_id );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_delete', $response, 403 );
}
public function test_prepare_item() {
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
'post_author' => $this->editor_id,
) );
$attachment = get_post( $attachment_id );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/media/%d', $attachment_id ) );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->check_post_data( $attachment, $data, 'view', $response->get_links() );
$this->check_post_data( $attachment, $data, 'embed', $response->get_links() );
}
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertEquals( 23, count( $properties ) );
$this->assertArrayHasKey( 'author', $properties );
$this->assertArrayHasKey( 'alt_text', $properties );
$this->assertArrayHasKey( 'caption', $properties );
$this->assertArrayHasKey( 'description', $properties );
$this->assertArrayHasKey( 'comment_status', $properties );
$this->assertArrayHasKey( 'date', $properties );
$this->assertArrayHasKey( 'date_gmt', $properties );
$this->assertArrayHasKey( 'guid', $properties );
$this->assertArrayHasKey( 'id', $properties );
$this->assertArrayHasKey( 'link', $properties );
$this->assertArrayHasKey( 'media_type', $properties );
$this->assertArrayHasKey( 'meta', $properties );
$this->assertArrayHasKey( 'mime_type', $properties );
$this->assertArrayHasKey( 'media_details', $properties );
$this->assertArrayHasKey( 'modified', $properties );
$this->assertArrayHasKey( 'modified_gmt', $properties );
$this->assertArrayHasKey( 'post', $properties );
$this->assertArrayHasKey( 'ping_status', $properties );
$this->assertArrayHasKey( 'status', $properties );
$this->assertArrayHasKey( 'slug', $properties );
$this->assertArrayHasKey( 'source_url', $properties );
$this->assertArrayHasKey( 'title', $properties );
$this->assertArrayHasKey( 'type', $properties );
}
public function test_get_additional_field_registration() {
$schema = array(
'type' => 'integer',
'description' => 'Some integer of mine',
'enum' => array( 1, 2, 3, 4 ),
'context' => array( 'view', 'edit' ),
);
register_rest_field( 'attachment', 'my_custom_int', array(
'schema' => $schema,
'get_callback' => array( $this, 'additional_field_get_callback' ),
) );
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/media' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
$this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/media/' . $attachment_id );
$response = $this->server->dispatch( $request );
$this->assertArrayHasKey( 'my_custom_int', $response->data );
global $wp_rest_additional_fields;
$wp_rest_additional_fields = array();
}
public function test_additional_field_update_errors() {
$schema = array(
'type' => 'integer',
'description' => 'Some integer of mine',
'enum' => array( 1, 2, 3, 4 ),
'context' => array( 'view', 'edit' ),
);
register_rest_field( 'attachment', 'my_custom_int', array(
'schema' => $schema,
'get_callback' => array( $this, 'additional_field_get_callback' ),
'update_callback' => array( $this, 'additional_field_update_callback' ),
) );
wp_set_current_user( $this->editor_id );
$attachment_id = $this->factory->attachment->create_object( $this->test_file, 0, array(
'post_mime_type' => 'image/jpeg',
'post_excerpt' => 'A sample caption',
'post_author' => $this->editor_id,
) );
// Check for error on update.
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/media/%d', $attachment_id ) );
$request->set_body_params(array(
'my_custom_int' => 'returnError',
));
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
global $wp_rest_additional_fields;
$wp_rest_additional_fields = array();
}
public function additional_field_get_callback( $object, $request ) {
return 123;
}
public function additional_field_update_callback( $value, $attachment ) {
if ( 'returnError' === $value ) {
return new WP_Error( 'rest_invalid_param', 'Testing an error.', array( 'status' => 400 ) );
}
}
public function tearDown() {
parent::tearDown();
if ( file_exists( $this->test_file ) ) {
unlink( $this->test_file );
}
if ( file_exists( $this->test_file2 ) ) {
unlink( $this->test_file2 );
}
}
protected function check_post_data( $attachment, $data, $context = 'view', $links ) {
parent::check_post_data( $attachment, $data, $context, $links );
$this->assertEquals( get_post_meta( $attachment->ID, '_wp_attachment_image_alt', true ), $data['alt_text'] );
$this->assertEquals( $attachment->post_excerpt, $data['caption'] );
$this->assertEquals( $attachment->post_content, $data['description'] );
$this->assertTrue( isset( $data['media_details'] ) );
if ( $attachment->post_parent ) {
$this->assertEquals( $attachment->post_parent, $data['post'] );
} else {
$this->assertNull( $data['post'] );
}
$this->assertEquals( wp_get_attachment_url( $attachment->ID ), $data['source_url'] );
}
}

View File

@ -0,0 +1,882 @@
<?php
/**
* Unit tests covering WP_REST_Terms_Controller functionality, used for
* Categories.
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Categories_Controller extends WP_Test_REST_Controller_Testcase {
public function setUp() {
parent::setUp();
$this->administrator = $this->factory->user->create( array(
'role' => 'administrator',
) );
$this->subscriber = $this->factory->user->create( array(
'role' => 'subscriber',
) );
}
public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( '/wp/v2/categories', $routes );
$this->assertArrayHasKey( '/wp/v2/categories/(?P<id>[\d]+)', $routes );
}
public function test_context_param() {
// Collection
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/categories' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
// Single
$category1 = $this->factory->category->create( array( 'name' => 'Season 5' ) );
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/categories/' . $category1 );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
}
public function test_registered_query_params() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/categories' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$keys = array_keys( $data['endpoints'][0]['args'] );
sort( $keys );
$this->assertEquals( array(
'context',
'exclude',
'hide_empty',
'include',
'order',
'orderby',
'page',
'parent',
'per_page',
'post',
'search',
'slug',
), $keys );
}
public function test_get_items() {
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$response = $this->server->dispatch( $request );
$this->check_get_taxonomy_terms_response( $response );
}
public function test_get_items_invalid_permission_for_context() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden_context', $response, 401 );
}
public function test_get_items_hide_empty_arg() {
$post_id = $this->factory->post->create();
$category1 = $this->factory->category->create( array( 'name' => 'Season 5' ) );
$category2 = $this->factory->category->create( array( 'name' => 'The Be Sharps' ) );
wp_set_object_terms( $post_id, array( $category1, $category2 ), 'category' );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'hide_empty', true );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( 'Season 5', $data[0]['name'] );
$this->assertEquals( 'The Be Sharps', $data[1]['name'] );
}
public function test_get_items_parent_zero_arg() {
$parent1 = $this->factory->category->create( array( 'name' => 'Homer' ) );
$parent2 = $this->factory->category->create( array( 'name' => 'Marge' ) );
$this->factory->category->create(
array(
'name' => 'Bart',
'parent' => $parent1,
)
);
$this->factory->category->create(
array(
'name' => 'Lisa',
'parent' => $parent2,
)
);
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'parent', 0 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$args = array(
'hide_empty' => false,
'parent' => 0,
);
$categories = get_terms( 'category', $args );
$this->assertEquals( count( $categories ), count( $data ) );
}
public function test_get_items_parent_zero_arg_string() {
$parent1 = $this->factory->category->create( array( 'name' => 'Homer' ) );
$parent2 = $this->factory->category->create( array( 'name' => 'Marge' ) );
$this->factory->category->create(
array(
'name' => 'Bart',
'parent' => $parent1,
)
);
$this->factory->category->create(
array(
'name' => 'Lisa',
'parent' => $parent2,
)
);
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'parent', '0' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$args = array(
'hide_empty' => false,
'parent' => 0,
);
$categories = get_terms( 'category', $args );
$this->assertEquals( count( $categories ), count( $data ) );
}
public function test_get_items_by_parent_non_found() {
$parent1 = $this->factory->category->create( array( 'name' => 'Homer' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'parent', $parent1 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( array(), $data );
}
public function test_get_items_invalid_page() {
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'page', 0 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
$data = $response->get_data();
$first_error = array_shift( $data['data']['params'] );
$this->assertContains( 'page must be greater than 1 (inclusive)', $first_error );
}
public function test_get_items_include_query() {
$id1 = $this->factory->category->create();
$this->factory->category->create();
$id3 = $this->factory->category->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
// Orderby=>asc
$request->set_param( 'include', array( $id3, $id1 ) );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( $id1, $data[0]['id'] );
// Orderby=>include
$request->set_param( 'orderby', 'include' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( $id3, $data[0]['id'] );
}
public function test_get_items_exclude_query() {
$id1 = $this->factory->category->create();
$id2 = $this->factory->category->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) );
$this->assertTrue( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) );
$request->set_param( 'exclude', array( $id2 ) );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) );
$this->assertFalse( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) );
}
public function test_get_items_orderby_args() {
$this->factory->category->create( array( 'name' => 'Apple' ) );
$this->factory->category->create( array( 'name' => 'Banana' ) );
/*
* Tests:
* - orderby
* - order
* - per_page
*/
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'orderby', 'name' );
$request->set_param( 'order', 'desc' );
$request->set_param( 'per_page', 1 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( 'Uncategorized', $data[0]['name'] );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'orderby', 'name' );
$request->set_param( 'order', 'asc' );
$request->set_param( 'per_page', 2 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( 'Apple', $data[0]['name'] );
}
public function test_get_items_orderby_id() {
$this->factory->category->create( array( 'name' => 'Cantaloupe' ) );
$this->factory->category->create( array( 'name' => 'Apple' ) );
$this->factory->category->create( array( 'name' => 'Banana' ) );
// defaults to orderby=name, order=asc
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'Apple', $data[0]['name'] );
$this->assertEquals( 'Banana', $data[1]['name'] );
$this->assertEquals( 'Cantaloupe', $data[2]['name'] );
$this->assertEquals( 'Uncategorized', $data[3]['name'] );
// orderby=id, with default order=asc
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'orderby', 'id' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'Uncategorized', $data[0]['name'] );
$this->assertEquals( 'Cantaloupe', $data[1]['name'] );
$this->assertEquals( 'Apple', $data[2]['name'] );
$this->assertEquals( 'Banana', $data[3]['name'] );
// orderby=id, order=desc
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'orderby', 'id' );
$request->set_param( 'order', 'desc' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'Banana', $data[0]['name'] );
$this->assertEquals( 'Apple', $data[1]['name'] );
$this->assertEquals( 'Cantaloupe', $data[2]['name'] );
}
protected function post_with_categories() {
$post_id = $this->factory->post->create();
$category1 = $this->factory->category->create( array(
'name' => 'DC',
'description' => 'Purveyor of fine detective comics',
) );
$category2 = $this->factory->category->create( array(
'name' => 'Marvel',
'description' => 'Home of the Marvel Universe',
) );
$category3 = $this->factory->category->create( array(
'name' => 'Image',
'description' => 'American independent comic publisher',
) );
wp_set_object_terms( $post_id, array( $category1, $category2, $category3 ), 'category' );
return $post_id;
}
public function test_get_items_post_args() {
$post_id = $this->post_with_categories();
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'post', $post_id );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 3, count( $data ) );
// Check ordered by name by default
$names = wp_list_pluck( $data, 'name' );
$this->assertEquals( array( 'DC', 'Image', 'Marvel' ), $names );
}
public function test_get_items_post_ordered_by_description() {
$post_id = $this->post_with_categories();
// Regular request
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'post', $post_id );
$request->set_param( 'orderby', 'description' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 3, count( $data ) );
$names = wp_list_pluck( $data, 'name' );
$this->assertEquals( array( 'Image', 'Marvel', 'DC' ), $names, 'Terms should be ordered by description' );
// Flip the order
$request->set_param( 'order', 'desc' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 3, count( $data ) );
$names = wp_list_pluck( $data, 'name' );
$this->assertEquals( array( 'DC', 'Marvel', 'Image' ), $names, 'Terms should be reverse-ordered by description' );
}
public function test_get_items_post_ordered_by_id() {
$post_id = $this->post_with_categories();
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'post', $post_id );
$request->set_param( 'orderby', 'id' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 3, count( $data ) );
$names = wp_list_pluck( $data, 'name' );
$this->assertEquals( array( 'DC', 'Marvel', 'Image' ), $names );
}
public function test_get_items_custom_tax_post_args() {
register_taxonomy( 'batman', 'post', array( 'show_in_rest' => true ) );
$controller = new WP_REST_Terms_Controller( 'batman' );
$controller->register_routes();
$term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'batman' ) );
$term2 = $this->factory->term->create( array( 'name' => 'Mask', 'taxonomy' => 'batman' ) );
$this->factory->term->create( array( 'name' => 'Car', 'taxonomy' => 'batman' ) );
$post_id = $this->factory->post->create();
wp_set_object_terms( $post_id, array( $term1, $term2 ), 'batman' );
$request = new WP_REST_Request( 'GET', '/wp/v2/batman' );
$request->set_param( 'post', $post_id );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( 'Cape', $data[0]['name'] );
}
public function test_get_items_search_args() {
$this->factory->category->create( array( 'name' => 'Apple' ) );
$this->factory->category->create( array( 'name' => 'Banana' ) );
/*
* Tests:
* - search
*/
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'search', 'App' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( 'Apple', $data[0]['name'] );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'search', 'Garbage' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 0, count( $data ) );
}
public function test_get_items_slug_arg() {
$this->factory->category->create( array( 'name' => 'Apple' ) );
$this->factory->category->create( array( 'name' => 'Banana' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'slug', 'apple' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( 'Apple', $data[0]['name'] );
}
public function test_get_terms_parent_arg() {
$category1 = $this->factory->category->create( array( 'name' => 'Parent' ) );
$this->factory->category->create( array( 'name' => 'Child', 'parent' => $category1 ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'parent', $category1 );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( 'Child', $data[0]['name'] );
}
public function test_get_terms_private_taxonomy() {
register_taxonomy( 'robin', 'post', array( 'public' => false ) );
$this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) );
$this->factory->term->create( array( 'name' => 'Mask', 'taxonomy' => 'robin' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/terms/robin' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_get_terms_invalid_taxonomy() {
$request = new WP_REST_Request( 'GET', '/wp/v2/invalid-taxonomy' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_get_terms_pagination_headers() {
// Start of the index + Uncategorized default term
for ( $i = 0; $i < 49; $i++ ) {
$this->factory->category->create( array(
'name' => "Category {$i}",
) );
}
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$response = $this->server->dispatch( $request );
$headers = $response->get_headers();
$this->assertEquals( 50, $headers['X-WP-Total'] );
$this->assertEquals( 5, $headers['X-WP-TotalPages'] );
$this->assertCount( 10, $response->get_data() );
$next_link = add_query_arg( array(
'page' => 2,
), rest_url( 'wp/v2/categories' ) );
$this->assertFalse( stripos( $headers['Link'], 'rel="prev"' ) );
$this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] );
// 3rd page
$this->factory->category->create( array(
'name' => 'Category 51',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'page', 3 );
$response = $this->server->dispatch( $request );
$headers = $response->get_headers();
$this->assertEquals( 51, $headers['X-WP-Total'] );
$this->assertEquals( 6, $headers['X-WP-TotalPages'] );
$this->assertCount( 10, $response->get_data() );
$prev_link = add_query_arg( array(
'page' => 2,
), rest_url( 'wp/v2/categories' ) );
$this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] );
$next_link = add_query_arg( array(
'page' => 4,
), rest_url( 'wp/v2/categories' ) );
$this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] );
// Last page
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'page', 6 );
$response = $this->server->dispatch( $request );
$headers = $response->get_headers();
$this->assertEquals( 51, $headers['X-WP-Total'] );
$this->assertEquals( 6, $headers['X-WP-TotalPages'] );
$this->assertCount( 1, $response->get_data() );
$prev_link = add_query_arg( array(
'page' => 5,
), rest_url( 'wp/v2/categories' ) );
$this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] );
$this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) );
// Out of bounds
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'page', 8 );
$response = $this->server->dispatch( $request );
$headers = $response->get_headers();
$this->assertEquals( 51, $headers['X-WP-Total'] );
$this->assertEquals( 6, $headers['X-WP-TotalPages'] );
$this->assertCount( 0, $response->get_data() );
$prev_link = add_query_arg( array(
'page' => 6,
), rest_url( 'wp/v2/categories' ) );
$this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] );
$this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) );
}
public function test_get_items_per_page_exceeds_number_of_items() {
// Start of the index + Uncategorized default term
for ( $i = 0; $i < 17; $i++ ) {
$this->factory->category->create( array(
'name' => "Category {$i}",
) );
}
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'page', 1 );
$request->set_param( 'per_page', 100 );
$response = $this->server->dispatch( $request );
$headers = $response->get_headers();
$this->assertEquals( 18, $headers['X-WP-Total'] );
$this->assertEquals( 1, $headers['X-WP-TotalPages'] );
$this->assertCount( 18, $response->get_data() );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories' );
$request->set_param( 'page', 2 );
$request->set_param( 'per_page', 100 );
$response = $this->server->dispatch( $request );
$headers = $response->get_headers();
$this->assertEquals( 18, $headers['X-WP-Total'] );
$this->assertEquals( 1, $headers['X-WP-TotalPages'] );
$this->assertCount( 0, $response->get_data() );
}
public function test_get_item() {
$request = new WP_REST_Request( 'GET', '/wp/v2/categories/1' );
$response = $this->server->dispatch( $request );
$this->check_get_taxonomy_term_response( $response );
}
public function test_get_term_invalid_taxonomy() {
$request = new WP_REST_Request( 'GET', '/wp/v2/invalid-taxonomy/1' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_get_term_invalid_term() {
$request = new WP_REST_Request( 'GET', '/wp/v2/categories/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_term_invalid', $response, 404 );
}
public function test_get_item_invalid_permission_for_context() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories/1' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden_context', $response, 401 );
}
public function test_get_term_private_taxonomy() {
register_taxonomy( 'robin', 'post', array( 'public' => false ) );
$term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/terms/robin/' . $term1 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_get_item_incorrect_taxonomy() {
register_taxonomy( 'robin', 'post' );
$term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories/' . $term1 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_term_invalid', $response, 404 );
}
public function test_create_item() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'POST', '/wp/v2/categories' );
$request->set_param( 'name', 'My Awesome Term' );
$request->set_param( 'description', 'This term is so awesome.' );
$request->set_param( 'slug', 'so-awesome' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 201, $response->get_status() );
$headers = $response->get_headers();
$data = $response->get_data();
$this->assertContains( '/wp/v2/categories/' . $data['id'], $headers['Location'] );
$this->assertEquals( 'My Awesome Term', $data['name'] );
$this->assertEquals( 'This term is so awesome.', $data['description'] );
$this->assertEquals( 'so-awesome', $data['slug'] );
}
public function test_create_item_invalid_taxonomy() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'POST', '/wp/v2/invalid-taxonomy' );
$request->set_param( 'name', 'Invalid Taxonomy' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_create_item_incorrect_permissions() {
wp_set_current_user( $this->subscriber );
$request = new WP_REST_Request( 'POST', '/wp/v2/categories' );
$request->set_param( 'name', 'Incorrect permissions' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_create', $response, 403 );
}
public function test_create_item_missing_arguments() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'POST', '/wp/v2/categories' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_missing_callback_param', $response, 400 );
}
public function test_create_item_with_parent() {
wp_set_current_user( $this->administrator );
$parent = wp_insert_term( 'test-category', 'category' );
$request = new WP_REST_Request( 'POST', '/wp/v2/categories' );
$request->set_param( 'name', 'My Awesome Term' );
$request->set_param( 'parent', $parent['term_id'] );
$response = $this->server->dispatch( $request );
$this->assertEquals( 201, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( $parent['term_id'], $data['parent'] );
}
public function test_create_item_invalid_parent() {
wp_set_current_user( $this->administrator );
$term = get_term_by( 'id', $this->factory->category->create(), 'category' );
$request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . $term->term_id );
$request->set_param( 'name', 'My Awesome Term' );
$request->set_param( 'parent', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_term_invalid', $response, 400 );
}
public function test_update_item() {
wp_set_current_user( $this->administrator );
$orig_args = array(
'name' => 'Original Name',
'description' => 'Original Description',
'slug' => 'original-slug',
);
$term = get_term_by( 'id', $this->factory->category->create( $orig_args ), 'category' );
$request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . $term->term_id );
$request->set_param( 'name', 'New Name' );
$request->set_param( 'description', 'New Description' );
$request->set_param( 'slug', 'new-slug' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'New Name', $data['name'] );
$this->assertEquals( 'New Description', $data['description'] );
$this->assertEquals( 'new-slug', $data['slug'] );
}
public function test_update_item_invalid_taxonomy() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'POST', '/wp/v2/invalid-taxonomy/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$request->set_param( 'name', 'Invalid Taxonomy' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_update_item_invalid_term() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$request->set_param( 'name', 'Invalid Term' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_term_invalid', $response, 404 );
}
public function test_update_item_incorrect_permissions() {
wp_set_current_user( $this->subscriber );
$term = get_term_by( 'id', $this->factory->category->create(), 'category' );
$request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . $term->term_id );
$request->set_param( 'name', 'Incorrect permissions' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_update', $response, 403 );
}
public function test_update_item_parent() {
wp_set_current_user( $this->administrator );
$parent = get_term_by( 'id', $this->factory->category->create(), 'category' );
$term = get_term_by( 'id', $this->factory->category->create(), 'category' );
$request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . $term->term_taxonomy_id );
$request->set_param( 'parent', $parent->term_id );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( $parent->term_id, $data['parent'] );
}
public function test_update_item_invalid_parent() {
wp_set_current_user( $this->administrator );
$term = get_term_by( 'id', $this->factory->category->create(), 'category' );
$request = new WP_REST_Request( 'POST', '/wp/v2/categories/' . $term->term_id );
$request->set_param( 'parent', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_term_invalid', $response, 400 );
}
public function test_delete_item() {
wp_set_current_user( $this->administrator );
$term = get_term_by( 'id', $this->factory->category->create( array( 'name' => 'Deleted Category' ) ), 'category' );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/categories/' . $term->term_id );
$request->set_param( 'force', true );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'Deleted Category', $data['name'] );
}
public function test_delete_item_force_false() {
wp_set_current_user( $this->administrator );
$term = get_term_by( 'id', $this->factory->category->create( array( 'name' => 'Deleted Category' ) ), 'category' );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/categories/' . $term->term_id );
// force defaults to false
$response = $this->server->dispatch( $request );
$this->assertEquals( 501, $response->get_status() );
}
public function test_delete_item_invalid_taxonomy() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/invalid-taxonomy/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_delete_item_invalid_term() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/categories/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_term_invalid', $response, 404 );
}
public function test_delete_item_incorrect_permissions() {
wp_set_current_user( $this->subscriber );
$term = get_term_by( 'id', $this->factory->category->create(), 'category' );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/categories/' . $term->term_id );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_delete', $response, 403 );
}
public function test_prepare_item() {
$term = get_term( 1, 'category' );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories/1' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->check_taxonomy_term( $term, $data, $response->get_links() );
}
public function test_prepare_taxonomy_term_child() {
$child = $this->factory->category->create( array(
'parent' => 1,
) );
$term = get_term( $child, 'category' );
$request = new WP_REST_Request( 'GET', '/wp/v2/categories/' . $child );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->check_taxonomy_term( $term, $data, $response->get_links() );
$this->assertEquals( 1, $data['parent'] );
$links = $response->get_links();
$this->assertEquals( rest_url( 'wp/v2/categories/1' ), $links['up'][0]['href'] );
}
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/categories' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertEquals( 9, count( $properties ) );
$this->assertArrayHasKey( 'id', $properties );
$this->assertArrayHasKey( 'count', $properties );
$this->assertArrayHasKey( 'description', $properties );
$this->assertArrayHasKey( 'link', $properties );
$this->assertArrayHasKey( 'meta', $properties );
$this->assertArrayHasKey( 'name', $properties );
$this->assertArrayHasKey( 'parent', $properties );
$this->assertArrayHasKey( 'slug', $properties );
$this->assertArrayHasKey( 'taxonomy', $properties );
$this->assertEquals( array_keys( get_taxonomies() ), $properties['taxonomy']['enum'] );
}
public function test_get_additional_field_registration() {
$schema = array(
'type' => 'integer',
'description' => 'Some integer of mine',
'enum' => array( 1, 2, 3, 4 ),
'context' => array( 'view', 'edit' ),
);
register_rest_field( 'category', 'my_custom_int', array(
'schema' => $schema,
'get_callback' => array( $this, 'additional_field_get_callback' ),
) );
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/categories' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
$this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
$category_id = $this->factory->category->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/categories/' . $category_id );
$response = $this->server->dispatch( $request );
$this->assertArrayHasKey( 'my_custom_int', $response->data );
global $wp_rest_additional_fields;
$wp_rest_additional_fields = array();
}
public function additional_field_get_callback( $object, $request ) {
return 123;
}
public function tearDown() {
_unregister_taxonomy( 'batman' );
_unregister_taxonomy( 'robin' );
parent::tearDown();
}
protected function check_get_taxonomy_terms_response( $response ) {
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$args = array(
'hide_empty' => false,
);
$categories = get_terms( 'category', $args );
$this->assertEquals( count( $categories ), count( $data ) );
$this->assertEquals( $categories[0]->term_id, $data[0]['id'] );
$this->assertEquals( $categories[0]->name, $data[0]['name'] );
$this->assertEquals( $categories[0]->slug, $data[0]['slug'] );
$this->assertEquals( $categories[0]->taxonomy, $data[0]['taxonomy'] );
$this->assertEquals( $categories[0]->description, $data[0]['description'] );
$this->assertEquals( $categories[0]->count, $data[0]['count'] );
}
protected function check_taxonomy_term( $term, $data, $links ) {
$this->assertEquals( $term->term_id, $data['id'] );
$this->assertEquals( $term->name, $data['name'] );
$this->assertEquals( $term->slug, $data['slug'] );
$this->assertEquals( $term->description, $data['description'] );
$this->assertEquals( get_term_link( $term ), $data['link'] );
$this->assertEquals( $term->count, $data['count'] );
$taxonomy = get_taxonomy( $term->taxonomy );
if ( $taxonomy->hierarchical ) {
$this->assertEquals( $term->parent, $data['parent'] );
} else {
$this->assertFalse( isset( $term->parent ) );
}
$relations = array(
'self',
'collection',
'about',
'https://api.w.org/post_type',
);
if ( ! empty( $data['parent'] ) ) {
$relations[] = 'up';
}
$this->assertEqualSets( $relations, array_keys( $links ) );
$this->assertContains( 'wp/v2/taxonomies/' . $term->taxonomy, $links['about'][0]['href'] );
$this->assertEquals( add_query_arg( 'categories', $term->term_id, rest_url( 'wp/v2/posts' ) ), $links['https://api.w.org/post_type'][0]['href'] );
}
protected function check_get_taxonomy_term_response( $response ) {
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$category = get_term( 1, 'category' );
$this->check_taxonomy_term( $category, $data, $response->get_links() );
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,217 @@
<?php
/**
* Unit tests covering WP_REST_Controller functionality
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Controller extends WP_Test_REST_TestCase {
public function setUp() {
parent::setUp();
$this->request = new WP_REST_Request( 'GET', '/wp/v2/testroute', array(
'args' => array(
'someinteger' => array(
'type' => 'integer',
),
'someboolean' => array(
'type' => 'boolean',
),
'somestring' => array(
'type' => 'string',
),
'someenum' => array(
'type' => 'string',
'enum' => array( 'a' ),
),
'somedate' => array(
'type' => 'string',
'format' => 'date-time',
),
'someemail' => array(
'type' => 'string',
'format' => 'email',
),
),
));
}
public function test_validate_schema_type_integer() {
$this->assertTrue(
rest_validate_request_arg( '123', $this->request, 'someinteger' )
);
$this->assertErrorResponse(
'rest_invalid_param',
rest_validate_request_arg( 'abc', $this->request, 'someinteger' )
);
}
public function test_validate_schema_type_boolean() {
$this->assertTrue(
rest_validate_request_arg( true, $this->request, 'someboolean' )
);
$this->assertTrue(
rest_validate_request_arg( false, $this->request, 'someboolean' )
);
$this->assertTrue(
rest_validate_request_arg( 'true', $this->request, 'someboolean' )
);
$this->assertTrue(
rest_validate_request_arg( 'TRUE', $this->request, 'someboolean' )
);
$this->assertTrue(
rest_validate_request_arg( 'false', $this->request, 'someboolean' )
);
$this->assertTrue(
rest_validate_request_arg( 'False', $this->request, 'someboolean' )
);
$this->assertTrue(
rest_validate_request_arg( '1', $this->request, 'someboolean' )
);
$this->assertTrue(
rest_validate_request_arg( '0', $this->request, 'someboolean' )
);
$this->assertTrue(
rest_validate_request_arg( 1, $this->request, 'someboolean' )
);
$this->assertTrue(
rest_validate_request_arg( 0, $this->request, 'someboolean' )
);
// Check sanitize testing.
$this->assertEquals( false,
rest_sanitize_request_arg( 'false', $this->request, 'someboolean' )
);
$this->assertEquals( false,
rest_sanitize_request_arg( '0', $this->request, 'someboolean' )
);
$this->assertEquals( false,
rest_sanitize_request_arg( 0, $this->request, 'someboolean' )
);
$this->assertEquals( false,
rest_sanitize_request_arg( 'FALSE', $this->request, 'someboolean' )
);
$this->assertEquals( true,
rest_sanitize_request_arg( 'true', $this->request, 'someboolean' )
);
$this->assertEquals( true,
rest_sanitize_request_arg( '1', $this->request, 'someboolean' )
);
$this->assertEquals( true,
rest_sanitize_request_arg( 1, $this->request, 'someboolean' )
);
$this->assertEquals( true,
rest_sanitize_request_arg( 'TRUE', $this->request, 'someboolean' )
);
$this->assertErrorResponse(
'rest_invalid_param',
rest_validate_request_arg( '123', $this->request, 'someboolean' )
);
}
public function test_validate_schema_type_string() {
$this->assertTrue(
rest_validate_request_arg( '123', $this->request, 'somestring' )
);
$this->assertErrorResponse(
'rest_invalid_param',
rest_validate_request_arg( array( 'foo' => 'bar' ), $this->request, 'somestring' )
);
}
public function test_validate_schema_enum() {
$this->assertTrue(
rest_validate_request_arg( 'a', $this->request, 'someenum' )
);
$this->assertErrorResponse(
'rest_invalid_param',
rest_validate_request_arg( 'd', $this->request, 'someenum' )
);
}
public function test_validate_schema_format_email() {
$this->assertTrue(
rest_validate_request_arg( 'joe@foo.bar', $this->request, 'someemail' )
);
$this->assertErrorResponse(
'rest_invalid_email',
rest_validate_request_arg( 'd', $this->request, 'someemail' )
);
}
public function test_validate_schema_format_date_time() {
$this->assertTrue(
rest_validate_request_arg( '2010-01-01T12:00:00', $this->request, 'somedate' )
);
$this->assertErrorResponse(
'rest_invalid_date',
rest_validate_request_arg( '2010-18-18T12:00:00', $this->request, 'somedate' )
);
}
public function test_get_endpoint_args_for_item_schema_description() {
$controller = new WP_REST_Test_Controller();
$args = $controller->get_endpoint_args_for_item_schema();
$this->assertEquals( 'A pretty string.', $args['somestring']['description'] );
$this->assertFalse( isset( $args['someinteger']['description'] ) );
}
public function test_get_endpoint_args_for_item_schema_arg_options() {
$controller = new WP_REST_Test_Controller();
$args = $controller->get_endpoint_args_for_item_schema();
$this->assertFalse( $args['someargoptions']['required'] );
$this->assertEquals( '__return_true', $args['someargoptions']['sanitize_callback'] );
}
public function test_get_endpoint_args_for_item_schema_default_value() {
$controller = new WP_REST_Test_Controller();
$args = $controller->get_endpoint_args_for_item_schema();
$this->assertEquals( 'a', $args['somedefault']['default'] );
}
public $rest_the_post_filter_apply_count = 0;
public function test_get_post() {
$post_id = $this->factory()->post->create( array( 'post_title' => 'Original' ) );
$controller = new WP_REST_Test_Controller();
$post = $controller->get_post( $post_id );
$this->assertEquals( 'Original', $post->post_title );
$filter_apply_count = $this->rest_the_post_filter_apply_count;
add_filter( 'rest_the_post', array( $this, 'filter_rest_the_post_for_test_get_post' ), 10, 2 );
$post = $controller->get_post( $post_id );
$this->assertEquals( 'Overridden', $post->post_title );
$this->assertEquals( 1 + $filter_apply_count, $this->rest_the_post_filter_apply_count );
}
public function filter_rest_the_post_for_test_get_post( $post, $post_id ) {
$this->assertInstanceOf( 'WP_Post', $post );
$this->assertInternalType( 'int', $post_id );
$post->post_title = 'Overridden';
$this->rest_the_post_filter_apply_count += 1;
return $post;
}
}

View File

@ -0,0 +1,407 @@
<?php
/**
* Unit tests covering WP_REST_Posts_Controller functionality, used for
* Pages
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Pages_Controller extends WP_Test_REST_Post_Type_Controller_Testcase {
public function setUp() {
parent::setUp();
$this->editor_id = $this->factory->user->create( array(
'role' => 'editor',
) );
$this->author_id = $this->factory->user->create( array(
'role' => 'author',
) );
$this->has_setup_template = false;
add_filter( 'theme_page_templates', array( $this, 'filter_theme_page_templates' ) );
}
public function test_register_routes() {
}
public function test_context_param() {
// Collection
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/pages' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
// Single
$page_id = $this->factory->post->create( array( 'post_type' => 'page' ) );
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/pages/' . $page_id );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEquals( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
}
public function test_registered_query_params() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/pages' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$keys = array_keys( $data['endpoints'][0]['args'] );
sort( $keys );
$this->assertEquals( array(
'after',
'author',
'author_exclude',
'before',
'context',
'exclude',
'filter',
'include',
'menu_order',
'offset',
'order',
'orderby',
'page',
'parent',
'parent_exclude',
'per_page',
'search',
'slug',
'status',
), $keys );
}
public function test_get_items() {
}
public function test_get_items_parent_query() {
$id1 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) );
$id2 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'post_parent' => $id1 ) );
// No parent
$request = new WP_REST_Request( 'GET', '/wp/v2/pages' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
// Filter to parent
$request->set_param( 'parent', $id1 );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( $id2, $data[0]['id'] );
}
public function test_get_items_parents_query() {
$id1 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) );
$id2 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'post_parent' => $id1 ) );
$id3 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) );
$id4 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'post_parent' => $id3 ) );
// No parent
$request = new WP_REST_Request( 'GET', '/wp/v2/pages' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 4, count( $data ) );
// Filter to parents
$request->set_param( 'parent', array( $id1, $id3 ) );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEqualSets( array( $id2, $id4 ), wp_list_pluck( $data, 'id' ) );
}
public function test_get_items_parent_exclude_query() {
$id1 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) );
$this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'post_parent' => $id1 ) );
// No parent
$request = new WP_REST_Request( 'GET', '/wp/v2/pages' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
// Filter to parent
$request->set_param( 'parent_exclude', $id1 );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( $id1, $data[0]['id'] );
}
public function test_get_items_menu_order_query() {
$id1 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) );
$id2 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'menu_order' => 2 ) );
$id3 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'menu_order' => 3 ) );
$id4 = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page', 'menu_order' => 1 ) );
// No parent
$request = new WP_REST_Request( 'GET', '/wp/v2/pages' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEqualSets( array( $id1, $id2, $id3, $id4 ), wp_list_pluck( $data, 'id' ) );
// Filter to menu_order
$request->set_param( 'menu_order', 1 );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEqualSets( array( $id4 ), wp_list_pluck( $data, 'id' ) );
// Order by menu order
$request = new WP_REST_Request( 'GET', '/wp/v2/pages' );
$request->set_param( 'order', 'asc' );
$request->set_param( 'orderby', 'menu_order' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( $id1, $data[0]['id'] );
$this->assertEquals( $id4, $data[1]['id'] );
$this->assertEquals( $id2, $data[2]['id'] );
$this->assertEquals( $id3, $data[3]['id'] );
}
public function test_get_items_min_max_pages_query() {
$request = new WP_REST_Request( 'GET', '/wp/v2/pages' );
$request->set_param( 'per_page', 0 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
$data = $response->get_data();
// Safe format for 4.4 and 4.5 https://core.trac.wordpress.org/ticket/35028
$first_error = array_shift( $data['data']['params'] );
$this->assertContains( 'per_page must be between 1 (inclusive) and 100 (inclusive)', $first_error );
$request->set_param( 'per_page', 101 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
$data = $response->get_data();
$first_error = array_shift( $data['data']['params'] );
$this->assertContains( 'per_page must be between 1 (inclusive) and 100 (inclusive)', $first_error );
}
public function test_get_items_private_filter_query_var() {
// Private query vars inaccessible to unauthorized users
wp_set_current_user( 0 );
$page_id = $this->factory->post->create( array( 'post_status' => 'publish', 'post_type' => 'page' ) );
$draft_id = $this->factory->post->create( array( 'post_status' => 'draft', 'post_type' => 'page' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/pages' );
$request->set_param( 'filter', array( 'post_status' => 'draft' ) );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertCount( 1, $data );
$this->assertEquals( $page_id, $data[0]['id'] );
// But they are accessible to authorized users
wp_set_current_user( $this->editor_id );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertCount( 1, $data );
$this->assertEquals( $draft_id, $data[0]['id'] );
}
public function test_get_items_invalid_date() {
$request = new WP_REST_Request( 'GET', '/wp/v2/pages' );
$request->set_param( 'after', rand_str() );
$request->set_param( 'before', rand_str() );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
}
public function test_get_items_valid_date() {
$post1 = $this->factory->post->create( array( 'post_date' => '2016-01-15T00:00:00Z', 'post_type' => 'page' ) );
$post2 = $this->factory->post->create( array( 'post_date' => '2016-01-16T00:00:00Z', 'post_type' => 'page' ) );
$post3 = $this->factory->post->create( array( 'post_date' => '2016-01-17T00:00:00Z', 'post_type' => 'page' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/pages' );
$request->set_param( 'after', '2016-01-15T00:00:00Z' );
$request->set_param( 'before', '2016-01-17T00:00:00Z' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertCount( 1, $data );
$this->assertEquals( $post2, $data[0]['id'] );
}
public function test_get_item() {
}
public function test_get_item_invalid_post_type() {
$post_id = $this->factory->post->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/pages/' . $post_id );
$response = $this->server->dispatch( $request );
$this->assertEquals( 404, $response->get_status() );
}
public function test_create_item() {
}
public function test_create_item_with_template() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/pages' );
$params = $this->set_post_data( array(
'template' => 'page-my-test-template.php',
) );
$request->set_body_params( $params );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$new_post = get_post( $data['id'] );
$this->assertEquals( 'page-my-test-template.php', $data['template'] );
$this->assertEquals( 'page-my-test-template.php', get_page_template_slug( $new_post->ID ) );
}
public function test_create_page_with_parent() {
$page_id = $this->factory->post->create( array(
'type' => 'page',
) );
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/pages' );
$params = $this->set_post_data( array(
'parent' => $page_id,
) );
$request->set_body_params( $params );
$response = $this->server->dispatch( $request );
$this->assertEquals( 201, $response->get_status() );
$links = $response->get_links();
$this->assertArrayHasKey( 'up', $links );
$data = $response->get_data();
$new_post = get_post( $data['id'] );
$this->assertEquals( $page_id, $data['parent'] );
$this->assertEquals( $page_id, $new_post->post_parent );
}
public function test_create_page_with_invalid_parent() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'POST', '/wp/v2/pages' );
$params = $this->set_post_data( array(
'parent' => -1,
) );
$request->set_body_params( $params );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_id', $response, 400 );
}
public function test_update_item() {
}
public function test_delete_item() {
}
public function test_prepare_item() {
}
public function test_get_pages_params() {
$this->factory->post->create_many( 8, array(
'post_type' => 'page',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/pages' );
$request->set_query_params( array(
'page' => 2,
'per_page' => 4,
) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$headers = $response->get_headers();
$this->assertEquals( 8, $headers['X-WP-Total'] );
$this->assertEquals( 2, $headers['X-WP-TotalPages'] );
$all_data = $response->get_data();
$this->assertEquals( 4, count( $all_data ) );
foreach ( $all_data as $post ) {
$this->assertEquals( 'page', $post['type'] );
}
}
public function test_update_page_menu_order() {
$page_id = $this->factory->post->create( array(
'post_type' => 'page',
) );
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/pages/%d', $page_id ) );
$request->set_body_params( array(
'menu_order' => 1,
) );
$response = $this->server->dispatch( $request );
$new_data = $response->get_data();
$this->assertEquals( 1, $new_data['menu_order'] );
}
public function test_update_page_menu_order_to_zero() {
$page_id = $this->factory->post->create( array(
'post_type' => 'page',
'menu_order' => 1,
) );
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'PUT', sprintf( '/wp/v2/pages/%d', $page_id ) );
$request->set_body_params(array(
'menu_order' => 0,
));
$response = $this->server->dispatch( $request );
$new_data = $response->get_data();
$this->assertEquals( 0, $new_data['menu_order'] );
}
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/pages' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertEquals( 21, count( $properties ) );
$this->assertArrayHasKey( 'author', $properties );
$this->assertArrayHasKey( 'comment_status', $properties );
$this->assertArrayHasKey( 'content', $properties );
$this->assertArrayHasKey( 'date', $properties );
$this->assertArrayHasKey( 'date_gmt', $properties );
$this->assertArrayHasKey( 'guid', $properties );
$this->assertArrayHasKey( 'excerpt', $properties );
$this->assertArrayHasKey( 'featured_media', $properties );
$this->assertArrayHasKey( 'id', $properties );
$this->assertArrayHasKey( 'link', $properties );
$this->assertArrayHasKey( 'menu_order', $properties );
$this->assertArrayHasKey( 'meta', $properties );
$this->assertArrayHasKey( 'modified', $properties );
$this->assertArrayHasKey( 'modified_gmt', $properties );
$this->assertArrayHasKey( 'parent', $properties );
$this->assertArrayHasKey( 'ping_status', $properties );
$this->assertArrayHasKey( 'slug', $properties );
$this->assertArrayHasKey( 'status', $properties );
$this->assertArrayHasKey( 'template', $properties );
$this->assertArrayHasKey( 'title', $properties );
$this->assertArrayHasKey( 'type', $properties );
}
public function tearDown() {
parent::tearDown();
remove_filter( 'theme_page_templates', array( $this, 'filter_theme_page_templates' ) );
}
public function filter_theme_page_templates( $page_templates ) {
return array(
'page-my-test-template.php' => 'My Test Template',
);
return $page_templates;
}
protected function set_post_data( $args = array() ) {
$args = parent::set_post_data( $args );
$args['type'] = 'page';
return $args;
}
}

View File

@ -0,0 +1,636 @@
<?php
/**
* Unit tests covering WP_REST_Posts meta functionality.
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Post_Meta_Fields extends WP_Test_REST_TestCase {
public function setUp() {
parent::setUp();
register_meta( 'post', 'test_single', array(
'show_in_rest' => true,
'single' => true,
));
register_meta( 'post', 'test_multi', array(
'show_in_rest' => true,
'single' => false,
));
register_meta( 'post', 'test_bad_auth', array(
'show_in_rest' => true,
'single' => true,
'auth_callback' => '__return_false',
));
register_meta( 'post', 'test_bad_auth_multi', array(
'show_in_rest' => true,
'single' => false,
'auth_callback' => '__return_false',
));
register_meta( 'post', 'test_no_rest', array() );
register_meta( 'post', 'test_rest_disabled', array(
'show_in_rest' => false,
));
register_meta( 'post', 'test_custom_schema', array(
'single' => true,
'type' => 'integer',
'show_in_rest' => array(
'schema' => array(
'type' => 'number',
),
),
));
register_meta( 'post', 'test_invalid_type', array(
'single' => true,
'type' => false,
'show_in_rest' => true,
));
/** @var WP_REST_Server $wp_rest_server */
global $wp_rest_server;
$this->server = $wp_rest_server = new Spy_REST_Server;
do_action( 'rest_api_init' );
$this->post_id = $this->factory->post->create();
}
protected function grant_write_permission() {
// Ensure we have write permission.
$user = $this->factory->user->create( array(
'role' => 'editor',
));
wp_set_current_user( $user );
}
public function test_get_value() {
add_post_meta( $this->post_id, 'test_single', 'testvalue' );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertArrayHasKey( 'meta', $data );
$meta = (array) $data['meta'];
$this->assertArrayHasKey( 'test_single', $meta );
$this->assertEquals( 'testvalue', $meta['test_single'] );
}
/**
* @depends test_get_value
*/
public function test_get_multi_value() {
add_post_meta( $this->post_id, 'test_multi', 'value1' );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$meta = (array) $data['meta'];
$this->assertArrayHasKey( 'test_multi', $meta );
$this->assertInternalType( 'array', $meta['test_multi'] );
$this->assertContains( 'value1', $meta['test_multi'] );
// Check after an update.
add_post_meta( $this->post_id, 'test_multi', 'value2' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$meta = (array) $data['meta'];
$this->assertContains( 'value1', $meta['test_multi'] );
$this->assertContains( 'value2', $meta['test_multi'] );
}
/**
* @depends test_get_value
*/
public function test_get_unregistered() {
add_post_meta( $this->post_id, 'test_unregistered', 'value1' );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$meta = (array) $data['meta'];
$this->assertArrayNotHasKey( 'test_unregistered', $meta );
}
/**
* @depends test_get_value
*/
public function test_get_registered_no_api_access() {
add_post_meta( $this->post_id, 'test_no_rest', 'for_the_wicked' );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$meta = (array) $data['meta'];
$this->assertArrayNotHasKey( 'test_no_rest', $meta );
}
/**
* @depends test_get_value
*/
public function test_get_registered_api_disabled() {
add_post_meta( $this->post_id, 'test_rest_disabled', 'sleepless_nights' );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$meta = (array) $data['meta'];
$this->assertArrayNotHasKey( 'test_rest_disabled', $meta );
}
public function test_get_value_types() {
register_meta( 'post', 'test_string', array(
'show_in_rest' => true,
'single' => true,
'type' => 'string',
));
register_meta( 'post', 'test_number', array(
'show_in_rest' => true,
'single' => true,
'type' => 'number',
));
register_meta( 'post', 'test_bool', array(
'show_in_rest' => true,
'single' => true,
'type' => 'boolean',
));
/** @var WP_REST_Server $wp_rest_server */
global $wp_rest_server;
$this->server = $wp_rest_server = new Spy_REST_Server;
do_action( 'rest_api_init' );
add_post_meta( $this->post_id, 'test_string', 42 );
add_post_meta( $this->post_id, 'test_number', '42' );
add_post_meta( $this->post_id, 'test_bool', 1 );
$request = new WP_REST_Request( 'GET', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$meta = (array) $data['meta'];
$this->assertArrayHasKey( 'test_string', $meta );
$this->assertInternalType( 'string', $meta['test_string'] );
$this->assertSame( '42', $meta['test_string'] );
$this->assertArrayHasKey( 'test_number', $meta );
$this->assertInternalType( 'float', $meta['test_number'] );
$this->assertSame( 42.0, $meta['test_number'] );
$this->assertArrayHasKey( 'test_bool', $meta );
$this->assertInternalType( 'boolean', $meta['test_bool'] );
$this->assertSame( true, $meta['test_bool'] );
}
/**
* @depends test_get_value
*/
public function test_set_value() {
// Ensure no data exists currently.
$values = get_post_meta( $this->post_id, 'test_single', false );
$this->assertEmpty( $values );
$this->grant_write_permission();
$data = array(
'meta' => array(
'test_single' => 'test_value',
),
);
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$meta = get_post_meta( $this->post_id, 'test_single', false );
$this->assertNotEmpty( $meta );
$this->assertCount( 1, $meta );
$this->assertEquals( 'test_value', $meta[0] );
$data = $response->get_data();
$meta = (array) $data['meta'];
$this->assertArrayHasKey( 'test_single', $meta );
$this->assertEquals( 'test_value', $meta['test_single'] );
}
/**
* @depends test_get_value
*/
public function test_set_duplicate_single_value() {
// Start with an existing metakey and value.
$values = update_post_meta( $this->post_id, 'test_single', 'test_value' );
$this->assertEquals( 'test_value', get_post_meta( $this->post_id, 'test_single', true ) );
$this->grant_write_permission();
$data = array(
'meta' => array(
'test_single' => 'test_value',
),
);
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$meta = get_post_meta( $this->post_id, 'test_single', true );
$this->assertNotEmpty( $meta );
$this->assertEquals( 'test_value', $meta );
$data = $response->get_data();
$meta = (array) $data['meta'];
$this->assertArrayHasKey( 'test_single', $meta );
$this->assertEquals( 'test_value', $meta['test_single'] );
}
/**
* @depends test_set_value
*/
public function test_set_value_unauthenticated() {
$data = array(
'meta' => array(
'test_single' => 'test_value',
),
);
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_edit', $response, 401 );
// Check that the value wasn't actually updated.
$this->assertEmpty( get_post_meta( $this->post_id, 'test_single', false ) );
}
/**
* @depends test_set_value
*/
public function test_set_value_blocked() {
$data = array(
'meta' => array(
'test_bad_auth' => 'test_value',
),
);
$this->grant_write_permission();
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_update', $response, 403 );
$this->assertEmpty( get_post_meta( $this->post_id, 'test_bad_auth', false ) );
}
/**
* @depends test_set_value
*/
public function test_set_value_db_error() {
$data = array(
'meta' => array(
'test_single' => 'test_value',
),
);
$this->grant_write_permission();
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
/**
* Disable showing error as the below is going to intentionally
* trigger a DB error.
*/
global $wpdb;
$wpdb->suppress_errors = true;
add_filter( 'query', array( $this, 'error_insert_query' ) );
$response = $this->server->dispatch( $request );
remove_filter( 'query', array( $this, 'error_insert_query' ) );
$wpdb->show_errors = true;
}
public function test_set_value_multiple() {
// Ensure no data exists currently.
$values = get_post_meta( $this->post_id, 'test_multi', false );
$this->assertEmpty( $values );
$this->grant_write_permission();
$data = array(
'meta' => array(
'test_multi' => array( 'val1' ),
),
);
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$meta = get_post_meta( $this->post_id, 'test_multi', false );
$this->assertNotEmpty( $meta );
$this->assertCount( 1, $meta );
$this->assertEquals( 'val1', $meta[0] );
// Add another value.
$data = array(
'meta' => array(
'test_multi' => array( 'val1', 'val2' ),
),
);
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$meta = get_post_meta( $this->post_id, 'test_multi', false );
$this->assertNotEmpty( $meta );
$this->assertCount( 2, $meta );
$this->assertContains( 'val1', $meta );
$this->assertContains( 'val2', $meta );
}
/**
* Test removing only one item with duplicate items.
*/
public function test_set_value_remove_one() {
add_post_meta( $this->post_id, 'test_multi', 'c' );
add_post_meta( $this->post_id, 'test_multi', 'n' );
add_post_meta( $this->post_id, 'test_multi', 'n' );
$this->grant_write_permission();
$data = array(
'meta' => array(
'test_multi' => array( 'c', 'n' ),
),
);
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$meta = get_post_meta( $this->post_id, 'test_multi', false );
$this->assertNotEmpty( $meta );
$this->assertCount( 2, $meta );
$this->assertContains( 'c', $meta );
$this->assertContains( 'n', $meta );
}
/**
* @depends test_set_value_multiple
*/
public function test_set_value_multiple_unauthenticated() {
// Ensure no data exists currently.
$values = get_post_meta( $this->post_id, 'test_multi', false );
$this->assertEmpty( $values );
wp_set_current_user( 0 );
$data = array(
'meta' => array(
'test_multi' => array( 'val1' ),
),
);
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_edit', $response, 401 );
$meta = get_post_meta( $this->post_id, 'test_multi', false );
$this->assertEmpty( $meta );
}
/**
* @depends test_set_value_multiple
*/
public function test_set_value_multiple_blocked() {
$data = array(
'meta' => array(
'test_bad_auth_multi' => array( 'test_value' ),
),
);
$this->grant_write_permission();
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_update', $response, 403 );
$this->assertEmpty( get_post_meta( $this->post_id, 'test_bad_auth_multi', false ) );
}
public function test_add_multi_value_db_error() {
// Ensure no data exists currently.
$values = get_post_meta( $this->post_id, 'test_multi', false );
$this->assertEmpty( $values );
$this->grant_write_permission();
$data = array(
'meta' => array(
'test_multi' => array( 'val1' ),
),
);
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
/**
* Disable showing error as the below is going to intentionally
* trigger a DB error.
*/
global $wpdb;
$wpdb->suppress_errors = true;
add_filter( 'query', array( $this, 'error_insert_query' ) );
$response = $this->server->dispatch( $request );
remove_filter( 'query', array( $this, 'error_insert_query' ) );
$wpdb->show_errors = true;
$this->assertErrorResponse( 'rest_meta_database_error', $response, 500 );
}
public function test_remove_multi_value_db_error() {
add_post_meta( $this->post_id, 'test_multi', 'val1' );
$values = get_post_meta( $this->post_id, 'test_multi', false );
$this->assertEquals( array( 'val1' ), $values );
$this->grant_write_permission();
$data = array(
'meta' => array(
'test_multi' => array(),
),
);
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
/**
* Disable showing error as the below is going to intentionally
* trigger a DB error.
*/
global $wpdb;
$wpdb->suppress_errors = true;
add_filter( 'query', array( $this, 'error_delete_query' ) );
$response = $this->server->dispatch( $request );
remove_filter( 'query', array( $this, 'error_delete_query' ) );
$wpdb->show_errors = true;
$this->assertErrorResponse( 'rest_meta_database_error', $response, 500 );
}
public function test_delete_value() {
add_post_meta( $this->post_id, 'test_single', 'val1' );
$current = get_post_meta( $this->post_id, 'test_single', true );
$this->assertEquals( 'val1', $current );
$this->grant_write_permission();
$data = array(
'meta' => array(
'test_single' => null,
),
);
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$meta = get_post_meta( $this->post_id, 'test_single', false );
$this->assertEmpty( $meta );
}
/**
* @depends test_delete_value
*/
public function test_delete_value_blocked() {
add_post_meta( $this->post_id, 'test_bad_auth', 'val1' );
$current = get_post_meta( $this->post_id, 'test_bad_auth', true );
$this->assertEquals( 'val1', $current );
$this->grant_write_permission();
$data = array(
'meta' => array(
'test_bad_auth' => null,
),
);
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_delete', $response, 403 );
$meta = get_post_meta( $this->post_id, 'test_bad_auth', true );
$this->assertEquals( 'val1', $meta );
}
/**
* @depends test_delete_value
*/
public function test_delete_value_db_error() {
add_post_meta( $this->post_id, 'test_single', 'val1' );
$current = get_post_meta( $this->post_id, 'test_single', true );
$this->assertEquals( 'val1', $current );
$this->grant_write_permission();
$data = array(
'meta' => array(
'test_single' => null,
),
);
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$request->set_body_params( $data );
/**
* Disable showing error as the below is going to intentionally
* trigger a DB error.
*/
global $wpdb;
$wpdb->suppress_errors = true;
add_filter( 'query', array( $this, 'error_delete_query' ) );
$response = $this->server->dispatch( $request );
remove_filter( 'query', array( $this, 'error_delete_query' ) );
$wpdb->show_errors = true;
$this->assertErrorResponse( 'rest_meta_database_error', $response, 500 );
}
public function test_get_schema() {
$request = new WP_REST_Request( 'OPTIONS', sprintf( '/wp/v2/posts/%d', $this->post_id ) );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$schema = $data['schema'];
$this->assertArrayHasKey( 'meta', $schema['properties'] );
$meta_schema = $schema['properties']['meta']['properties'];
$this->assertArrayHasKey( 'test_single', $meta_schema );
$this->assertEquals( 'string', $meta_schema['test_single']['type'] );
$this->assertArrayHasKey( 'test_multi', $meta_schema );
$this->assertEquals( 'array', $meta_schema['test_multi']['type'] );
$this->assertArrayHasKey( 'items', $meta_schema['test_multi'] );
$this->assertEquals( 'string', $meta_schema['test_multi']['items']['type'] );
$this->assertArrayHasKey( 'test_custom_schema', $meta_schema );
$this->assertEquals( 'number', $meta_schema['test_custom_schema']['type'] );
$this->assertArrayNotHasKey( 'test_no_rest', $meta_schema );
$this->assertArrayNotHasKey( 'test_rest_disabled', $meta_schema );
$this->assertArrayNotHasKey( 'test_invalid_type', $meta_schema );
}
/**
* Internal function used to disable an insert query which
* will trigger a wpdb error for testing purposes.
*/
public function error_insert_query( $query ) {
if ( strpos( $query, 'INSERT' ) === 0 ) {
$query = '],';
}
return $query;
}
/**
* Internal function used to disable an insert query which
* will trigger a wpdb error for testing purposes.
*/
public function error_delete_query( $query ) {
if ( strpos( $query, 'DELETE' ) === 0 ) {
$query = '],';
}
return $query;
}
}

View File

@ -0,0 +1,194 @@
<?php
/**
* Unit tests covering WP_REST_Posts_Statuses_Controller functionality.
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Post_Statuses_Controller extends WP_Test_REST_Controller_Testcase {
public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( '/wp/v2/statuses', $routes );
$this->assertArrayHasKey( '/wp/v2/statuses/(?P<status>[\w-]+)', $routes );
}
public function test_context_param() {
// Collection
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/statuses' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'embed', 'view', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
// Single
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/statuses/publish' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'embed', 'view', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
}
public function test_get_items() {
$request = new WP_REST_Request( 'GET', '/wp/v2/statuses' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$statuses = get_post_stati( array( 'public' => true ), 'objects' );
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( 'publish', $data['publish']['slug'] );
}
public function test_get_items_logged_in() {
$user_id = $this->factory->user->create( array( 'role' => 'author' ) );
wp_set_current_user( $user_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/statuses' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 6, count( $data ) );
$this->assertEqualSets( array(
'publish',
'private',
'pending',
'draft',
'trash',
'future',
), array_keys( $data ) );
}
public function test_get_items_unauthorized_context() {
$request = new WP_REST_Request( 'GET', '/wp/v2/statuses' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_view', $response, 401 );
}
public function test_get_item() {
$user_id = $this->factory->user->create( array( 'role' => 'author' ) );
wp_set_current_user( $user_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/statuses/publish' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->check_post_status_object_response( $response );
}
public function test_get_item_invalid_status() {
$request = new WP_REST_Request( 'GET', '/wp/v2/statuses/invalid' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_status_invalid', $response, 404 );
}
public function test_get_item_invalid_access() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/statuses/draft' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read_status', $response, 401 );
}
public function test_get_item_invalid_internal() {
$user_id = $this->factory->user->create();
wp_set_current_user( $user_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/statuses/inherit' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read_status', $response, 403 );
}
public function test_create_item() {
/** Post statuses can't be created **/
}
public function test_update_item() {
/** Post statuses can't be updated **/
}
public function test_delete_item() {
/** Post statuses can't be deleted **/
}
public function test_prepare_item() {
$obj = get_post_status_object( 'publish' );
$endpoint = new WP_REST_Post_Statuses_Controller;
$request = new WP_REST_Request;
$request->set_param( 'context', 'edit' );
$data = $endpoint->prepare_item_for_response( $obj, $request );
$this->check_post_status_obj( $obj, $data->get_data(), $data->get_links() );
}
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/statuses' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertEquals( 7, count( $properties ) );
$this->assertArrayHasKey( 'name', $properties );
$this->assertArrayHasKey( 'private', $properties );
$this->assertArrayHasKey( 'protected', $properties );
$this->assertArrayHasKey( 'public', $properties );
$this->assertArrayHasKey( 'queryable', $properties );
$this->assertArrayHasKey( 'show_in_list', $properties );
$this->assertArrayHasKey( 'slug', $properties );
}
public function test_get_additional_field_registration() {
$schema = array(
'type' => 'integer',
'description' => 'Some integer of mine',
'enum' => array( 1, 2, 3, 4 ),
'context' => array( 'view', 'edit' ),
);
register_rest_field( 'status', 'my_custom_int', array(
'schema' => $schema,
'get_callback' => array( $this, 'additional_field_get_callback' ),
'update_callback' => array( $this, 'additional_field_update_callback' ),
) );
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/statuses' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
$this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
$request = new WP_REST_Request( 'GET', '/wp/v2/statuses/publish' );
$response = $this->server->dispatch( $request );
$this->assertArrayHasKey( 'my_custom_int', $response->data );
global $wp_rest_additional_fields;
$wp_rest_additional_fields = array();
}
public function additional_field_get_callback( $object ) {
return 123;
}
protected function check_post_status_obj( $status_obj, $data, $links ) {
$this->assertEquals( $status_obj->label, $data['name'] );
$this->assertEquals( $status_obj->private, $data['private'] );
$this->assertEquals( $status_obj->protected, $data['protected'] );
$this->assertEquals( $status_obj->public, $data['public'] );
$this->assertEquals( $status_obj->publicly_queryable, $data['queryable'] );
$this->assertEquals( $status_obj->show_in_admin_all_list, $data['show_in_list'] );
$this->assertEquals( $status_obj->name, $data['slug'] );
$this->assertEqualSets( array(
'archives',
), array_keys( $links ) );
}
protected function check_post_status_object_response( $response ) {
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$obj = get_post_status_object( 'publish' );
$this->check_post_status_obj( $obj, $data, $response->get_links() );
}
}

View File

@ -0,0 +1,182 @@
<?php
/**
* Unit tests covering WP_REST_Posts_Types_Controller functionality.
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Post_Types_Controller extends WP_Test_REST_Controller_Testcase {
public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( '/wp/v2/types', $routes );
$this->assertArrayHasKey( '/wp/v2/types/(?P<type>[\w-]+)', $routes );
}
public function test_context_param() {
// Collection
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/types' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] );
// Single
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/types/post' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] );
}
public function test_get_items() {
$request = new WP_REST_Request( 'GET', '/wp/v2/types' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$post_types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
$this->assertEquals( count( $post_types ), count( $data ) );
$this->assertEquals( $post_types['post']->name, $data['post']['slug'] );
$this->check_post_type_obj( 'view', $post_types['post'], $data['post'], $data['post']['_links'] );
$this->assertEquals( $post_types['page']->name, $data['page']['slug'] );
$this->check_post_type_obj( 'view', $post_types['page'], $data['page'], $data['page']['_links'] );
$this->assertFalse( isset( $data['revision'] ) );
}
public function test_get_items_invalid_permission_for_context() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/types' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_view', $response, 401 );
}
public function test_get_item() {
$request = new WP_REST_Request( 'GET', '/wp/v2/types/post' );
$response = $this->server->dispatch( $request );
$this->check_post_type_object_response( 'view', $response );
}
public function test_get_item_invalid_type() {
$request = new WP_REST_Request( 'GET', '/wp/v2/types/invalid' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_type_invalid', $response, 404 );
}
public function test_get_item_edit_context() {
$editor_id = $this->factory->user->create( array( 'role' => 'editor' ) );
wp_set_current_user( $editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/types/post' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->check_post_type_object_response( 'edit', $response );
}
public function test_get_item_invalid_permission_for_context() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/types/post' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden_context', $response, 401 );
}
public function test_create_item() {
/** Post types can't be created **/
}
public function test_update_item() {
/** Post types can't be updated **/
}
public function test_delete_item() {
/** Post types can't be deleted **/
}
public function test_prepare_item() {
$obj = get_post_type_object( 'post' );
$endpoint = new WP_REST_Post_Types_Controller;
$request = new WP_REST_Request;
$request->set_param( 'context', 'edit' );
$response = $endpoint->prepare_item_for_response( $obj, $request );
$this->check_post_type_obj( 'edit', $obj, $response->get_data(), $response->get_links() );
}
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/types' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertEquals( 6, count( $properties ) );
$this->assertArrayHasKey( 'capabilities', $properties );
$this->assertArrayHasKey( 'description', $properties );
$this->assertArrayHasKey( 'hierarchical', $properties );
$this->assertArrayHasKey( 'labels', $properties );
$this->assertArrayHasKey( 'name', $properties );
$this->assertArrayHasKey( 'slug', $properties );
}
public function test_get_additional_field_registration() {
$schema = array(
'type' => 'integer',
'description' => 'Some integer of mine',
'enum' => array( 1, 2, 3, 4 ),
'context' => array( 'view', 'edit' ),
);
register_rest_field( 'type', 'my_custom_int', array(
'schema' => $schema,
'get_callback' => array( $this, 'additional_field_get_callback' ),
'update_callback' => array( $this, 'additional_field_update_callback' ),
) );
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/types/schema' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
$this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
$request = new WP_REST_Request( 'GET', '/wp/v2/types/post' );
$response = $this->server->dispatch( $request );
$this->assertArrayHasKey( 'my_custom_int', $response->data );
global $wp_rest_additional_fields;
$wp_rest_additional_fields = array();
}
public function additional_field_get_callback( $object ) {
return 123;
}
protected function check_post_type_obj( $context, $post_type_obj, $data, $links ) {
$this->assertEquals( $post_type_obj->label, $data['name'] );
$this->assertEquals( $post_type_obj->name, $data['slug'] );
$this->assertEquals( $post_type_obj->description, $data['description'] );
$this->assertEquals( $post_type_obj->hierarchical, $data['hierarchical'] );
$links = test_rest_expand_compact_links( $links );
$this->assertEquals( rest_url( 'wp/v2/types' ), $links['collection'][0]['href'] );
$this->assertArrayHasKey( 'https://api.w.org/items', $links );
if ( 'edit' === $context ) {
$this->assertEquals( $post_type_obj->cap, $data['capabilities'] );
$this->assertEquals( $post_type_obj->labels, $data['labels'] );
} else {
$this->assertFalse( isset( $data['capabilities'] ) );
$this->assertFalse( isset( $data['labels'] ) );
}
}
protected function check_post_type_object_response( $context, $response ) {
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$obj = get_post_type_object( 'post' );
$this->check_post_type_obj( $context, $obj, $data, $response->get_links() );
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,167 @@
<?php
class WP_Test_REST_Request_Validation extends WP_Test_REST_TestCase {
public function test_validate_within_min_max_range_inclusive() {
$request = new WP_REST_Request( 'GET', '/wp/v2/foo', array(
'args' => array(
'minmaxrange' => array(
'type' => 'integer',
'minimum' => 2,
'maximum' => 10,
),
),
) );
$ret = rest_validate_request_arg( 1, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (inclusive) and 10 (inclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 2, $request, 'minmaxrange' );
$this->assertTrue( $ret );
$ret = rest_validate_request_arg( 10, $request, 'minmaxrange' );
$this->assertTrue( $ret );
$ret = rest_validate_request_arg( 11, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (inclusive) and 10 (inclusive)', $ret->get_error_message() );
}
public function test_validate_within_min_max_range_min_exclusive() {
$request = new WP_REST_Request( 'GET', '/wp/v2/foo', array(
'args' => array(
'minmaxrange' => array(
'type' => 'integer',
'minimum' => 2,
'maximum' => 10,
'exclusiveMinimum' => true,
),
),
) );
$ret = rest_validate_request_arg( 1, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (inclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 2, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (inclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 3, $request, 'minmaxrange' );
$this->assertTrue( $ret );
$ret = rest_validate_request_arg( 9, $request, 'minmaxrange' );
$this->assertTrue( $ret );
$ret = rest_validate_request_arg( 10, $request, 'minmaxrange' );
$this->assertTrue( $ret );
$ret = rest_validate_request_arg( 11, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (inclusive)', $ret->get_error_message() );
}
public function test_validate_within_min_max_range_max_exclusive() {
$request = new WP_REST_Request( 'GET', '/wp/v2/foo', array(
'args' => array(
'minmaxrange' => array(
'type' => 'integer',
'minimum' => 2,
'maximum' => 10,
'exclusiveMaximum' => true,
),
),
) );
$ret = rest_validate_request_arg( 1, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (inclusive) and 10 (exclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 2, $request, 'minmaxrange' );
$this->assertTrue( $ret );
$ret = rest_validate_request_arg( 3, $request, 'minmaxrange' );
$this->assertTrue( $ret );
$ret = rest_validate_request_arg( 9, $request, 'minmaxrange' );
$this->assertTrue( $ret );
$ret = rest_validate_request_arg( 10, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (inclusive) and 10 (exclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 11, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (inclusive) and 10 (exclusive)', $ret->get_error_message() );
}
public function test_validate_within_min_max_range_both_exclusive() {
$request = new WP_REST_Request( 'GET', '/wp/v2/foo', array(
'args' => array(
'minmaxrange' => array(
'type' => 'integer',
'minimum' => 2,
'maximum' => 10,
'exclusiveMinimum' => true,
'exclusiveMaximum' => true,
),
),
) );
$ret = rest_validate_request_arg( 1, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (exclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 2, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (exclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 3, $request, 'minmaxrange' );
$this->assertTrue( $ret );
$ret = rest_validate_request_arg( 9, $request, 'minmaxrange' );
$this->assertTrue( $ret );
$ret = rest_validate_request_arg( 10, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (exclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 11, $request, 'minmaxrange' );
$this->assertEquals( 'minmaxrange must be between 2 (exclusive) and 10 (exclusive)', $ret->get_error_message() );
}
public function test_validate_greater_than_min_inclusive() {
$request = new WP_REST_Request( 'GET', '/wp/v2/foo', array(
'args' => array(
'greaterthanmin' => array(
'type' => 'integer',
'minimum' => 2,
),
),
) );
$ret = rest_validate_request_arg( 1, $request, 'greaterthanmin' );
$this->assertEquals( 'greaterthanmin must be greater than 2 (inclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 2, $request, 'greaterthanmin' );
$this->assertTrue( $ret );
}
public function test_validate_greater_than_min_exclusive() {
$request = new WP_REST_Request( 'GET', '/wp/v2/foo', array(
'args' => array(
'greaterthanmin' => array(
'type' => 'integer',
'minimum' => 2,
'exclusiveMinimum' => true,
),
),
) );
$ret = rest_validate_request_arg( 1, $request, 'greaterthanmin' );
$this->assertEquals( 'greaterthanmin must be greater than 2 (exclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 2, $request, 'greaterthanmin' );
$this->assertEquals( 'greaterthanmin must be greater than 2 (exclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 3, $request, 'greaterthanmin' );
$this->assertTrue( $ret );
}
public function test_validate_less_than_max_inclusive() {
$request = new WP_REST_Request( 'GET', '/wp/v2/foo', array(
'args' => array(
'lessthanmax' => array(
'type' => 'integer',
'maximum' => 10,
),
),
) );
$ret = rest_validate_request_arg( 11, $request, 'lessthanmax' );
$this->assertEquals( 'lessthanmax must be less than 10 (inclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 10, $request, 'lessthanmax' );
$this->assertTrue( $ret );
}
public function test_validate_less_than_max_exclusive() {
$request = new WP_REST_Request( 'GET', '/wp/v2/foo', array(
'args' => array(
'lessthanmax' => array(
'type' => 'integer',
'maximum' => 10,
'exclusiveMaximum' => true,
),
),
) );
$ret = rest_validate_request_arg( 11, $request, 'lessthanmax' );
$this->assertEquals( 'lessthanmax must be less than 10 (exclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 10, $request, 'lessthanmax' );
$this->assertEquals( 'lessthanmax must be less than 10 (exclusive)', $ret->get_error_message() );
$ret = rest_validate_request_arg( 9, $request, 'lessthanmax' );
$this->assertTrue( $ret );
}
}

View File

@ -0,0 +1,301 @@
<?php
/**
* Unit tests covering WP_REST_Revisions_Controller functionality.
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Revisions_Controller extends WP_Test_REST_Controller_Testcase {
public function setUp() {
parent::setUp();
$this->post_id = $this->factory->post->create();
$this->page_id = $this->factory->post->create( array( 'post_type' => 'page' ) );
$this->editor_id = $this->factory->user->create( array(
'role' => 'editor',
) );
$this->contributor_id = $this->factory->user->create( array(
'role' => 'contributor',
) );
wp_update_post( array( 'post_content' => 'This content is better.', 'ID' => $this->post_id ) );
wp_update_post( array( 'post_content' => 'This content is marvelous.', 'ID' => $this->post_id ) );
$revisions = wp_get_post_revisions( $this->post_id );
$this->revision_1 = array_pop( $revisions );
$this->revision_id1 = $this->revision_1->ID;
$this->revision_2 = array_pop( $revisions );
$this->revision_id2 = $this->revision_2->ID;
}
public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( '/wp/v2/posts/(?P<parent>[\d]+)/revisions', $routes );
$this->assertArrayHasKey( '/wp/v2/posts/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)', $routes );
$this->assertArrayHasKey( '/wp/v2/pages/(?P<parent>[\d]+)/revisions', $routes );
$this->assertArrayHasKey( '/wp/v2/pages/(?P<parent>[\d]+)/revisions/(?P<id>[\d]+)', $routes );
}
public function test_context_param() {
// Collection
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . $this->post_id . '/revisions' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] );
// Single
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_1->ID );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] );
}
public function test_get_items() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertCount( 2, $data );
// Reverse chron
$this->assertEquals( $this->revision_id2, $data[0]['id'] );
$this->check_get_revision_response( $data[0], $this->revision_2 );
$this->assertEquals( $this->revision_id1, $data[1]['id'] );
$this->check_get_revision_response( $data[1], $this->revision_1 );
}
public function test_get_items_no_permission() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
wp_set_current_user( $this->contributor_id );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
}
public function test_get_items_missing_parent() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/revisions' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
public function test_get_items_invalid_parent_post_type() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->page_id . '/revisions' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
public function test_get_item() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$this->check_get_revision_response( $response, $this->revision_1 );
$fields = array(
'author',
'date',
'date_gmt',
'modified',
'modified_gmt',
'guid',
'id',
'parent',
'slug',
'title',
'excerpt',
'content',
);
$data = $response->get_data();
$this->assertEqualSets( $fields, array_keys( $data ) );
}
public function test_get_item_embed_context() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 );
$request->set_param( 'context', 'embed' );
$response = $this->server->dispatch( $request );
$fields = array(
'author',
'date',
'id',
'parent',
'slug',
'title',
'excerpt',
);
$data = $response->get_data();
$this->assertEqualSets( $fields, array_keys( $data ) );
}
public function test_get_item_no_permission() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 401 );
wp_set_current_user( $this->contributor_id );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
}
public function test_get_item_missing_parent() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER . '/revisions/' . $this->revision_id1 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
public function test_get_item_invalid_parent_post_type() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->page_id . '/revisions/' . $this->revision_id1 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_post_invalid_parent', $response, 404 );
}
public function test_delete_item() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$this->assertNull( get_post( $this->revision_id1 ) );
}
public function test_delete_item_no_permission() {
wp_set_current_user( $this->contributor_id );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_read', $response, 403 );
}
public function test_prepare_item() {
wp_set_current_user( $this->editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$this->check_get_revision_response( $response, $this->revision_1 );
}
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . $this->post_id . '/revisions' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertEquals( 12, count( $properties ) );
$this->assertArrayHasKey( 'author', $properties );
$this->assertArrayHasKey( 'content', $properties );
$this->assertArrayHasKey( 'date', $properties );
$this->assertArrayHasKey( 'date_gmt', $properties );
$this->assertArrayHasKey( 'excerpt', $properties );
$this->assertArrayHasKey( 'guid', $properties );
$this->assertArrayHasKey( 'id', $properties );
$this->assertArrayHasKey( 'modified', $properties );
$this->assertArrayHasKey( 'modified_gmt', $properties );
$this->assertArrayHasKey( 'parent', $properties );
$this->assertArrayHasKey( 'slug', $properties );
$this->assertArrayHasKey( 'title', $properties );
}
public function test_create_item() {
$request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . $this->post_id . '/revisions' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_update_item() {
$request = new WP_REST_Request( 'POST', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_get_additional_field_registration() {
$schema = array(
'type' => 'integer',
'description' => 'Some integer of mine',
'enum' => array( 1, 2, 3, 4 ),
'context' => array( 'view', 'edit' ),
);
register_rest_field( 'post-revision', 'my_custom_int', array(
'schema' => $schema,
'get_callback' => array( $this, 'additional_field_get_callback' ),
'update_callback' => array( $this, 'additional_field_update_callback' ),
) );
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/posts/' . $this->post_id . '/revisions' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
$this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
wp_set_current_user( 1 );
$request = new WP_REST_Request( 'GET', '/wp/v2/posts/' . $this->post_id . '/revisions/' . $this->revision_id1 );
$response = $this->server->dispatch( $request );
$this->assertArrayHasKey( 'my_custom_int', $response->data );
global $wp_rest_additional_fields;
$wp_rest_additional_fields = array();
}
public function additional_field_get_callback( $object ) {
return get_post_meta( $object['id'], 'my_custom_int', true );
}
public function additional_field_update_callback( $value, $post ) {
update_post_meta( $post->ID, 'my_custom_int', $value );
}
protected function check_get_revision_response( $response, $revision ) {
if ( $response instanceof WP_REST_Response ) {
$links = $response->get_links();
$response = $response->get_data();
} else {
$this->assertArrayHasKey( '_links', $response );
$links = $response['_links'];
}
$this->assertEquals( $revision->post_author, $response['author'] );
$rendered_content = apply_filters( 'the_content', $revision->post_content );
$this->assertEquals( $rendered_content, $response['content']['rendered'] );
$this->assertEquals( mysql_to_rfc3339( $revision->post_date ), $response['date'] );
$this->assertEquals( mysql_to_rfc3339( $revision->post_date_gmt ), $response['date_gmt'] );
$rendered_excerpt = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $revision->post_excerpt, $revision ) );
$this->assertEquals( $rendered_excerpt, $response['excerpt']['rendered'] );
$rendered_guid = apply_filters( 'get_the_guid', $revision->guid );
$this->assertEquals( $rendered_guid, $response['guid']['rendered'] );
$this->assertEquals( $revision->ID, $response['id'] );
$this->assertEquals( mysql_to_rfc3339( $revision->post_modified ), $response['modified'] );
$this->assertEquals( mysql_to_rfc3339( $revision->post_modified_gmt ), $response['modified_gmt'] );
$this->assertEquals( $revision->post_name, $response['slug'] );
$rendered_title = get_the_title( $revision->ID );
$this->assertEquals( $rendered_title, $response['title']['rendered'] );
$parent = get_post( $revision->post_parent );
$parent_controller = new WP_REST_Posts_Controller( $parent->post_type );
$parent_object = get_post_type_object( $parent->post_type );
$parent_base = ! empty( $parent_object->rest_base ) ? $parent_object->rest_base : $parent_object->name;
$this->assertEquals( rest_url( '/wp/v2/' . $parent_base . '/' . $revision->post_parent ), $links['parent'][0]['href'] );
}
}

View File

@ -0,0 +1,239 @@
<?php
/**
* Unit tests covering WP_Test_REST_Settings_Controller functionality.
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Settings_Controller extends WP_Test_REST_Controller_Testcase {
public function setUp() {
parent::setUp();
$this->administrator = $this->factory->user->create( array(
'role' => 'administrator',
) );
$this->endpoint = new WP_REST_Settings_Controller();
}
public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( '/wp/v2/settings', $routes );
}
public function test_get_items() {
}
public function test_context_param() {
}
public function test_get_item_is_not_public() {
$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 403, $response->get_status() );
}
public function test_get_item() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( array(
'title',
'description',
'url',
'email',
'timezone',
'date_format',
'time_format',
'start_of_week',
'language',
'use_smilies',
'default_category',
'default_post_format',
'posts_per_page',
), array_keys( $data ) );
}
public function test_get_item_value_is_cast_to_type() {
wp_set_current_user( $this->administrator );
update_option( 'posts_per_page', 'invalid_number' ); // this is cast to (int) 1
$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 1, $data['posts_per_page'] );
}
public function test_get_item_with_custom_setting() {
wp_set_current_user( $this->administrator );
register_setting( 'somegroup', 'mycustomsetting', array(
'show_in_rest' => array(
'name' => 'mycustomsettinginrest',
'schema' => array(
'enum' => array( 'validvalue1', 'validvalue2' ),
'default' => 'validvalue1',
),
),
'type' => 'string',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertArrayHasKey( 'mycustomsettinginrest', $data );
$this->assertEquals( 'validvalue1', $data['mycustomsettinginrest'] );
update_option( 'mycustomsetting', 'validvalue2' );
$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'validvalue2', $data['mycustomsettinginrest'] );
unregister_setting( 'somegroup', 'mycustomsetting' );
}
public function get_setting_custom_callback( $result, $name, $args ) {
switch ( $name ) {
case 'mycustomsetting1':
return 'filtered1';
}
return $result;
}
public function test_get_item_with_filter() {
wp_set_current_user( $this->administrator );
add_filter( 'rest_pre_get_setting', array( $this, 'get_setting_custom_callback' ), 10, 3 );
register_setting( 'somegroup', 'mycustomsetting1', array(
'show_in_rest' => array(
'name' => 'mycustomsettinginrest1',
),
'type' => 'string',
) );
register_setting( 'somegroup', 'mycustomsetting2', array(
'show_in_rest' => array(
'name' => 'mycustomsettinginrest2',
),
'type' => 'string',
) );
update_option( 'mycustomsetting1', 'unfiltered1' );
update_option( 'mycustomsetting2', 'unfiltered2' );
$request = new WP_REST_Request( 'GET', '/wp/v2/settings' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertArrayHasKey( 'mycustomsettinginrest1', $data );
$this->assertEquals( 'unfiltered1', $data['mycustomsettinginrest1'] );
$this->assertArrayHasKey( 'mycustomsettinginrest2', $data );
$this->assertEquals( 'unfiltered2', $data['mycustomsettinginrest2'] );
unregister_setting( 'somegroup', 'mycustomsetting' );
remove_all_filters( 'rest_pre_get_setting' );
}
public function test_create_item() {
}
public function test_update_item() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
$request->set_param( 'title', 'The new title!' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'The new title!', $data['title'] );
$this->assertEquals( get_option( 'blogname' ), $data['title'] );
}
public function update_setting_custom_callback( $result, $name, $value, $args ) {
if ( 'title' === $name && 'The new title!' === $value ) {
// Do not allow changing the title in this case
return true;
}
return false;
}
public function test_update_item_with_filter() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
$request->set_param( 'title', 'The old title!' );
$request->set_param( 'description', 'The old description!' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'The old title!', $data['title'] );
$this->assertEquals( 'The old description!', $data['description'] );
$this->assertEquals( get_option( 'blogname' ), $data['title'] );
$this->assertEquals( get_option( 'blogdescription' ), $data['description'] );
add_filter( 'rest_pre_update_setting', array( $this, 'update_setting_custom_callback' ), 10, 4 );
$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
$request->set_param( 'title', 'The new title!' );
$request->set_param( 'description', 'The new description!' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'The old title!', $data['title'] );
$this->assertEquals( 'The new description!', $data['description'] );
$this->assertEquals( get_option( 'blogname' ), $data['title'] );
$this->assertEquals( get_option( 'blogdescription' ), $data['description'] );
remove_all_filters( 'rest_pre_update_setting' );
}
public function test_update_item_with_invalid_type() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
$request->set_param( 'title', array( 'rendered' => 'This should fail.' ) );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
}
/**
* Setting an item to "null" will essentially restore it to it's default value.
*/
public function test_update_item_with_null() {
update_option( 'posts_per_page', 9 );
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'PUT', '/wp/v2/settings' );
$request->set_param( 'posts_per_page', null );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 10, $data['posts_per_page'] );
}
public function test_delete_item() {
}
public function test_prepare_item() {
}
public function test_get_item_schema() {
}
}

View File

@ -0,0 +1,738 @@
<?php
/**
* Unit tests covering WP_REST_Terms_Controller functionality, used for Tags.
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Tags_Controller extends WP_Test_REST_Controller_Testcase {
public function setUp() {
parent::setUp();
$this->administrator = $this->factory->user->create( array(
'role' => 'administrator',
) );
$this->subscriber = $this->factory->user->create( array(
'role' => 'subscriber',
) );
}
public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( '/wp/v2/tags', $routes );
$this->assertArrayHasKey( '/wp/v2/tags/(?P<id>[\d]+)', $routes );
}
public function test_context_param() {
// Collection
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
// Single
$tag1 = $this->factory->tag->create( array( 'name' => 'Season 5' ) );
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags/' . $tag1 );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'view', 'embed', 'edit' ), $data['endpoints'][0]['args']['context']['enum'] );
}
public function test_registered_query_params() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$keys = array_keys( $data['endpoints'][0]['args'] );
sort( $keys );
$this->assertEquals( array(
'context',
'exclude',
'hide_empty',
'include',
'offset',
'order',
'orderby',
'page',
'per_page',
'post',
'search',
'slug',
), $keys );
}
public function test_get_items() {
$this->factory->tag->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$response = $this->server->dispatch( $request );
$this->check_get_taxonomy_terms_response( $response );
}
public function test_get_items_invalid_permission_for_context() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden_context', $response, 401 );
}
public function test_get_items_hide_empty_arg() {
$post_id = $this->factory->post->create();
$tag1 = $this->factory->tag->create( array( 'name' => 'Season 5' ) );
$tag2 = $this->factory->tag->create( array( 'name' => 'The Be Sharps' ) );
wp_set_object_terms( $post_id, array( $tag1, $tag2 ), 'post_tag' );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'hide_empty', true );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( 'Season 5', $data[0]['name'] );
$this->assertEquals( 'The Be Sharps', $data[1]['name'] );
}
public function test_get_items_include_query() {
$id1 = $this->factory->tag->create();
$id2 = $this->factory->tag->create();
$id3 = $this->factory->tag->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
// Orderby=>asc
$request->set_param( 'include', array( $id3, $id1 ) );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( $id1, $data[0]['id'] );
// Orderby=>include
$request->set_param( 'orderby', 'include' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( $id3, $data[0]['id'] );
}
public function test_get_items_exclude_query() {
$id1 = $this->factory->tag->create();
$id2 = $this->factory->tag->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) );
$this->assertTrue( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) );
$request->set_param( 'exclude', array( $id2 ) );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertTrue( in_array( $id1, wp_list_pluck( $data, 'id' ), true ) );
$this->assertFalse( in_array( $id2, wp_list_pluck( $data, 'id' ), true ) );
}
public function test_get_items_offset_query() {
$id1 = $this->factory->tag->create();
$id2 = $this->factory->tag->create();
$id3 = $this->factory->tag->create();
$id4 = $this->factory->tag->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'offset', 1 );
$response = $this->server->dispatch( $request );
$this->assertCount( 3, $response->get_data() );
// 'offset' works with 'per_page'
$request->set_param( 'per_page', 2 );
$response = $this->server->dispatch( $request );
$this->assertCount( 2, $response->get_data() );
// 'offset' takes priority over 'page'
$request->set_param( 'page', 3 );
$response = $this->server->dispatch( $request );
$this->assertCount( 2, $response->get_data() );
}
public function test_get_items_orderby_args() {
$tag1 = $this->factory->tag->create( array( 'name' => 'Apple' ) );
$tag2 = $this->factory->tag->create( array( 'name' => 'Banana' ) );
/*
* Tests:
* - orderby
* - order
* - per_page
*/
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'orderby', 'name' );
$request->set_param( 'order', 'desc' );
$request->set_param( 'per_page', 1 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( 'Banana', $data[0]['name'] );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'orderby', 'name' );
$request->set_param( 'order', 'asc' );
$request->set_param( 'per_page', 2 );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( 'Apple', $data[0]['name'] );
}
public function test_get_items_orderby_id() {
$tag0 = $this->factory->tag->create( array( 'name' => 'Cantaloupe' ) );
$tag1 = $this->factory->tag->create( array( 'name' => 'Apple' ) );
$tag2 = $this->factory->tag->create( array( 'name' => 'Banana' ) );
// defaults to orderby=name, order=asc
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'Apple', $data[0]['name'] );
$this->assertEquals( 'Banana', $data[1]['name'] );
$this->assertEquals( 'Cantaloupe', $data[2]['name'] );
// orderby=id, with default order=asc
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'orderby', 'id' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'Cantaloupe', $data[0]['name'] );
$this->assertEquals( 'Apple', $data[1]['name'] );
$this->assertEquals( 'Banana', $data[2]['name'] );
// orderby=id, order=desc
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'orderby', 'id' );
$request->set_param( 'order', 'desc' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 200, $response->get_status() );
$this->assertEquals( 'Banana', $data[0]['name'] );
$this->assertEquals( 'Apple', $data[1]['name'] );
$this->assertEquals( 'Cantaloupe', $data[2]['name'] );
}
public function test_get_items_post_args() {
$post_id = $this->factory->post->create();
$tag1 = $this->factory->tag->create( array( 'name' => 'DC' ) );
$tag2 = $this->factory->tag->create( array( 'name' => 'Marvel' ) );
$this->factory->tag->create( array( 'name' => 'Dark Horse' ) );
wp_set_object_terms( $post_id, array( $tag1, $tag2 ), 'post_tag' );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'post', $post_id );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( 'DC', $data[0]['name'] );
}
public function test_get_terms_post_args_paging() {
$post_id = $this->factory->post->create();
$tag_ids = array();
for ( $i = 0; $i < 30; $i++ ) {
$tag_ids[] = $this->factory->tag->create( array(
'name' => "Tag {$i}",
) );
}
wp_set_object_terms( $post_id, $tag_ids, 'post_tag' );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'post', $post_id );
$request->set_param( 'page', 1 );
$request->set_param( 'per_page', 15 );
$request->set_param( 'orderby', 'id' );
$response = $this->server->dispatch( $request );
$tags = $response->get_data();
$i = 0;
foreach ( $tags as $tag ) {
$this->assertEquals( $tag['name'], "Tag {$i}" );
$i++;
}
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'post', $post_id );
$request->set_param( 'page', 2 );
$request->set_param( 'per_page', 15 );
$request->set_param( 'orderby', 'id' );
$response = $this->server->dispatch( $request );
$tags = $response->get_data();
foreach ( $tags as $tag ) {
$this->assertEquals( $tag['name'], "Tag {$i}" );
$i++;
}
}
public function test_get_items_post_empty() {
$post_id = $this->factory->post->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'post', $post_id );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertCount( 0, $data );
}
public function test_get_items_custom_tax_post_args() {
register_taxonomy( 'batman', 'post', array( 'show_in_rest' => true ) );
$controller = new WP_REST_Terms_Controller( 'batman' );
$controller->register_routes();
$term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'batman' ) );
$term2 = $this->factory->term->create( array( 'name' => 'Mask', 'taxonomy' => 'batman' ) );
$this->factory->term->create( array( 'name' => 'Car', 'taxonomy' => 'batman' ) );
$post_id = $this->factory->post->create();
wp_set_object_terms( $post_id, array( $term1, $term2 ), 'batman' );
$request = new WP_REST_Request( 'GET', '/wp/v2/batman' );
$request->set_param( 'post', $post_id );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 2, count( $data ) );
$this->assertEquals( 'Cape', $data[0]['name'] );
}
public function test_get_items_search_args() {
$tag1 = $this->factory->tag->create( array( 'name' => 'Apple' ) );
$tag2 = $this->factory->tag->create( array( 'name' => 'Banana' ) );
/*
* Tests:
* - search
*/
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'search', 'App' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( 'Apple', $data[0]['name'] );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'search', 'Garbage' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 0, count( $data ) );
}
public function test_get_items_slug_arg() {
$tag1 = $this->factory->tag->create( array( 'name' => 'Apple' ) );
$tag2 = $this->factory->tag->create( array( 'name' => 'Banana' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'slug', 'apple' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 1, count( $data ) );
$this->assertEquals( 'Apple', $data[0]['name'] );
}
public function test_get_terms_private_taxonomy() {
register_taxonomy( 'robin', 'post', array( 'public' => false ) );
$term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) );
$term2 = $this->factory->term->create( array( 'name' => 'Mask', 'taxonomy' => 'robin' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/terms/robin' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_get_terms_pagination_headers() {
// Start of the index
for ( $i = 0; $i < 50; $i++ ) {
$this->factory->tag->create( array(
'name' => "Tag {$i}",
) );
}
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$response = $this->server->dispatch( $request );
$headers = $response->get_headers();
$this->assertEquals( 50, $headers['X-WP-Total'] );
$this->assertEquals( 5, $headers['X-WP-TotalPages'] );
$next_link = add_query_arg( array(
'page' => 2,
), rest_url( 'wp/v2/tags' ) );
$this->assertFalse( stripos( $headers['Link'], 'rel="prev"' ) );
$this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] );
// 3rd page
$this->factory->tag->create( array(
'name' => 'Tag 51',
) );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'page', 3 );
$response = $this->server->dispatch( $request );
$headers = $response->get_headers();
$this->assertEquals( 51, $headers['X-WP-Total'] );
$this->assertEquals( 6, $headers['X-WP-TotalPages'] );
$prev_link = add_query_arg( array(
'page' => 2,
), rest_url( 'wp/v2/tags' ) );
$this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] );
$next_link = add_query_arg( array(
'page' => 4,
), rest_url( 'wp/v2/tags' ) );
$this->assertContains( '<' . $next_link . '>; rel="next"', $headers['Link'] );
// Last page
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'page', 6 );
$response = $this->server->dispatch( $request );
$headers = $response->get_headers();
$this->assertEquals( 51, $headers['X-WP-Total'] );
$this->assertEquals( 6, $headers['X-WP-TotalPages'] );
$prev_link = add_query_arg( array(
'page' => 5,
), rest_url( 'wp/v2/tags' ) );
$this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] );
$this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) );
// Out of bounds
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'page', 8 );
$response = $this->server->dispatch( $request );
$headers = $response->get_headers();
$this->assertEquals( 51, $headers['X-WP-Total'] );
$this->assertEquals( 6, $headers['X-WP-TotalPages'] );
$prev_link = add_query_arg( array(
'page' => 6,
), rest_url( 'wp/v2/tags' ) );
$this->assertContains( '<' . $prev_link . '>; rel="prev"', $headers['Link'] );
$this->assertFalse( stripos( $headers['Link'], 'rel="next"' ) );
}
public function test_get_items_invalid_context() {
$request = new WP_REST_Request( 'GET', '/wp/v2/tags' );
$request->set_param( 'context', 'banana' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
}
public function test_get_item() {
$id = $this->factory->tag->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . $id );
$response = $this->server->dispatch( $request );
$this->check_get_taxonomy_term_response( $response, $id );
}
public function test_get_term_invalid_term() {
$request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_term_invalid', $response, 404 );
}
public function test_get_item_invalid_permission_for_context() {
$id = $this->factory->tag->create();
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . $id );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden_context', $response, 401 );
}
public function test_get_term_private_taxonomy() {
register_taxonomy( 'robin', 'post', array( 'public' => false ) );
$term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/terms/robin/' . $term1 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_no_route', $response, 404 );
}
public function test_get_item_incorrect_taxonomy() {
register_taxonomy( 'robin', 'post' );
$term1 = $this->factory->term->create( array( 'name' => 'Cape', 'taxonomy' => 'robin' ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . $term1 );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_term_invalid', $response, 404 );
}
public function test_create_item() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'POST', '/wp/v2/tags' );
$request->set_param( 'name', 'My Awesome Term' );
$request->set_param( 'description', 'This term is so awesome.' );
$request->set_param( 'slug', 'so-awesome' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 201, $response->get_status() );
$headers = $response->get_headers();
$data = $response->get_data();
$this->assertContains( '/wp/v2/tags/' . $data['id'], $headers['Location'] );
$this->assertEquals( 'My Awesome Term', $data['name'] );
$this->assertEquals( 'This term is so awesome.', $data['description'] );
$this->assertEquals( 'so-awesome', $data['slug'] );
}
public function test_create_item_incorrect_permissions() {
wp_set_current_user( $this->subscriber );
$request = new WP_REST_Request( 'POST', '/wp/v2/tags' );
$request->set_param( 'name', 'Incorrect permissions' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_create', $response, 403 );
}
public function test_create_item_missing_arguments() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'POST', '/wp/v2/tags' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_missing_callback_param', $response, 400 );
}
public function test_create_item_parent_non_hierarchical_taxonomy() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'POST', '/wp/v2/tags' );
$request->set_param( 'name', 'My Awesome Term' );
$request->set_param( 'parent', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_taxonomy_not_hierarchical', $response, 400 );
}
public function test_update_item() {
wp_set_current_user( $this->administrator );
$orig_args = array(
'name' => 'Original Name',
'description' => 'Original Description',
'slug' => 'original-slug',
);
$term = get_term_by( 'id', $this->factory->tag->create( $orig_args ), 'post_tag' );
$request = new WP_REST_Request( 'POST', '/wp/v2/tags/' . $term->term_id );
$request->set_param( 'name', 'New Name' );
$request->set_param( 'description', 'New Description' );
$request->set_param( 'slug', 'new-slug' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'New Name', $data['name'] );
$this->assertEquals( 'New Description', $data['description'] );
$this->assertEquals( 'new-slug', $data['slug'] );
}
public function test_update_item_invalid_term() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'POST', '/wp/v2/tags/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$request->set_param( 'name', 'Invalid Term' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_term_invalid', $response, 404 );
}
public function test_update_item_incorrect_permissions() {
wp_set_current_user( $this->subscriber );
$term = get_term_by( 'id', $this->factory->tag->create(), 'post_tag' );
$request = new WP_REST_Request( 'POST', '/wp/v2/tags/' . $term->term_id );
$request->set_param( 'name', 'Incorrect permissions' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_update', $response, 403 );
}
public function test_update_item_parent_non_hierarchical_taxonomy() {
wp_set_current_user( $this->administrator );
$term = get_term_by( 'id', $this->factory->tag->create(), 'post_tag' );
$request = new WP_REST_Request( 'POST', '/wp/v2/tags/' . $term->term_taxonomy_id );
$request->set_param( 'parent', REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_taxonomy_not_hierarchical', $response, 400 );
}
public function test_delete_item() {
wp_set_current_user( $this->administrator );
$term = get_term_by( 'id', $this->factory->tag->create( array( 'name' => 'Deleted Tag' ) ), 'post_tag' );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/tags/' . $term->term_id );
$request->set_param( 'force', true );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( 'Deleted Tag', $data['name'] );
}
public function test_delete_item_force_false() {
wp_set_current_user( $this->administrator );
$term = get_term_by( 'id', $this->factory->tag->create( array( 'name' => 'Deleted Tag' ) ), 'post_tag' );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/tags/' . $term->term_id );
// force defaults to false
$response = $this->server->dispatch( $request );
$this->assertEquals( 501, $response->get_status() );
}
public function test_delete_item_invalid_term() {
wp_set_current_user( $this->administrator );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/tags/' . REST_TESTS_IMPOSSIBLY_HIGH_NUMBER );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_term_invalid', $response, 404 );
}
public function test_delete_item_incorrect_permissions() {
wp_set_current_user( $this->subscriber );
$term = get_term_by( 'id', $this->factory->tag->create(), 'post_tag' );
$request = new WP_REST_Request( 'DELETE', '/wp/v2/tags/' . $term->term_id );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_delete', $response, 403 );
}
public function test_prepare_item() {
$term = get_term_by( 'id', $this->factory->tag->create(), 'post_tag' );
$request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . $term->term_id );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->check_taxonomy_term( $term, $data, $response->get_links() );
}
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertEquals( 8, count( $properties ) );
$this->assertArrayHasKey( 'id', $properties );
$this->assertArrayHasKey( 'count', $properties );
$this->assertArrayHasKey( 'description', $properties );
$this->assertArrayHasKey( 'link', $properties );
$this->assertArrayHasKey( 'meta', $properties );
$this->assertArrayHasKey( 'name', $properties );
$this->assertArrayHasKey( 'slug', $properties );
$this->assertArrayHasKey( 'taxonomy', $properties );
$this->assertEquals( array_keys( get_taxonomies() ), $properties['taxonomy']['enum'] );
}
public function test_get_item_schema_non_hierarchical() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertArrayHasKey( 'id', $properties );
$this->assertFalse( isset( $properties['parent'] ) );
}
public function test_get_additional_field_registration() {
$schema = array(
'type' => 'integer',
'description' => 'Some integer of mine',
'enum' => array( 1, 2, 3, 4 ),
'context' => array( 'view', 'edit' ),
);
register_rest_field( 'tag', 'my_custom_int', array(
'schema' => $schema,
'get_callback' => array( $this, 'additional_field_get_callback' ),
) );
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/tags' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertArrayHasKey( 'my_custom_int', $data['schema']['properties'] );
$this->assertEquals( $schema, $data['schema']['properties']['my_custom_int'] );
$tag_id = $this->factory->tag->create();
$request = new WP_REST_Request( 'GET', '/wp/v2/tags/' . $tag_id );
$response = $this->server->dispatch( $request );
$this->assertArrayHasKey( 'my_custom_int', $response->data );
global $wp_rest_additional_fields;
$wp_rest_additional_fields = array();
}
public function test_additional_field_update_errors() {
$schema = array(
'type' => 'integer',
'description' => 'Some integer of mine',
'enum' => array( 1, 2, 3, 4 ),
'context' => array( 'view', 'edit' ),
);
register_rest_field( 'tag', 'my_custom_int', array(
'schema' => $schema,
'get_callback' => array( $this, 'additional_field_get_callback' ),
'update_callback' => array( $this, 'additional_field_update_callback' ),
) );
wp_set_current_user( $this->administrator );
$tag_id = $this->factory->tag->create();
// Check for error on update.
$request = new WP_REST_Request( 'POST', sprintf( '/wp/v2/tags/%d', $tag_id ) );
$request->set_body_params( array(
'my_custom_int' => 'returnError',
) );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_invalid_param', $response, 400 );
global $wp_rest_additional_fields;
$wp_rest_additional_fields = array();
}
public function additional_field_get_callback( $object, $request ) {
return 123;
}
public function additional_field_update_callback( $value, $tag ) {
if ( 'returnError' === $value ) {
return new WP_Error( 'rest_invalid_param', 'Testing an error.', array( 'status' => 400 ) );
}
}
public function tearDown() {
_unregister_taxonomy( 'batman' );
_unregister_taxonomy( 'robin' );
parent::tearDown();
}
protected function check_get_taxonomy_terms_response( $response ) {
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$args = array(
'hide_empty' => false,
);
$tags = get_terms( 'post_tag', $args );
$this->assertEquals( count( $tags ), count( $data ) );
$this->assertEquals( $tags[0]->term_id, $data[0]['id'] );
$this->assertEquals( $tags[0]->name, $data[0]['name'] );
$this->assertEquals( $tags[0]->slug, $data[0]['slug'] );
$this->assertEquals( $tags[0]->taxonomy, $data[0]['taxonomy'] );
$this->assertEquals( $tags[0]->description, $data[0]['description'] );
$this->assertEquals( $tags[0]->count, $data[0]['count'] );
}
protected function check_taxonomy_term( $term, $data, $links ) {
$this->assertEquals( $term->term_id, $data['id'] );
$this->assertEquals( $term->name, $data['name'] );
$this->assertEquals( $term->slug, $data['slug'] );
$this->assertEquals( $term->description, $data['description'] );
$this->assertEquals( get_term_link( $term ), $data['link'] );
$this->assertEquals( $term->count, $data['count'] );
$taxonomy = get_taxonomy( $term->taxonomy );
if ( $taxonomy->hierarchical ) {
$this->assertEquals( $term->parent, $data['parent'] );
} else {
$this->assertFalse( isset( $data['parent'] ) );
}
$expected_links = array(
'self',
'collection',
'about',
'https://api.w.org/post_type',
);
if ( $taxonomy->hierarchical && $term->parent ) {
$expected_links[] = 'up';
}
$this->assertEqualSets( $expected_links, array_keys( $links ) );
$this->assertContains( 'wp/v2/taxonomies/' . $term->taxonomy, $links['about'][0]['href'] );
$this->assertEquals( add_query_arg( 'tags', $term->term_id, rest_url( 'wp/v2/posts' ) ), $links['https://api.w.org/post_type'][0]['href'] );
}
protected function check_get_taxonomy_term_response( $response, $id ) {
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$tag = get_term( $id, 'post_tag' );
$this->check_taxonomy_term( $tag, $data, $response->get_links() );
}
}

View File

@ -0,0 +1,198 @@
<?php
/**
* Unit tests covering WP_REST_Taxonomies_Controller functionality.
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_Test_REST_Taxonomies_Controller extends WP_Test_REST_Controller_Testcase {
public function test_register_routes() {
$routes = $this->server->get_routes();
$this->assertArrayHasKey( '/wp/v2/taxonomies', $routes );
$this->assertArrayHasKey( '/wp/v2/taxonomies/(?P<taxonomy>[\w-]+)', $routes );
}
public function test_context_param() {
// Collection
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/taxonomies' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] );
// Single
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/taxonomies/post_tag' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$this->assertEquals( 'view', $data['endpoints'][0]['args']['context']['default'] );
$this->assertEqualSets( array( 'view', 'edit', 'embed' ), $data['endpoints'][0]['args']['context']['enum'] );
}
public function test_get_items() {
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$taxonomies = $this->get_public_taxonomies( get_taxonomies( '', 'objects' ) );
$this->assertEquals( count( $taxonomies ), count( $data ) );
$this->assertEquals( 'Categories', $data['category']['name'] );
$this->assertEquals( 'category', $data['category']['slug'] );
$this->assertEquals( true, $data['category']['hierarchical'] );
$this->assertEquals( 'Tags', $data['post_tag']['name'] );
$this->assertEquals( 'post_tag', $data['post_tag']['slug'] );
$this->assertEquals( false, $data['post_tag']['hierarchical'] );
}
public function test_get_items_invalid_permission_for_context() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_cannot_view', $response, 401 );
}
public function test_get_taxonomies_for_type() {
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
$request->set_param( 'type', 'post' );
$response = $this->server->dispatch( $request );
$this->check_taxonomies_for_type_response( 'post', $response );
}
public function test_get_taxonomies_for_invalid_type() {
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies' );
$request->set_param( 'type', 'wingding' );
$response = $this->server->dispatch( $request );
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$this->assertEquals( '{}', json_encode( $data ) );
}
public function test_get_item() {
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' );
$response = $this->server->dispatch( $request );
$this->check_taxonomy_object_response( 'view', $response );
}
public function test_get_item_edit_context() {
$editor_id = $this->factory->user->create( array( 'role' => 'editor' ) );
wp_set_current_user( $editor_id );
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->check_taxonomy_object_response( 'edit', $response );
}
public function test_get_item_invalid_permission_for_context() {
wp_set_current_user( 0 );
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/category' );
$request->set_param( 'context', 'edit' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden_context', $response, 401 );
}
public function test_get_invalid_taxonomy() {
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/invalid' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_taxonomy_invalid', $response, 404 );
}
public function test_get_non_public_taxonomy() {
register_taxonomy( 'api-private', 'post', array( 'public' => false ) );
$request = new WP_REST_Request( 'GET', '/wp/v2/taxonomies/api-private' );
$response = $this->server->dispatch( $request );
$this->assertErrorResponse( 'rest_forbidden', $response, 403 );
}
public function test_create_item() {
/** Taxonomies can't be created **/
}
public function test_update_item() {
/** Taxonomies can't be updated **/
}
public function test_delete_item() {
/** Taxonomies can't be deleted **/
}
public function test_prepare_item() {
$tax = get_taxonomy( 'category' );
$endpoint = new WP_REST_Taxonomies_Controller;
$request = new WP_REST_Request;
$request->set_param( 'context', 'edit' );
$response = $endpoint->prepare_item_for_response( $tax, $request );
$this->check_taxonomy_object( 'edit', $tax, $response->get_data(), $response->get_links() );
}
public function test_get_item_schema() {
$request = new WP_REST_Request( 'OPTIONS', '/wp/v2/taxonomies' );
$response = $this->server->dispatch( $request );
$data = $response->get_data();
$properties = $data['schema']['properties'];
$this->assertEquals( 8, count( $properties ) );
$this->assertArrayHasKey( 'capabilities', $properties );
$this->assertArrayHasKey( 'description', $properties );
$this->assertArrayHasKey( 'hierarchical', $properties );
$this->assertArrayHasKey( 'labels', $properties );
$this->assertArrayHasKey( 'name', $properties );
$this->assertArrayHasKey( 'slug', $properties );
$this->assertArrayHasKey( 'show_cloud', $properties );
$this->assertArrayHasKey( 'types', $properties );
}
public function tearDown() {
parent::tearDown();
}
/**
* Utility function for use in get_public_taxonomies
*/
private function is_public( $taxonomy ) {
return ! empty( $taxonomy->show_in_rest );
}
/**
* Utility function to filter down to only public taxonomies
*/
private function get_public_taxonomies( $taxonomies ) {
// Pass through array_values to re-index after filtering
return array_values( array_filter( $taxonomies, array( $this, 'is_public' ) ) );
}
protected function check_taxonomy_object( $context, $tax_obj, $data, $links ) {
$this->assertEquals( $tax_obj->label, $data['name'] );
$this->assertEquals( $tax_obj->name, $data['slug'] );
$this->assertEquals( $tax_obj->description, $data['description'] );
$this->assertEquals( $tax_obj->hierarchical, $data['hierarchical'] );
$this->assertEquals( rest_url( 'wp/v2/taxonomies' ), $links['collection'][0]['href'] );
$this->assertArrayHasKey( 'https://api.w.org/items', $links );
if ( 'edit' === $context ) {
$this->assertEquals( $tax_obj->cap, $data['capabilities'] );
$this->assertEquals( $tax_obj->labels, $data['labels'] );
$this->assertEquals( $tax_obj->show_tagcloud, $data['show_cloud'] );
} else {
$this->assertFalse( isset( $data['capabilities'] ) );
$this->assertFalse( isset( $data['labels'] ) );
$this->assertFalse( isset( $data['show_cloud'] ) );
}
}
protected function check_taxonomy_object_response( $context, $response ) {
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$category = get_taxonomy( 'category' );
$this->check_taxonomy_object( $context, $category, $data, $response->get_links() );
}
protected function check_taxonomies_for_type_response( $type, $response ) {
$this->assertEquals( 200, $response->get_status() );
$data = $response->get_data();
$taxonomies = $this->get_public_taxonomies( get_object_taxonomies( $type, 'objects' ) );
$this->assertEquals( count( $taxonomies ), count( $data ) );
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* Unit tests covering WP_REST_Controller functionality
*
* @package WordPress
* @subpackage REST API
*/
/**
* @group restapi
*/
class WP_REST_Test_Controller extends WP_REST_Controller {
/**
* Get the Post type's schema, conforming to JSON Schema
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'type',
'type' => 'object',
'properties' => array(
'somestring' => array(
'type' => 'string',
'description' => 'A pretty string.',
'context' => array( 'view' ),
),
'someinteger' => array(
'type' => 'integer',
'context' => array( 'view' ),
),
'someboolean' => array(
'type' => 'boolean',
'context' => array( 'view' ),
),
'someurl' => array(
'type' => 'string',
'format' => 'uri',
'context' => array( 'view' ),
),
'somedate' => array(
'type' => 'string',
'format' => 'date-time',
'context' => array( 'view' ),
),
'someemail' => array(
'type' => 'string',
'format' => 'email',
'context' => array( 'view' ),
),
'someenum' => array(
'type' => 'string',
'enum' => array( 'a', 'b', 'c' ),
'context' => array( 'view' ),
),
'someargoptions' => array(
'type' => 'integer',
'required' => true,
'arg_options' => array(
'required' => false,
'sanitize_callback' => '__return_true',
),
),
'somedefault' => array(
'type' => 'string',
'enum' => array( 'a', 'b', 'c' ),
'context' => array( 'view' ),
'default' => 'a',
),
),
);
return $schema;
}
}

File diff suppressed because it is too large Load Diff