PDF rausgenommen
This commit is contained in:
1002
msd2/wordpress/wp-includes/rest-api/class-wp-rest-request.php
Normal file
1002
msd2/wordpress/wp-includes/rest-api/class-wp-rest-request.php
Normal file
File diff suppressed because it is too large
Load Diff
290
msd2/wordpress/wp-includes/rest-api/class-wp-rest-response.php
Normal file
290
msd2/wordpress/wp-includes/rest-api/class-wp-rest-response.php
Normal file
@ -0,0 +1,290 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Response class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.4.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to implement a REST response object.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @see WP_HTTP_Response
|
||||
*/
|
||||
class WP_REST_Response extends WP_HTTP_Response {
|
||||
|
||||
/**
|
||||
* Links related to the response.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @var array
|
||||
*/
|
||||
protected $links = array();
|
||||
|
||||
/**
|
||||
* The route that was to create the response.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @var string
|
||||
*/
|
||||
protected $matched_route = '';
|
||||
|
||||
/**
|
||||
* The handler that was used to create the response.
|
||||
*
|
||||
* @since 4.4.0
|
||||
* @var null|array
|
||||
*/
|
||||
protected $matched_handler = null;
|
||||
|
||||
/**
|
||||
* Adds a link to the response.
|
||||
*
|
||||
* @internal The $rel parameter is first, as this looks nicer when sending multiple.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc5988
|
||||
* @link https://www.iana.org/assignments/link-relations/link-relations.xml
|
||||
*
|
||||
* @param string $rel Link relation. Either an IANA registered type,
|
||||
* or an absolute URL.
|
||||
* @param string $href Target URI for the link.
|
||||
* @param array $attributes Optional. Link parameters to send along with the URL. Default empty array.
|
||||
*/
|
||||
public function add_link( $rel, $href, $attributes = array() ) {
|
||||
if ( empty( $this->links[ $rel ] ) ) {
|
||||
$this->links[ $rel ] = array();
|
||||
}
|
||||
|
||||
if ( isset( $attributes['href'] ) ) {
|
||||
// Remove the href attribute, as it's used for the main URL.
|
||||
unset( $attributes['href'] );
|
||||
}
|
||||
|
||||
$this->links[ $rel ][] = array(
|
||||
'href' => $href,
|
||||
'attributes' => $attributes,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a link from the response.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param string $rel Link relation. Either an IANA registered type, or an absolute URL.
|
||||
* @param string $href Optional. Only remove links for the relation matching the given href.
|
||||
* Default null.
|
||||
*/
|
||||
public function remove_link( $rel, $href = null ) {
|
||||
if ( ! isset( $this->links[ $rel ] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $href ) {
|
||||
$this->links[ $rel ] = wp_list_filter( $this->links[ $rel ], array( 'href' => $href ), 'NOT' );
|
||||
} else {
|
||||
$this->links[ $rel ] = array();
|
||||
}
|
||||
|
||||
if ( ! $this->links[ $rel ] ) {
|
||||
unset( $this->links[ $rel ] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds multiple links to the response.
|
||||
*
|
||||
* Link data should be an associative array with link relation as the key.
|
||||
* The value can either be an associative array of link attributes
|
||||
* (including `href` with the URL for the response), or a list of these
|
||||
* associative arrays.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param array $links Map of link relation to list of links.
|
||||
*/
|
||||
public function add_links( $links ) {
|
||||
foreach ( $links as $rel => $set ) {
|
||||
// If it's a single link, wrap with an array for consistent handling.
|
||||
if ( isset( $set['href'] ) ) {
|
||||
$set = array( $set );
|
||||
}
|
||||
|
||||
foreach ( $set as $attributes ) {
|
||||
$this->add_link( $rel, $attributes['href'], $attributes );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves links for the response.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @return array List of links.
|
||||
*/
|
||||
public function get_links() {
|
||||
return $this->links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a single link header.
|
||||
*
|
||||
* @internal The $rel parameter is first, as this looks nicer when sending multiple.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @link https://tools.ietf.org/html/rfc5988
|
||||
* @link https://www.iana.org/assignments/link-relations/link-relations.xml
|
||||
*
|
||||
* @param string $rel Link relation. Either an IANA registered type, or an absolute URL.
|
||||
* @param string $link Target IRI for the link.
|
||||
* @param array $other Optional. Other parameters to send, as an assocative array.
|
||||
* Default empty array.
|
||||
*/
|
||||
public function link_header( $rel, $link, $other = array() ) {
|
||||
$header = '<' . $link . '>; rel="' . $rel . '"';
|
||||
|
||||
foreach ( $other as $key => $value ) {
|
||||
if ( 'title' === $key ) {
|
||||
$value = '"' . $value . '"';
|
||||
}
|
||||
$header .= '; ' . $key . '=' . $value;
|
||||
}
|
||||
$this->header( 'Link', $header, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the route that was used.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @return string The matched route.
|
||||
*/
|
||||
public function get_matched_route() {
|
||||
return $this->matched_route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the route (regex for path) that caused the response.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param string $route Route name.
|
||||
*/
|
||||
public function set_matched_route( $route ) {
|
||||
$this->matched_route = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the handler that was used to generate the response.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @return null|array The handler that was used to create the response.
|
||||
*/
|
||||
public function get_matched_handler() {
|
||||
return $this->matched_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the handler that was responsible for generating the response.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @param array $handler The matched handler.
|
||||
*/
|
||||
public function set_matched_handler( $handler ) {
|
||||
$this->matched_handler = $handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the response is an error, i.e. >= 400 response code.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @return bool Whether the response is an error.
|
||||
*/
|
||||
public function is_error() {
|
||||
return $this->get_status() >= 400;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a WP_Error object from the response.
|
||||
*
|
||||
* @since 4.4.0
|
||||
*
|
||||
* @return WP_Error|null WP_Error or null on not an errored response.
|
||||
*/
|
||||
public function as_error() {
|
||||
if ( ! $this->is_error() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$error = new WP_Error;
|
||||
|
||||
if ( is_array( $this->get_data() ) ) {
|
||||
$data = $this->get_data();
|
||||
$error->add( $data['code'], $data['message'], $data['data'] );
|
||||
if ( ! empty( $data['additional_errors'] ) ) {
|
||||
foreach ( $data['additional_errors'] as $err ) {
|
||||
$error->add( $err['code'], $err['message'], $err['data'] );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$error->add( $this->get_status(), '', array( 'status' => $this->get_status() ) );
|
||||
}
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the CURIEs (compact URIs) used for relations.
|
||||
*
|
||||
* @since 4.5.0
|
||||
*
|
||||
* @return array Compact URIs.
|
||||
*/
|
||||
public function get_curies() {
|
||||
$curies = array(
|
||||
array(
|
||||
'name' => 'wp',
|
||||
'href' => 'https://api.w.org/{rel}',
|
||||
'templated' => true,
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters extra CURIEs available on API responses.
|
||||
*
|
||||
* CURIEs allow a shortened version of URI relations. This allows a more
|
||||
* usable form for custom relations than using the full URI. These work
|
||||
* similarly to how XML namespaces work.
|
||||
*
|
||||
* Registered CURIES need to specify a name and URI template. This will
|
||||
* automatically transform URI relations into their shortened version.
|
||||
* The shortened relation follows the format `{name}:{rel}`. `{rel}` in
|
||||
* the URI template will be replaced with the `{rel}` part of the
|
||||
* shortened relation.
|
||||
*
|
||||
* For example, a CURIE with name `example` and URI template
|
||||
* `http://w.org/{rel}` would transform a `http://w.org/term` relation
|
||||
* into `example:term`.
|
||||
*
|
||||
* Well-behaved clients should expand and normalise these back to their
|
||||
* full URI relation, however some naive clients may not resolve these
|
||||
* correctly, so adding new CURIEs may break backward compatibility.
|
||||
*
|
||||
* @since 4.5.0
|
||||
*
|
||||
* @param array $additional Additional CURIEs to register with the API.
|
||||
*/
|
||||
$additional = apply_filters( 'rest_response_link_curies', array() );
|
||||
return array_merge( $curies, $additional );
|
||||
}
|
||||
}
|
1348
msd2/wordpress/wp-includes/rest-api/class-wp-rest-server.php
Normal file
1348
msd2/wordpress/wp-includes/rest-api/class-wp-rest-server.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,838 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Attachments_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core controller used to access attachments via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Posts_Controller
|
||||
*/
|
||||
class WP_REST_Attachments_Controller extends WP_REST_Posts_Controller {
|
||||
|
||||
/**
|
||||
* Determines the allowed query_vars for a get_items() response and
|
||||
* prepares for WP_Query.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $prepared_args Optional. Array of prepared arguments. Default empty array.
|
||||
* @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'] ) ) {
|
||||
$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'];
|
||||
}
|
||||
}
|
||||
|
||||
// Filter query clauses to include filenames.
|
||||
if ( isset( $query_args['s'] ) ) {
|
||||
add_filter( 'posts_clauses', '_filter_query_attachment_filenames' );
|
||||
}
|
||||
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to create an attachment.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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 = 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 post.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single attachment.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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'];
|
||||
|
||||
// Include image functions to get access to wp_read_image_metadata().
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
|
||||
// use image exif/iptc data for title and caption defaults if possible
|
||||
$image_meta = wp_read_image_metadata( $file );
|
||||
|
||||
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->post_mime_type = $type;
|
||||
$attachment->guid = $url;
|
||||
|
||||
if ( empty( $attachment->post_title ) ) {
|
||||
$attachment->post_title = preg_replace( '/\.[^.]+$/', '', basename( $file ) );
|
||||
}
|
||||
|
||||
// $post_parent is inherited from $attachment['post_parent'].
|
||||
$id = wp_insert_attachment( wp_slash( (array) $attachment ), $file, 0, 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 = get_post( $id );
|
||||
|
||||
/**
|
||||
* Fires after a single attachment is created or updated via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_Post $attachment Inserted or updated attachment
|
||||
* object.
|
||||
* @param WP_REST_Request $request The request sent to the API.
|
||||
* @param bool $creating True when creating an attachment, false when updating.
|
||||
*/
|
||||
do_action( 'rest_insert_attachment', $attachment, $request, true );
|
||||
|
||||
// Include admin function to get access to wp_generate_attachment_metadata().
|
||||
require_once ABSPATH . 'wp-admin/includes/media.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' );
|
||||
|
||||
/**
|
||||
* Fires after a single attachment is completely created or updated via the REST API.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_Post $attachment Inserted or updated attachment object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @param bool $creating True when creating an attachment, false when updating.
|
||||
*/
|
||||
do_action( 'rest_after_insert_attachment', $attachment, $request, true );
|
||||
|
||||
$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 ) ) );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single attachment.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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 = 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' );
|
||||
|
||||
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-attachments-controller.php */
|
||||
do_action( 'rest_after_insert_attachment', $attachment, $request, false );
|
||||
|
||||
$response = $this->prepare_item_for_response( $attachment, $request );
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single attachment for create or update.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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 );
|
||||
|
||||
// Attachment caption (post_excerpt internally)
|
||||
if ( isset( $request['caption'] ) ) {
|
||||
if ( is_string( $request['caption'] ) ) {
|
||||
$prepared_attachment->post_excerpt = $request['caption'];
|
||||
} elseif ( isset( $request['caption']['raw'] ) ) {
|
||||
$prepared_attachment->post_excerpt = $request['caption']['raw'];
|
||||
}
|
||||
}
|
||||
|
||||
// Attachment description (post_content internally)
|
||||
if ( isset( $request['description'] ) ) {
|
||||
if ( is_string( $request['description'] ) ) {
|
||||
$prepared_attachment->post_content = $request['description'];
|
||||
} elseif ( isset( $request['description']['raw'] ) ) {
|
||||
$prepared_attachment->post_content = $request['description']['raw'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $request['post'] ) ) {
|
||||
$prepared_attachment->post_parent = (int) $request['post'];
|
||||
}
|
||||
|
||||
return $prepared_attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single attachment output for response.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_Post $post Attachment 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 );
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
if ( in_array( 'description', $fields, true ) ) {
|
||||
$data['description'] = array(
|
||||
'raw' => $post->post_content,
|
||||
/** This filter is documented in wp-includes/post-template.php */
|
||||
'rendered' => apply_filters( 'the_content', $post->post_content ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( in_array( 'caption', $fields, true ) ) {
|
||||
/** This filter is documented in wp-includes/post-template.php */
|
||||
$caption = apply_filters( 'the_excerpt', apply_filters( 'get_the_excerpt', $post->post_excerpt, $post ) );
|
||||
$data['caption'] = array(
|
||||
'raw' => $post->post_excerpt,
|
||||
'rendered' => $caption,
|
||||
);
|
||||
}
|
||||
|
||||
if ( in_array( 'alt_text', $fields, true ) ) {
|
||||
$data['alt_text'] = get_post_meta( $post->ID, '_wp_attachment_image_alt', true );
|
||||
}
|
||||
|
||||
if ( in_array( 'media_type', $fields, true ) ) {
|
||||
$data['media_type'] = wp_attachment_is_image( $post->ID ) ? 'image' : 'file';
|
||||
}
|
||||
|
||||
if ( in_array( 'mime_type', $fields, true ) ) {
|
||||
$data['mime_type'] = $post->post_mime_type;
|
||||
}
|
||||
|
||||
if ( in_array( 'media_details', $fields, true ) ) {
|
||||
$data['media_details'] = wp_get_attachment_metadata( $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;
|
||||
}
|
||||
}
|
||||
|
||||
if ( in_array( 'post', $fields, true ) ) {
|
||||
$data['post'] = ! empty( $post->post_parent ) ? (int) $post->post_parent : null;
|
||||
}
|
||||
|
||||
if ( in_array( 'source_url', $fields, true ) ) {
|
||||
$data['source_url'] = wp_get_attachment_url( $post->ID );
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
$links = $response->get_links();
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
foreach ( $links as $rel => $rel_links ) {
|
||||
foreach ( $rel_links as $link ) {
|
||||
$response->add_link( $rel, $link['href'], $link['attributes'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters an attachment returned from the REST API.
|
||||
*
|
||||
* Allows modification of the attachment right before it is returned.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the attachment's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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 attachment is not displayed.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
);
|
||||
|
||||
$schema['properties']['caption'] = array(
|
||||
'description' => __( 'The attachment caption.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
|
||||
'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database()
|
||||
),
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'description' => __( 'Caption for the attachment, as it exists in the database.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'HTML caption for the attachment, transformed for display.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$schema['properties']['description'] = array(
|
||||
'description' => __( 'The attachment description.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => null, // Note: sanitization implemented in self::prepare_item_for_database()
|
||||
'validate_callback' => null, // Note: validation implemented in self::prepare_item_for_database()
|
||||
),
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'description' => __( 'Description for the object, as it exists in the database.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'HTML description for the object, transformed for display.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$schema['properties']['media_type'] = array(
|
||||
'description' => __( 'Attachment type.' ),
|
||||
'type' => 'string',
|
||||
'enum' => array( 'image', 'file' ),
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
);
|
||||
|
||||
$schema['properties']['mime_type'] = array(
|
||||
'description' => __( 'The attachment MIME type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
);
|
||||
|
||||
$schema['properties']['media_details'] = array(
|
||||
'description' => __( 'Details about the media 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 attachment.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
);
|
||||
|
||||
$schema['properties']['source_url'] = array(
|
||||
'description' => __( 'URL to the original attachment file.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
);
|
||||
|
||||
unset( $schema['properties']['password'] );
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an upload via raw POST data.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $data Supplied file data.
|
||||
* @param array $headers HTTP headers from the request.
|
||||
* @return array|WP_Error Data from 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/file.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,
|
||||
);
|
||||
|
||||
$size_check = self::check_upload_size( $file_data );
|
||||
if ( is_wp_error( $size_check ) ) {
|
||||
return $size_check;
|
||||
}
|
||||
|
||||
$overrides = array(
|
||||
'test_form' => false,
|
||||
);
|
||||
|
||||
$sideloaded = wp_handle_sideload( $file_data, $overrides );
|
||||
|
||||
if ( isset( $sideloaded['error'] ) ) {
|
||||
@unlink( $tmpfname );
|
||||
|
||||
return new WP_Error( 'rest_upload_sideload_error', $sideloaded['error'], array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
return $sideloaded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses 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 "*">
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @link http://tools.ietf.org/html/rfc2388
|
||||
* @link 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for collections of attachments.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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']['items']['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 ),
|
||||
);
|
||||
|
||||
$params['mime_type'] = array(
|
||||
'default' => null,
|
||||
'description' => __( 'Limit result set to attachments of a particular MIME type.' ),
|
||||
'type' => 'string',
|
||||
);
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates whether the user can query private statuses.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param mixed $value Status value.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @param string $parameter Additional parameter to pass for validation.
|
||||
* @return WP_Error|bool 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an upload via multipart/form-data ($_FILES).
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $files Data from the `$_FILES` superglobal.
|
||||
* @param array $headers HTTP headers from the request.
|
||||
* @return array|WP_Error Data from 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';
|
||||
}
|
||||
|
||||
$size_check = self::check_upload_size( $files['file'] );
|
||||
if ( is_wp_error( $size_check ) ) {
|
||||
return $size_check;
|
||||
}
|
||||
|
||||
/** Include admin function to get access to wp_handle_upload(). */
|
||||
require_once ABSPATH . 'wp-admin/includes/file.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the supported media types.
|
||||
*
|
||||
* Media types are considered the MIME type category.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Array of supported media types.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if uploaded file exceeds space quota on multisite.
|
||||
*
|
||||
* Replicates check_upload_size().
|
||||
*
|
||||
* @since 4.9.8
|
||||
*
|
||||
* @param array $file $_FILES array for a given file.
|
||||
* @return true|WP_Error True if can upload, error for errors.
|
||||
*/
|
||||
protected function check_upload_size( $file ) {
|
||||
if ( ! is_multisite() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( get_site_option( 'upload_space_check_disabled' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$space_left = get_upload_space_available();
|
||||
|
||||
$file_size = filesize( $file['tmp_name'] );
|
||||
if ( $space_left < $file_size ) {
|
||||
/* translators: %s: required disk space in kilobytes */
|
||||
return new WP_Error( 'rest_upload_limited_space', sprintf( __( 'Not enough space to upload. %s KB needed.' ), number_format( ( $file_size - $space_left ) / KB_IN_BYTES ) ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
if ( $file_size > ( KB_IN_BYTES * get_site_option( 'fileupload_maxk', 1500 ) ) ) {
|
||||
/* translators: %s: maximum allowed file size in kilobytes */
|
||||
return new WP_Error( 'rest_upload_file_too_big', sprintf( __( 'This file is too big. Files must be less than %s KB in size.' ), get_site_option( 'fileupload_maxk', 1500 ) ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
// Include admin function to get access to upload_is_user_over_quota().
|
||||
require_once ABSPATH . 'wp-admin/includes/ms.php';
|
||||
|
||||
if ( upload_is_user_over_quota( false ) ) {
|
||||
return new WP_Error( 'rest_upload_user_quota_exceeded', __( 'You have used your space quota. Please delete files before uploading.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,427 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Autosaves_Controller class.
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access autosaves via the REST API.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Autosaves_Controller extends WP_REST_Revisions_Controller {
|
||||
|
||||
/**
|
||||
* Parent post type.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @var string
|
||||
*/
|
||||
private $parent_post_type;
|
||||
|
||||
/**
|
||||
* Parent post controller.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @var WP_REST_Controller
|
||||
*/
|
||||
private $parent_controller;
|
||||
|
||||
/**
|
||||
* Revision controller.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @var WP_REST_Controller
|
||||
*/
|
||||
private $revisions_controller;
|
||||
|
||||
/**
|
||||
* The base of the parent controller's route.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @var string
|
||||
*/
|
||||
private $parent_base;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param string $parent_post_type Post type of the parent.
|
||||
*/
|
||||
public function __construct( $parent_post_type ) {
|
||||
$this->parent_post_type = $parent_post_type;
|
||||
$post_type_object = get_post_type_object( $parent_post_type );
|
||||
|
||||
// Ensure that post type-specific controller logic is available.
|
||||
$parent_controller_class = ! empty( $post_type_object->rest_controller_class ) ? $post_type_object->rest_controller_class : 'WP_REST_Posts_Controller';
|
||||
|
||||
$this->parent_controller = new $parent_controller_class( $post_type_object->name );
|
||||
$this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
|
||||
$this->rest_namespace = 'wp/v2';
|
||||
$this->rest_base = 'autosaves';
|
||||
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes for autosaves.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->rest_namespace,
|
||||
'/' . $this->parent_base . '/(?P<id>[\d]+)/' . $this->rest_base,
|
||||
array(
|
||||
'args' => array(
|
||||
'parent' => array(
|
||||
'description' => __( 'The ID for the parent of the object.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
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->parent_controller->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->rest_namespace,
|
||||
'/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base . '/(?P<id>[\d]+)',
|
||||
array(
|
||||
'args' => array(
|
||||
'parent' => array(
|
||||
'description' => __( 'The ID for the parent of the object.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'The ID for the object.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this->revisions_controller, 'get_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent post.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param int $parent_id Supplied ID.
|
||||
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_parent( $parent_id ) {
|
||||
return $this->revisions_controller->get_parent( $parent_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get autosaves.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
$parent = $this->get_parent( $request['id'] );
|
||||
if ( is_wp_error( $parent ) ) {
|
||||
return $parent;
|
||||
}
|
||||
|
||||
$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 are not allowed to view autosaves of this post.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to create an autosave revision.
|
||||
*
|
||||
* Autosave revisions inherit permissions from the parent post,
|
||||
* check if the current user has permission to edit the post.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has access to create the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function create_item_permissions_check( $request ) {
|
||||
$id = $request->get_param( 'id' );
|
||||
if ( empty( $id ) ) {
|
||||
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid item ID.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return $this->parent_controller->update_item_permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates, updates or deletes an autosave revision.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function create_item( $request ) {
|
||||
|
||||
if ( ! defined( 'DOING_AUTOSAVE' ) ) {
|
||||
define( 'DOING_AUTOSAVE', true );
|
||||
}
|
||||
|
||||
$post = get_post( $request['id'] );
|
||||
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
$prepared_post = $this->parent_controller->prepare_item_for_database( $request );
|
||||
$prepared_post->ID = $post->ID;
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) {
|
||||
// Draft posts for the same author: autosaving updates the post and does not create a revision.
|
||||
// Convert the post object to an array and add slashes, wp_update_post expects escaped array.
|
||||
$autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
|
||||
} else {
|
||||
// Non-draft posts: create or update the post autosave.
|
||||
$autosave_id = $this->create_post_autosave( (array) $prepared_post );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $autosave_id ) ) {
|
||||
return $autosave_id;
|
||||
}
|
||||
|
||||
$autosave = get_post( $autosave_id );
|
||||
$request->set_param( 'context', 'edit' );
|
||||
|
||||
$response = $this->prepare_item_for_response( $autosave, $request );
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the autosave, if the ID is valid.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
$parent_id = (int) $request->get_param( 'parent' );
|
||||
|
||||
if ( $parent_id <= 0 ) {
|
||||
return new WP_Error( 'rest_post_invalid_id', __( 'Invalid parent post ID.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$autosave = wp_get_post_autosave( $parent_id );
|
||||
|
||||
if ( ! $autosave ) {
|
||||
return new WP_Error( 'rest_post_no_autosave', __( 'There is no autosave revision for this post.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$response = $this->prepare_item_for_response( $autosave, $request );
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a collection of autosaves using wp_get_post_autosave.
|
||||
*
|
||||
* Contains the user's autosave, for empty if it doesn't exist.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$parent = $this->get_parent( $request['id'] );
|
||||
if ( is_wp_error( $parent ) ) {
|
||||
return $parent;
|
||||
}
|
||||
|
||||
$response = array();
|
||||
$parent_id = $parent->ID;
|
||||
$revisions = wp_get_post_revisions( $parent_id, array( 'check_enabled' => false ) );
|
||||
|
||||
foreach ( $revisions as $revision ) {
|
||||
if ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) {
|
||||
$data = $this->prepare_item_for_response( $revision, $request );
|
||||
$response[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the autosave's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = $this->revisions_controller->get_item_schema();
|
||||
|
||||
$schema['properties']['preview_link'] = array(
|
||||
'description' => __( 'Preview link for the post.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
);
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates autosave for the specified post.
|
||||
*
|
||||
* From wp-admin/post.php.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param mixed $post_data Associative array containing the post data.
|
||||
* @return mixed The autosave revision ID or WP_Error.
|
||||
*/
|
||||
public function create_post_autosave( $post_data ) {
|
||||
|
||||
$post_id = (int) $post_data['ID'];
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
// Store one autosave per author. If there is already an autosave, overwrite it.
|
||||
$old_autosave = wp_get_post_autosave( $post_id, $user_id );
|
||||
|
||||
if ( $old_autosave ) {
|
||||
$new_autosave = _wp_post_revision_data( $post_data, true );
|
||||
$new_autosave['ID'] = $old_autosave->ID;
|
||||
$new_autosave['post_author'] = $user_id;
|
||||
|
||||
// If the new autosave has the same content as the post, delete the autosave.
|
||||
$autosave_is_different = false;
|
||||
|
||||
foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
|
||||
if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) {
|
||||
$autosave_is_different = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $autosave_is_different ) {
|
||||
wp_delete_post_revision( $old_autosave->ID );
|
||||
return new WP_Error( 'rest_autosave_no_changes', __( 'There is nothing to save. The autosave and the post content are the same.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* This filter is documented in wp-admin/post.php.
|
||||
*/
|
||||
do_action( 'wp_creating_autosave', $new_autosave );
|
||||
|
||||
// wp_update_post expects escaped array.
|
||||
return wp_update_post( wp_slash( $new_autosave ) );
|
||||
}
|
||||
|
||||
// Create the new autosave as a special post revision.
|
||||
return _wp_put_post_revision( $post_data, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the revision for the REST response.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_Post $post Post revision object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $post, $request ) {
|
||||
|
||||
$response = $this->revisions_controller->prepare_item_for_response( $post, $request );
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
if ( in_array( 'preview_link', $fields, true ) ) {
|
||||
$parent_id = wp_is_post_autosave( $post );
|
||||
$preview_post_id = false === $parent_id ? $post->ID : $parent_id;
|
||||
$preview_query_args = array();
|
||||
|
||||
if ( false !== $parent_id ) {
|
||||
$preview_query_args['preview_id'] = $parent_id;
|
||||
$preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id );
|
||||
}
|
||||
|
||||
$response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args );
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$response->data = $this->add_additional_fields_to_object( $response->data, $request );
|
||||
$response->data = $this->filter_response_by_context( $response->data, $context );
|
||||
|
||||
/**
|
||||
* Filters a revision returned from the API.
|
||||
*
|
||||
* Allows modification of the revision right before it is returned.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @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_autosave', $response, $post, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the autosaves collection.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
return array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,178 @@
|
||||
<?php
|
||||
/**
|
||||
* Block Renderer REST API: WP_REST_Block_Renderer_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller which provides REST endpoint for rendering a block.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Block_Renderer_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructs the controller.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'block-renderer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the necessary REST API routes, one for each dynamic block.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
$block_types = WP_Block_Type_Registry::get_instance()->get_all_registered();
|
||||
|
||||
foreach ( $block_types as $block_type ) {
|
||||
if ( ! $block_type->is_dynamic() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<name>' . $block_type->name . ')',
|
||||
array(
|
||||
'args' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'Unique registered name for the block.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
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' ) ),
|
||||
'attributes' => array(
|
||||
/* translators: %s is the name of the block */
|
||||
'description' => sprintf( __( 'Attributes for %s block' ), $block_type->name ),
|
||||
'type' => 'object',
|
||||
'additionalProperties' => false,
|
||||
'properties' => $block_type->get_attributes(),
|
||||
'default' => array(),
|
||||
),
|
||||
'post_id' => array(
|
||||
'description' => __( 'ID of the post context.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read blocks.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Request.
|
||||
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
global $post;
|
||||
|
||||
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
|
||||
|
||||
if ( 0 < $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
|
||||
return new WP_Error(
|
||||
'block_cannot_read',
|
||||
__( 'Sorry, you are not allowed to read blocks of this post.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
return new WP_Error(
|
||||
'block_cannot_read',
|
||||
__( 'Sorry, you are not allowed to read blocks as this user.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns block output from block's registered render_callback.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
global $post;
|
||||
|
||||
$post_id = isset( $request['post_id'] ) ? intval( $request['post_id'] ) : 0;
|
||||
|
||||
if ( 0 < $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
|
||||
// Set up postdata since this will be needed if post_id was set.
|
||||
setup_postdata( $post );
|
||||
}
|
||||
$registry = WP_Block_Type_Registry::get_instance();
|
||||
$block = $registry->get_registered( $request['name'] );
|
||||
|
||||
if ( null === $block ) {
|
||||
return new WP_Error(
|
||||
'block_invalid',
|
||||
__( 'Invalid block.' ),
|
||||
array(
|
||||
'status' => 404,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$data = array(
|
||||
'rendered' => $block->render( $request->get_param( 'attributes' ) ),
|
||||
);
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves block's output schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
return array(
|
||||
'$schema' => 'http://json-schema.org/schema#',
|
||||
'title' => 'rendered-block',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'rendered' => array(
|
||||
'description' => __( 'The rendered block.' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Reusable blocks REST API: WP_REST_Blocks_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller which provides a REST endpoint for the editor to read, create,
|
||||
* edit and delete reusable blocks. Blocks are stored as posts with the wp_block
|
||||
* post type.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see WP_REST_Posts_Controller
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Blocks_Controller extends WP_REST_Posts_Controller {
|
||||
|
||||
/**
|
||||
* Checks if a block can be read.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param object $post Post object that backs the block.
|
||||
* @return bool Whether the block can be read.
|
||||
*/
|
||||
public function check_read_permission( $post ) {
|
||||
// Ensure that the user is logged in and has the read_blocks capability.
|
||||
$post_type = get_post_type_object( $post->post_type );
|
||||
if ( ! current_user_can( $post_type->cap->read_post, $post->ID ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::check_read_permission( $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a response based on the context defined in the schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param array $data Response data to fiter.
|
||||
* @param string $context Context defined in the schema.
|
||||
* @return array Filtered response.
|
||||
*/
|
||||
public function filter_response_by_context( $data, $context ) {
|
||||
$data = parent::filter_response_by_context( $data, $context );
|
||||
|
||||
/*
|
||||
* Remove `title.rendered` and `content.rendered` from the response. It
|
||||
* doesn't make sense for a reusable block to have rendered content on its
|
||||
* own, since rendering a block requires it to be inside a post or a page.
|
||||
*/
|
||||
unset( $data['title']['rendered'] );
|
||||
unset( $data['content']['rendered'] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the block's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = parent::get_item_schema();
|
||||
|
||||
/*
|
||||
* Allow all contexts to access `title.raw` and `content.raw`. Clients always
|
||||
* need the raw markup of a reusable block to do anything useful, e.g. parse
|
||||
* it or display it in an editor.
|
||||
*/
|
||||
$schema['properties']['title']['properties']['raw']['context'] = array( 'view', 'edit' );
|
||||
$schema['properties']['content']['properties']['raw']['context'] = array( 'view', 'edit' );
|
||||
|
||||
/*
|
||||
* Remove `title.rendered` and `content.rendered` from the schema. It doesn’t
|
||||
* make sense for a reusable block to have rendered content on its own, since
|
||||
* rendering a block requires it to be inside a post or a page.
|
||||
*/
|
||||
unset( $schema['properties']['title']['properties']['rendered'] );
|
||||
unset( $schema['properties']['content']['properties']['rendered'] );
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,634 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core base controller for managing and interacting with REST API items.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*/
|
||||
abstract class WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* The namespace of this controller's route.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace;
|
||||
|
||||
/**
|
||||
* The base of this controller's route.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base;
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
/* translators: %s: register_routes() */
|
||||
_doing_it_wrong( 'WP_REST_Controller::register_routes', sprintf( __( "Method '%s' must be overridden." ), __METHOD__ ), '4.7' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get items.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Error|bool True if the request has read access, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a collection of items.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get a specific item.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves one item from the collection.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to create items.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Error|bool True if the request has access to create items, WP_Error object otherwise.
|
||||
*/
|
||||
public function create_item_permissions_check( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates one item from the collection.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function create_item( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to update a specific item.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Error|bool True if the request has access to update the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function update_item_permissions_check( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates one item from the collection.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function update_item( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to delete a specific item.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Error|bool True if the request has access to delete the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function delete_item_permissions_check( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes one item from the collection.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function delete_item( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares one item for create or update operation.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_Error|object The prepared item, or WP_Error object on failure.
|
||||
*/
|
||||
protected function prepare_item_for_database( $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the item for the REST response.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param mixed $item WordPress representation of the item.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
/* translators: %s: method name */
|
||||
return new WP_Error( 'invalid-method', sprintf( __( "Method '%s' not implemented. Must be overridden in subclass." ), __METHOD__ ), array( 'status' => 405 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a response for insertion into a collection.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Response $response Response object.
|
||||
* @return array|mixed 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();
|
||||
$links = $server->get_compact_response_links( $response );
|
||||
|
||||
if ( ! empty( $links ) ) {
|
||||
$data['_links'] = $links;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a response based on the context defined in the schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $data Response data to fiter.
|
||||
* @param string $context Context defined in the schema.
|
||||
* @return array Filtered response.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
return $this->add_additional_fields_schema( array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item's schema for display / public consumption purposes.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Public item schema data.
|
||||
*/
|
||||
public function get_public_item_schema() {
|
||||
|
||||
$schema = $this->get_item_schema();
|
||||
|
||||
foreach ( $schema['properties'] as &$property ) {
|
||||
unset( $property['arg_options'] );
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the collections.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Query parameters for the collection.
|
||||
*/
|
||||
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',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the magical context param.
|
||||
*
|
||||
* Ensures consistent descriptions between endpoints, and populates enum from schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $args Optional. Additional arguments for context parameter. Default empty array.
|
||||
* @return array Context parameter details.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the values from additional fields to a data object.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $object Data object.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return array Modified data object with additional fields.
|
||||
*/
|
||||
protected function add_additional_fields_to_object( $object, $request ) {
|
||||
|
||||
$additional_fields = $this->get_additional_fields();
|
||||
|
||||
$requested_fields = $this->get_fields_for_response( $request );
|
||||
|
||||
foreach ( $additional_fields as $field_name => $field_options ) {
|
||||
|
||||
if ( ! $field_options['get_callback'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! in_array( $field_name, $requested_fields, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$object[ $field_name ] = call_user_func( $field_options['get_callback'], $object, $field_name, $request, $this->get_object_type() );
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the values of additional fields added to a data object.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $object Data Object.
|
||||
* @param WP_REST_Request $request Full details about the 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the schema from additional fields to a schema array.
|
||||
*
|
||||
* The type of object is inferred from the passed schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all of the registered additional fields for a given object-type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param string $object_type Optional. The object type.
|
||||
* @return array Registered additional fields (if any), empty array if none or if the object type could
|
||||
* not be inferred.
|
||||
*/
|
||||
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 ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object type this controller is responsible for managing.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string Object type for the controller.
|
||||
*/
|
||||
protected function get_object_type() {
|
||||
$schema = $this->get_item_schema();
|
||||
|
||||
if ( ! $schema || ! isset( $schema['title'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $schema['title'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of fields to be included on the response.
|
||||
*
|
||||
* Included fields are based on item schema and `_fields=` request argument.
|
||||
*
|
||||
* @since 4.9.6
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return array Fields to be included in the response.
|
||||
*/
|
||||
public function get_fields_for_response( $request ) {
|
||||
$schema = $this->get_item_schema();
|
||||
$fields = isset( $schema['properties'] ) ? array_keys( $schema['properties'] ) : array();
|
||||
|
||||
$additional_fields = $this->get_additional_fields();
|
||||
foreach ( $additional_fields as $field_name => $field_options ) {
|
||||
// For back-compat, include any field with an empty schema
|
||||
// because it won't be present in $this->get_item_schema().
|
||||
if ( is_null( $field_options['schema'] ) ) {
|
||||
$fields[] = $field_name;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! isset( $request['_fields'] ) ) {
|
||||
return $fields;
|
||||
}
|
||||
$requested_fields = wp_parse_list( $request['_fields'] );
|
||||
if ( 0 === count( $requested_fields ) ) {
|
||||
return $fields;
|
||||
}
|
||||
// Trim off outside whitespace from the comma delimited list.
|
||||
$requested_fields = array_map( 'trim', $requested_fields );
|
||||
// Always persist 'id', because it can be needed for add_additional_fields_to_object().
|
||||
if ( in_array( 'id', $fields, true ) ) {
|
||||
$requested_fields[] = 'id';
|
||||
}
|
||||
return array_intersect( $fields, $requested_fields );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an array of endpoint arguments from the item schema for the controller.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param string $method Optional. 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 WP_REST_Server::CREATABLE.
|
||||
* @return array Endpoint arguments.
|
||||
*/
|
||||
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', 'items', 'properties', 'additionalProperties' ) 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the slug value.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @internal We can't use 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 );
|
||||
}
|
||||
}
|
@ -0,0 +1,337 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Post_Statuses_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access post statuses via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Post_Statuses_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'statuses';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
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(
|
||||
'args' => array(
|
||||
'status' => array(
|
||||
'description' => __( 'An alphanumeric identifier for the status.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given request has permission to read post statuses.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|bool True if the request has read access, WP_Error object otherwise.
|
||||
*/
|
||||
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 are not allowed to manage post statuses.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all post statuses, depending on user context.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read a post status.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|bool True if the request has read access for the item, WP_Error object otherwise.
|
||||
*/
|
||||
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 status.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$check = $this->check_read_permission( $status );
|
||||
|
||||
if ( ! $check ) {
|
||||
return new WP_Error( 'rest_cannot_read_status', __( 'Cannot view status.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given post status should be visible.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param object $status Post status.
|
||||
* @return bool True if the post status is visible, otherwise false.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific post status.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
$obj = get_post_status_object( $request['status'] );
|
||||
|
||||
if ( empty( $obj ) ) {
|
||||
return new WP_Error( 'rest_status_invalid', __( 'Invalid status.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$data = $this->prepare_item_for_response( $obj, $request );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a post status object for serialization.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param stdClass $status Post status data.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Post status data.
|
||||
*/
|
||||
public function prepare_item_for_response( $status, $request ) {
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( in_array( 'name', $fields, true ) ) {
|
||||
$data['name'] = $status->label;
|
||||
}
|
||||
|
||||
if ( in_array( 'private', $fields, true ) ) {
|
||||
$data['private'] = (bool) $status->private;
|
||||
}
|
||||
|
||||
if ( in_array( 'protected', $fields, true ) ) {
|
||||
$data['protected'] = (bool) $status->protected;
|
||||
}
|
||||
|
||||
if ( in_array( 'public', $fields, true ) ) {
|
||||
$data['public'] = (bool) $status->public;
|
||||
}
|
||||
|
||||
if ( in_array( 'queryable', $fields, true ) ) {
|
||||
$data['queryable'] = (bool) $status->publicly_queryable;
|
||||
}
|
||||
|
||||
if ( in_array( 'show_in_list', $fields, true ) ) {
|
||||
$data['show_in_list'] = (bool) $status->show_in_admin_all_list;
|
||||
}
|
||||
|
||||
if ( in_array( 'slug', $fields, true ) ) {
|
||||
$data['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' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a status returned from the REST API.
|
||||
*
|
||||
* Allows modification of the status data right before it is returned.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post status' schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
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 status.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'private' => array(
|
||||
'description' => __( 'Whether posts with this status should be private.' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'protected' => array(
|
||||
'description' => __( 'Whether posts with this status should be protected.' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'public' => array(
|
||||
'description' => __( 'Whether posts of this status 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 status 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 status.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for collections.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
return array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,331 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Post_Types_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class to access post types via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Post_Types_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'types';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
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(
|
||||
'args' => array(
|
||||
'type' => array(
|
||||
'description' => __( 'An alphanumeric identifier for the post type.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given request has permission to read types.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|true True if the request has read access, WP_Error object otherwise.
|
||||
*/
|
||||
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 are not allowed to edit posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all public post types.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific post type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
$obj = get_post_type_object( $request['type'] );
|
||||
|
||||
if ( empty( $obj ) ) {
|
||||
return new WP_Error( 'rest_type_invalid', __( 'Invalid post type.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
if ( empty( $obj->show_in_rest ) ) {
|
||||
return new WP_Error( 'rest_cannot_read_type', __( 'Cannot view post type.' ), 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 edit posts in this post type.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
$data = $this->prepare_item_for_response( $obj, $request );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a post type object for serialization.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_Post_Type $post_type Post type object.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $post_type, $request ) {
|
||||
$taxonomies = wp_list_filter( get_object_taxonomies( $post_type->name, 'objects' ), array( 'show_in_rest' => true ) );
|
||||
$taxonomies = wp_list_pluck( $taxonomies, 'name' );
|
||||
$base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
|
||||
$supports = get_all_post_type_supports( $post_type->name );
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( in_array( 'capabilities', $fields, true ) ) {
|
||||
$data['capabilities'] = $post_type->cap;
|
||||
}
|
||||
|
||||
if ( in_array( 'description', $fields, true ) ) {
|
||||
$data['description'] = $post_type->description;
|
||||
}
|
||||
|
||||
if ( in_array( 'hierarchical', $fields, true ) ) {
|
||||
$data['hierarchical'] = $post_type->hierarchical;
|
||||
}
|
||||
|
||||
if ( in_array( 'viewable', $fields, true ) ) {
|
||||
$data['viewable'] = is_post_type_viewable( $post_type );
|
||||
}
|
||||
|
||||
if ( in_array( 'labels', $fields, true ) ) {
|
||||
$data['labels'] = $post_type->labels;
|
||||
}
|
||||
|
||||
if ( in_array( 'name', $fields, true ) ) {
|
||||
$data['name'] = $post_type->label;
|
||||
}
|
||||
|
||||
if ( in_array( 'slug', $fields, true ) ) {
|
||||
$data['slug'] = $post_type->name;
|
||||
}
|
||||
|
||||
if ( in_array( 'supports', $fields, true ) ) {
|
||||
$data['supports'] = $supports;
|
||||
}
|
||||
|
||||
if ( in_array( 'taxonomies', $fields, true ) ) {
|
||||
$data['taxonomies'] = array_values( $taxonomies );
|
||||
}
|
||||
|
||||
if ( in_array( 'rest_base', $fields, true ) ) {
|
||||
$data['rest_base'] = $base;
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
$response->add_links(
|
||||
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 ) ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters a post type returned from the API.
|
||||
*
|
||||
* Allows modification of the post type data right before it is returned.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post type's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
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 post type.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'A human-readable description of the post type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'hierarchical' => array(
|
||||
'description' => __( 'Whether or not the post type should have children.' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'viewable' => array(
|
||||
'description' => __( 'Whether or not the post type can be viewed.' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'labels' => array(
|
||||
'description' => __( 'Human-readable labels for the post type for various contexts.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'The title for the post type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'slug' => array(
|
||||
'description' => __( 'An alphanumeric identifier for the post type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'supports' => array(
|
||||
'description' => __( 'All features, supported by the post type.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'taxonomies' => array(
|
||||
'description' => __( 'Taxonomies associated with post type.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'rest_base' => array(
|
||||
'description' => __( 'REST base route for the post type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for collections.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
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
@ -0,0 +1,770 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Revisions_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access revisions via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Revisions_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Parent post type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $parent_post_type;
|
||||
|
||||
/**
|
||||
* Parent controller.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var WP_REST_Controller
|
||||
*/
|
||||
private $parent_controller;
|
||||
|
||||
/**
|
||||
* The base of the parent controller's route.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $parent_base;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param string $parent_post_type Post type of the parent.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers routes for revisions based on post types supporting revisions.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
public function register_routes() {
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->parent_base . '/(?P<parent>[\d]+)/' . $this->rest_base,
|
||||
array(
|
||||
'args' => array(
|
||||
'parent' => array(
|
||||
'description' => __( 'The ID for the parent of the object.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
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(
|
||||
'args' => array(
|
||||
'parent' => array(
|
||||
'description' => __( 'The ID for the parent of the object.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the object.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
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' ),
|
||||
'args' => array(
|
||||
'force' => array(
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
'description' => __( 'Required to be true, as revisions do not support trashing.' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent post, if the ID is valid.
|
||||
*
|
||||
* @since 4.7.2
|
||||
*
|
||||
* @param int $id Supplied ID.
|
||||
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_parent( $parent ) {
|
||||
$error = new WP_Error( 'rest_post_invalid_parent', __( 'Invalid post parent ID.' ), array( 'status' => 404 ) );
|
||||
if ( (int) $parent <= 0 ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$parent = get_post( (int) $parent );
|
||||
if ( empty( $parent ) || empty( $parent->ID ) || $this->parent_post_type !== $parent->post_type ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
return $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get revisions.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
$parent = $this->get_parent( $request['parent'] );
|
||||
if ( is_wp_error( $parent ) ) {
|
||||
return $parent;
|
||||
}
|
||||
|
||||
$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 are not allowed to view revisions of this post.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the revision, if the ID is valid.
|
||||
*
|
||||
* @since 4.7.2
|
||||
*
|
||||
* @param int $id Supplied ID.
|
||||
* @return WP_Post|WP_Error Revision post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_revision( $id ) {
|
||||
$error = new WP_Error( 'rest_post_invalid_id', __( 'Invalid revision ID.' ), array( 'status' => 404 ) );
|
||||
if ( (int) $id <= 0 ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$revision = get_post( (int) $id );
|
||||
if ( empty( $revision ) || empty( $revision->ID ) || 'revision' !== $revision->post_type ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
return $revision;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a collection of revisions.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$parent = $this->get_parent( $request['parent'] );
|
||||
if ( is_wp_error( $parent ) ) {
|
||||
return $parent;
|
||||
}
|
||||
|
||||
// Ensure a search string is set in case the orderby is set to 'relevance'.
|
||||
if ( ! empty( $request['orderby'] ) && 'relevance' === $request['orderby'] && empty( $request['search'] ) ) {
|
||||
return new WP_Error( 'rest_no_search_term_defined', __( 'You need to define a search term to order by relevance.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
// Ensure an include parameter is set in case the orderby is set to 'include'.
|
||||
if ( ! empty( $request['orderby'] ) && 'include' === $request['orderby'] && empty( $request['include'] ) ) {
|
||||
return new WP_Error( 'rest_orderby_include_missing_include', __( 'You need to define an include parameter to order by include.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
if ( wp_revisions_enabled( $parent ) ) {
|
||||
$registered = $this->get_collection_params();
|
||||
$args = array(
|
||||
'post_parent' => $parent->ID,
|
||||
'post_type' => 'revision',
|
||||
'post_status' => 'inherit',
|
||||
'posts_per_page' => -1,
|
||||
'orderby' => 'date ID',
|
||||
'order' => 'DESC',
|
||||
'suppress_filters' => true,
|
||||
);
|
||||
|
||||
$parameter_mappings = array(
|
||||
'exclude' => 'post__not_in',
|
||||
'include' => 'post__in',
|
||||
'offset' => 'offset',
|
||||
'order' => 'order',
|
||||
'orderby' => 'orderby',
|
||||
'page' => 'paged',
|
||||
'per_page' => 'posts_per_page',
|
||||
'search' => 's',
|
||||
);
|
||||
|
||||
foreach ( $parameter_mappings as $api_param => $wp_param ) {
|
||||
if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
|
||||
$args[ $wp_param ] = $request[ $api_param ];
|
||||
}
|
||||
}
|
||||
|
||||
// For backward-compatibility, 'date' needs to resolve to 'date ID'.
|
||||
if ( isset( $args['orderby'] ) && 'date' === $args['orderby'] ) {
|
||||
$args['orderby'] = 'date ID';
|
||||
}
|
||||
|
||||
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
|
||||
$args = apply_filters( 'rest_revision_query', $args, $request );
|
||||
$query_args = $this->prepare_items_query( $args, $request );
|
||||
|
||||
$revisions_query = new WP_Query();
|
||||
$revisions = $revisions_query->query( $query_args );
|
||||
$offset = isset( $query_args['offset'] ) ? (int) $query_args['offset'] : 0;
|
||||
$page = (int) $query_args['paged'];
|
||||
$total_revisions = $revisions_query->found_posts;
|
||||
|
||||
if ( $total_revisions < 1 ) {
|
||||
// Out-of-bounds, run the query again without LIMIT for total count.
|
||||
unset( $query_args['paged'], $query_args['offset'] );
|
||||
|
||||
$count_query = new WP_Query();
|
||||
$count_query->query( $query_args );
|
||||
|
||||
$total_revisions = $count_query->found_posts;
|
||||
}
|
||||
|
||||
if ( $revisions_query->query_vars['posts_per_page'] > 0 ) {
|
||||
$max_pages = ceil( $total_revisions / (int) $revisions_query->query_vars['posts_per_page'] );
|
||||
} else {
|
||||
$max_pages = $total_revisions > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
if ( $total_revisions > 0 ) {
|
||||
if ( $offset >= $total_revisions ) {
|
||||
return new WP_Error( 'rest_revision_invalid_offset_number', __( 'The offset number requested is larger than or equal to the number of available revisions.' ), array( 'status' => 400 ) );
|
||||
} elseif ( ! $offset && $page > $max_pages ) {
|
||||
return new WP_Error( 'rest_revision_invalid_page_number', __( 'The page number requested is larger than the number of pages available.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$revisions = array();
|
||||
$total_revisions = 0;
|
||||
$max_pages = 0;
|
||||
$page = (int) $request['page'];
|
||||
}
|
||||
|
||||
$response = array();
|
||||
foreach ( $revisions as $revision ) {
|
||||
$data = $this->prepare_item_for_response( $revision, $request );
|
||||
$response[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
$response->header( 'X-WP-Total', (int) $total_revisions );
|
||||
$response->header( 'X-WP-TotalPages', (int) $max_pages );
|
||||
|
||||
$request_params = $request->get_query_params();
|
||||
$base = add_query_arg( $request_params, rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get a specific revision.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
return $this->get_items_permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves one revision from the collection.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full data about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
$parent = $this->get_parent( $request['parent'] );
|
||||
if ( is_wp_error( $parent ) ) {
|
||||
return $parent;
|
||||
}
|
||||
|
||||
$revision = $this->get_revision( $request['id'] );
|
||||
if ( is_wp_error( $revision ) ) {
|
||||
return $revision;
|
||||
}
|
||||
|
||||
$response = $this->prepare_item_for_response( $revision, $request );
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to delete a revision.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return bool|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function delete_item_permissions_check( $request ) {
|
||||
$parent = $this->get_parent( $request['parent'] );
|
||||
if ( is_wp_error( $parent ) ) {
|
||||
return $parent;
|
||||
}
|
||||
|
||||
$revision = $this->get_revision( $request['id'] );
|
||||
if ( is_wp_error( $revision ) ) {
|
||||
return $revision;
|
||||
}
|
||||
|
||||
$response = $this->get_items_permissions_check( $request );
|
||||
if ( ! $response || is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$post_type = get_post_type_object( 'revision' );
|
||||
return current_user_can( $post_type->cap->delete_post, $revision->ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single revision.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function delete_item( $request ) {
|
||||
$revision = $this->get_revision( $request['id'] );
|
||||
if ( is_wp_error( $revision ) ) {
|
||||
return $revision;
|
||||
}
|
||||
|
||||
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
|
||||
|
||||
// We don't support trashing for revisions.
|
||||
if ( ! $force ) {
|
||||
/* translators: %s: force=true */
|
||||
return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Revisions do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) );
|
||||
}
|
||||
|
||||
$previous = $this->prepare_item_for_response( $revision, $request );
|
||||
|
||||
$result = wp_delete_post( $request['id'], true );
|
||||
|
||||
/**
|
||||
* Fires after a revision is deleted via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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 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 new WP_Error( 'rest_cannot_delete', __( 'The post cannot be deleted.' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
$response = new WP_REST_Response();
|
||||
$response->set_data(
|
||||
array(
|
||||
'deleted' => true,
|
||||
'previous' => $previous->get_data(),
|
||||
)
|
||||
);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the allowed query_vars for a get_items() response and prepares
|
||||
* them for WP_Query.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param array $prepared_args Optional. Prepared WP_Query arguments. Default empty array.
|
||||
* @param WP_REST_Request $request Optional. Full details about the request.
|
||||
* @return array Items query arguments.
|
||||
*/
|
||||
protected function prepare_items_query( $prepared_args = array(), $request = null ) {
|
||||
$query_args = array();
|
||||
|
||||
foreach ( $prepared_args as $key => $value ) {
|
||||
/** This filter is documented in wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php */
|
||||
$query_args[ $key ] = apply_filters( "rest_query_var-{$key}", $value ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
|
||||
}
|
||||
|
||||
// Map to proper WP_Query orderby param.
|
||||
if ( isset( $query_args['orderby'] ) && isset( $request['orderby'] ) ) {
|
||||
$orderby_mappings = array(
|
||||
'id' => 'ID',
|
||||
'include' => 'post__in',
|
||||
'slug' => 'post_name',
|
||||
'include_slugs' => 'post_name__in',
|
||||
);
|
||||
|
||||
if ( isset( $orderby_mappings[ $request['orderby'] ] ) ) {
|
||||
$query_args['orderby'] = $orderby_mappings[ $request['orderby'] ];
|
||||
}
|
||||
}
|
||||
|
||||
return $query_args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the revision for the REST response.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_Post $post Post revision object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $post, $request ) {
|
||||
$GLOBALS['post'] = $post;
|
||||
|
||||
setup_postdata( $post );
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( in_array( 'author', $fields, true ) ) {
|
||||
$data['author'] = (int) $post->post_author;
|
||||
}
|
||||
|
||||
if ( in_array( 'date', $fields, true ) ) {
|
||||
$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
|
||||
}
|
||||
|
||||
if ( in_array( 'date_gmt', $fields, true ) ) {
|
||||
$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
|
||||
}
|
||||
|
||||
if ( in_array( 'id', $fields, true ) ) {
|
||||
$data['id'] = $post->ID;
|
||||
}
|
||||
|
||||
if ( in_array( 'modified', $fields, true ) ) {
|
||||
$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
|
||||
}
|
||||
|
||||
if ( in_array( 'modified_gmt', $fields, true ) ) {
|
||||
$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
|
||||
}
|
||||
|
||||
if ( in_array( 'parent', $fields, true ) ) {
|
||||
$data['parent'] = (int) $post->post_parent;
|
||||
}
|
||||
|
||||
if ( in_array( 'slug', $fields, true ) ) {
|
||||
$data['slug'] = $post->post_name;
|
||||
}
|
||||
|
||||
if ( in_array( 'guid', $fields, true ) ) {
|
||||
$data['guid'] = array(
|
||||
/** This filter is documented in wp-includes/post-template.php */
|
||||
'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ),
|
||||
'raw' => $post->guid,
|
||||
);
|
||||
}
|
||||
|
||||
if ( in_array( 'title', $fields, true ) ) {
|
||||
$data['title'] = array(
|
||||
'raw' => $post->post_title,
|
||||
'rendered' => get_the_title( $post->ID ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( in_array( 'content', $fields, true ) ) {
|
||||
|
||||
$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 ( in_array( 'excerpt', $fields, true ) ) {
|
||||
$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'] ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a revision returned from the API.
|
||||
*
|
||||
* Allows modification of the revision right before it is returned.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the post_date_gmt or modified_gmt and prepare any post or
|
||||
* modified date for single post output.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param string $date_gmt GMT publication time.
|
||||
* @param string|null $date Optional. Local publication time. Default null.
|
||||
* @return string|null ISO8601/RFC3339 formatted datetime, otherwise null.
|
||||
*/
|
||||
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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the revision's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
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, in the site's timezone." ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'date_gmt' => array(
|
||||
'description' => __( 'The date the object was published, as GMT.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'guid' => array(
|
||||
'description' => __( '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, in the site's timezone." ),
|
||||
'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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for collections.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$query_params = parent::get_collection_params();
|
||||
|
||||
$query_params['context']['default'] = 'view';
|
||||
|
||||
unset( $query_params['per_page']['default'] );
|
||||
|
||||
$query_params['exclude'] = array(
|
||||
'description' => __( 'Ensure result set excludes specific IDs.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'default' => array(),
|
||||
);
|
||||
|
||||
$query_params['include'] = array(
|
||||
'description' => __( 'Limit result set to specific IDs.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
'default' => array(),
|
||||
);
|
||||
|
||||
$query_params['offset'] = array(
|
||||
'description' => __( 'Offset the result set by a specific number of items.' ),
|
||||
'type' => 'integer',
|
||||
);
|
||||
|
||||
$query_params['order'] = array(
|
||||
'description' => __( 'Order sort attribute ascending or descending.' ),
|
||||
'type' => 'string',
|
||||
'default' => 'desc',
|
||||
'enum' => array( 'asc', 'desc' ),
|
||||
);
|
||||
|
||||
$query_params['orderby'] = array(
|
||||
'description' => __( 'Sort collection by object attribute.' ),
|
||||
'type' => 'string',
|
||||
'default' => 'date',
|
||||
'enum' => array(
|
||||
'date',
|
||||
'id',
|
||||
'include',
|
||||
'relevance',
|
||||
'slug',
|
||||
'include_slugs',
|
||||
'title',
|
||||
),
|
||||
);
|
||||
|
||||
return $query_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the post excerpt and prepare it for single post output.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param string $excerpt The post excerpt.
|
||||
* @param WP_Post $post Post revision object.
|
||||
* @return string Prepared excerpt or empty string.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
@ -0,0 +1,359 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Search_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class to search through all WordPress content via the REST API.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Search_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* ID property name.
|
||||
*/
|
||||
const PROP_ID = 'id';
|
||||
|
||||
/**
|
||||
* Title property name.
|
||||
*/
|
||||
const PROP_TITLE = 'title';
|
||||
|
||||
/**
|
||||
* URL property name.
|
||||
*/
|
||||
const PROP_URL = 'url';
|
||||
|
||||
/**
|
||||
* Type property name.
|
||||
*/
|
||||
const PROP_TYPE = 'type';
|
||||
|
||||
/**
|
||||
* Subtype property name.
|
||||
*/
|
||||
const PROP_SUBTYPE = 'subtype';
|
||||
|
||||
/**
|
||||
* Identifier for the 'any' type.
|
||||
*/
|
||||
const TYPE_ANY = 'any';
|
||||
|
||||
/**
|
||||
* Search handlers used by the controller.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @var array
|
||||
*/
|
||||
protected $search_handlers = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param array $search_handlers List of search handlers to use in the controller. Each search
|
||||
* handler instance must extend the `WP_REST_Search_Handler` class.
|
||||
*/
|
||||
public function __construct( array $search_handlers ) {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'search';
|
||||
|
||||
foreach ( $search_handlers as $search_handler ) {
|
||||
if ( ! $search_handler instanceof WP_REST_Search_Handler ) {
|
||||
|
||||
/* translators: %s: PHP class name */
|
||||
_doing_it_wrong( __METHOD__, sprintf( __( 'REST search handlers must extend the %s class.' ), 'WP_REST_Search_Handler' ), '5.0.0' );
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->search_handlers[ $search_handler->get_type() ] = $search_handler;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
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_permission_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to search content.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has search access, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_items_permission_check( $request ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a collection of search results.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$handler = $this->get_search_handler( $request );
|
||||
if ( is_wp_error( $handler ) ) {
|
||||
return $handler;
|
||||
}
|
||||
|
||||
$result = $handler->search_items( $request );
|
||||
|
||||
if ( ! isset( $result[ WP_REST_Search_Handler::RESULT_IDS ] ) || ! is_array( $result[ WP_REST_Search_Handler::RESULT_IDS ] ) || ! isset( $result[ WP_REST_Search_Handler::RESULT_TOTAL ] ) ) {
|
||||
return new WP_Error( 'rest_search_handler_error', __( 'Internal search handler error.' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
$ids = array_map( 'absint', $result[ WP_REST_Search_Handler::RESULT_IDS ] );
|
||||
|
||||
$results = array();
|
||||
foreach ( $ids as $id ) {
|
||||
$data = $this->prepare_item_for_response( $id, $request );
|
||||
$results[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
$total = (int) $result[ WP_REST_Search_Handler::RESULT_TOTAL ];
|
||||
$page = (int) $request['page'];
|
||||
$per_page = (int) $request['per_page'];
|
||||
$max_pages = ceil( $total / $per_page );
|
||||
|
||||
if ( $page > $max_pages && $total > 0 ) {
|
||||
return new WP_Error( 'rest_search_invalid_page_number', __( 'The page number requested is larger than the number of pages available.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $results );
|
||||
$response->header( 'X-WP-Total', $total );
|
||||
$response->header( 'X-WP-TotalPages', $max_pages );
|
||||
|
||||
$request_params = $request->get_query_params();
|
||||
$base = add_query_arg( $request_params, rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ) );
|
||||
|
||||
if ( $page > 1 ) {
|
||||
$prev_link = add_query_arg( 'page', $page - 1, $base );
|
||||
$response->link_header( 'prev', $prev_link );
|
||||
}
|
||||
if ( $page < $max_pages ) {
|
||||
$next_link = add_query_arg( 'page', $page + 1, $base );
|
||||
$response->link_header( 'next', $next_link );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single search result for response.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param int $id ID of the item to prepare.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $id, $request ) {
|
||||
$handler = $this->get_search_handler( $request );
|
||||
if ( is_wp_error( $handler ) ) {
|
||||
return new WP_REST_Response();
|
||||
}
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
$data = $handler->prepare_item( $id, $fields );
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
$links = $handler->prepare_item_links( $id );
|
||||
$links['collection'] = array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
);
|
||||
$response->add_links( $links );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$types = array();
|
||||
$subtypes = array();
|
||||
foreach ( $this->search_handlers as $search_handler ) {
|
||||
$types[] = $search_handler->get_type();
|
||||
$subtypes = array_merge( $subtypes, $search_handler->get_subtypes() );
|
||||
}
|
||||
|
||||
$types = array_unique( $types );
|
||||
$subtypes = array_unique( $subtypes );
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'search-result',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
self::PROP_ID => array(
|
||||
'description' => __( 'Unique identifier for the object.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
self::PROP_TITLE => array(
|
||||
'description' => __( 'The title for the object.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
self::PROP_URL => array(
|
||||
'description' => __( 'URL to the object.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => array( 'view', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
self::PROP_TYPE => array(
|
||||
'description' => __( 'Object type.' ),
|
||||
'type' => 'string',
|
||||
'enum' => $types,
|
||||
'context' => array( 'view', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
self::PROP_SUBTYPE => array(
|
||||
'description' => __( 'Object subtype.' ),
|
||||
'type' => 'string',
|
||||
'enum' => $subtypes,
|
||||
'context' => array( 'view', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the search results collection.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$types = array();
|
||||
$subtypes = array();
|
||||
foreach ( $this->search_handlers as $search_handler ) {
|
||||
$types[] = $search_handler->get_type();
|
||||
$subtypes = array_merge( $subtypes, $search_handler->get_subtypes() );
|
||||
}
|
||||
|
||||
$types = array_unique( $types );
|
||||
$subtypes = array_unique( $subtypes );
|
||||
|
||||
$query_params = parent::get_collection_params();
|
||||
|
||||
$query_params['context']['default'] = 'view';
|
||||
|
||||
$query_params[ self::PROP_TYPE ] = array(
|
||||
'default' => $types[0],
|
||||
'description' => __( 'Limit results to items of an object type.' ),
|
||||
'type' => 'string',
|
||||
'enum' => $types,
|
||||
);
|
||||
|
||||
$query_params[ self::PROP_SUBTYPE ] = array(
|
||||
'default' => self::TYPE_ANY,
|
||||
'description' => __( 'Limit results to items of one or more object subtypes.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'enum' => array_merge( $subtypes, array( self::TYPE_ANY ) ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'sanitize_callback' => array( $this, 'sanitize_subtypes' ),
|
||||
);
|
||||
|
||||
return $query_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the list of subtypes, to ensure only subtypes of the passed type are included.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param string|array $subtypes One or more subtypes.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @param string $parameter Parameter name.
|
||||
* @return array|WP_Error List of valid subtypes, or WP_Error object on failure.
|
||||
*/
|
||||
public function sanitize_subtypes( $subtypes, $request, $parameter ) {
|
||||
$subtypes = wp_parse_slug_list( $subtypes );
|
||||
|
||||
$subtypes = rest_parse_request_arg( $subtypes, $request, $parameter );
|
||||
if ( is_wp_error( $subtypes ) ) {
|
||||
return $subtypes;
|
||||
}
|
||||
|
||||
// 'any' overrides any other subtype.
|
||||
if ( in_array( self::TYPE_ANY, $subtypes, true ) ) {
|
||||
return array( self::TYPE_ANY );
|
||||
}
|
||||
|
||||
$handler = $this->get_search_handler( $request );
|
||||
if ( is_wp_error( $handler ) ) {
|
||||
return $handler;
|
||||
}
|
||||
|
||||
return array_intersect( $subtypes, $handler->get_subtypes() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the search handler to handle the current request.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Search_Handler|WP_Error Search handler for the request type, or WP_Error object on failure.
|
||||
*/
|
||||
protected function get_search_handler( $request ) {
|
||||
$type = $request->get_param( self::PROP_TYPE );
|
||||
|
||||
if ( ! $type || ! isset( $this->search_handlers[ $type ] ) ) {
|
||||
return new WP_Error( 'rest_search_invalid_type', __( 'Invalid type parameter.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
return $this->search_handlers[ $type ];
|
||||
}
|
||||
}
|
@ -0,0 +1,340 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Settings_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to manage a site's settings via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Settings_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'settings';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
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' ),
|
||||
)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read and manage settings.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return bool True if the request has read access for the item, otherwise false.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the settings.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return array|WP_Error Array on success, or WP_Error object on failure.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a value for output based off a schema array.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param mixed $value Value to prepare.
|
||||
* @param array $schema Schema to match.
|
||||
* @return mixed The prepared value.
|
||||
*/
|
||||
protected function prepare_value( $value, $schema ) {
|
||||
// If the value is not valid by the schema, set the value to null. Null
|
||||
// values are specifcally non-destructive so this will not cause overwriting
|
||||
// the current invalid value to null.
|
||||
if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) {
|
||||
return null;
|
||||
}
|
||||
return rest_sanitize_value_from_schema( $value, $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates settings for the settings object.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return array|WP_Error Array on success, or error object on failure.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* Allows hijacking the setting update logic and overriding the built-in behavior by
|
||||
* returning true.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param bool $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 for an option would have the same effect as
|
||||
* deleting the option from the database, and relying on the
|
||||
* default value.
|
||||
*/
|
||||
if ( is_null( $request[ $name ] ) ) {
|
||||
/*
|
||||
* A null value is returned in the response for any option
|
||||
* that has a non-scalar value.
|
||||
*
|
||||
* To protect clients from accidentally including the null
|
||||
* values from a response object in a request, we do not allow
|
||||
* options with values that don't pass validation to be updated to null.
|
||||
* Without this added protection a client could mistakenly
|
||||
* delete all options that have invalid values from the
|
||||
* database.
|
||||
*/
|
||||
if ( is_wp_error( rest_validate_value_from_schema( get_option( $args['option_name'], false ), $args['schema'] ) ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_stored_value',
|
||||
sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
delete_option( $args['option_name'] );
|
||||
} else {
|
||||
update_option( $args['option_name'], $request[ $name ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->get_item( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all of the registered options for the Settings API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Array of registered options.
|
||||
*/
|
||||
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', 'integer', 'string', 'boolean', 'array', 'object' ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rest_args['schema'] = $this->set_additional_properties_to_false( $rest_args['schema'] );
|
||||
|
||||
$rest_options[ $rest_args['name'] ] = $rest_args;
|
||||
}
|
||||
|
||||
return $rest_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the site setting schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
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'];
|
||||
$schema['properties'][ $option_name ]['arg_options'] = array(
|
||||
'sanitize_callback' => array( $this, 'sanitize_callback' ),
|
||||
);
|
||||
}
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom sanitize callback used for all options to allow the use of 'null'.
|
||||
*
|
||||
* By default, the schema of settings will throw an error if a value is set to
|
||||
* `null` as it's not a valid value for something like "type => string". We
|
||||
* provide a wrapper sanitizer to whitelist the use of `null`.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param mixed $value The value for the setting.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
* @param string $param The parameter name.
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
public function sanitize_callback( $value, $request, $param ) {
|
||||
if ( is_null( $value ) ) {
|
||||
return $value;
|
||||
}
|
||||
return rest_parse_request_arg( $value, $request, $param );
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively add additionalProperties = false to all objects in a schema.
|
||||
*
|
||||
* This is need to restrict properties of objects in settings values to only
|
||||
* registered items, as the REST API will allow additional properties by
|
||||
* default.
|
||||
*
|
||||
* @since 4.9.0
|
||||
*
|
||||
* @param array $schema The schema array.
|
||||
* @return array
|
||||
*/
|
||||
protected function set_additional_properties_to_false( $schema ) {
|
||||
switch ( $schema['type'] ) {
|
||||
case 'object':
|
||||
foreach ( $schema['properties'] as $key => $child_schema ) {
|
||||
$schema['properties'][ $key ] = $this->set_additional_properties_to_false( $child_schema );
|
||||
}
|
||||
$schema['additionalProperties'] = false;
|
||||
break;
|
||||
case 'array':
|
||||
$schema['items'] = $this->set_additional_properties_to_false( $schema['items'] );
|
||||
break;
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
@ -0,0 +1,396 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Taxonomies_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to manage taxonomies via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Taxonomies_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'taxonomies';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
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(
|
||||
'args' => array(
|
||||
'taxonomy' => array(
|
||||
'description' => __( 'An alphanumeric identifier for the taxonomy.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given request has permission to read taxonomies.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
|
||||
*/
|
||||
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->assign_terms ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return new WP_Error( 'rest_cannot_view', __( 'Sorry, you are not allowed to manage terms in this taxonomy.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all public taxonomies.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
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->assign_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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to a taxonomy.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has read access for the item, otherwise false or WP_Error object.
|
||||
*/
|
||||
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->assign_terms ) ) {
|
||||
return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to manage terms in this taxonomy.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific taxonomy.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
$tax_obj = get_taxonomy( $request['taxonomy'] );
|
||||
if ( empty( $tax_obj ) ) {
|
||||
return new WP_Error( 'rest_taxonomy_invalid', __( 'Invalid taxonomy.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
$data = $this->prepare_item_for_response( $tax_obj, $request );
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a taxonomy object for serialization.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param stdClass $taxonomy Taxonomy data.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $taxonomy, $request ) {
|
||||
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( in_array( 'name', $fields, true ) ) {
|
||||
$data['name'] = $taxonomy->label;
|
||||
}
|
||||
|
||||
if ( in_array( 'slug', $fields, true ) ) {
|
||||
$data['slug'] = $taxonomy->name;
|
||||
}
|
||||
|
||||
if ( in_array( 'capabilities', $fields, true ) ) {
|
||||
$data['capabilities'] = $taxonomy->cap;
|
||||
}
|
||||
|
||||
if ( in_array( 'description', $fields, true ) ) {
|
||||
$data['description'] = $taxonomy->description;
|
||||
}
|
||||
|
||||
if ( in_array( 'labels', $fields, true ) ) {
|
||||
$data['labels'] = $taxonomy->labels;
|
||||
}
|
||||
|
||||
if ( in_array( 'types', $fields, true ) ) {
|
||||
$data['types'] = $taxonomy->object_type;
|
||||
}
|
||||
|
||||
if ( in_array( 'show_cloud', $fields, true ) ) {
|
||||
$data['show_cloud'] = $taxonomy->show_tagcloud;
|
||||
}
|
||||
|
||||
if ( in_array( 'hierarchical', $fields, true ) ) {
|
||||
$data['hierarchical'] = $taxonomy->hierarchical;
|
||||
}
|
||||
|
||||
if ( in_array( 'rest_base', $fields, true ) ) {
|
||||
$data['rest_base'] = $base;
|
||||
}
|
||||
|
||||
if ( in_array( 'visibility', $fields, true ) ) {
|
||||
$data['visibility'] = array(
|
||||
'public' => (bool) $taxonomy->public,
|
||||
'publicly_queryable' => (bool) $taxonomy->publicly_queryable,
|
||||
'show_admin_column' => (bool) $taxonomy->show_admin_column,
|
||||
'show_in_nav_menus' => (bool) $taxonomy->show_in_nav_menus,
|
||||
'show_in_quick_edit' => (bool) $taxonomy->show_in_quick_edit,
|
||||
'show_ui' => (bool) $taxonomy->show_ui,
|
||||
);
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
$response->add_links(
|
||||
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 ) ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters a taxonomy returned from the REST API.
|
||||
*
|
||||
* Allows modification of the taxonomy data right before it is returned.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the taxonomy's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
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 taxonomy.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'A human-readable description of the taxonomy.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'hierarchical' => array(
|
||||
'description' => __( 'Whether or not the taxonomy should have children.' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'labels' => array(
|
||||
'description' => __( 'Human-readable labels for the taxonomy for various contexts.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'The title for the taxonomy.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'slug' => array(
|
||||
'description' => __( 'An alphanumeric identifier for the taxonomy.' ),
|
||||
'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 the taxonomy.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'rest_base' => array(
|
||||
'description' => __( 'REST base route for the taxonomy.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'visibility' => array(
|
||||
'description' => __( 'The visibility settings for the taxonomy.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'public' => array(
|
||||
'description' => __( 'Whether a taxonomy is intended for use publicly either via the admin interface or by front-end users.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'publicly_queryable' => array(
|
||||
'description' => __( 'Whether the taxonomy is publicly queryable.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'show_ui' => array(
|
||||
'description' => __( 'Whether to generate a default UI for managing this taxonomy.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'show_admin_column' => array(
|
||||
'description' => __( 'Whether to allow automatic creation of taxonomy columns on associated post-types table.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'show_in_nav_menus' => array(
|
||||
'description' => __( 'Whether to make the taxonomy available for selection in navigation menus.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'show_in_quick_edit' => array(
|
||||
'description' => __( 'Whether to show the taxonomy in the quick/bulk edit panel.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for collections.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
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 taxonomies associated with a specific post type.' ),
|
||||
'type' => 'string',
|
||||
);
|
||||
return $new_params;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,237 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Themes_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to manage themes via the REST API.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Themes_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'themes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
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_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read the theme.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has read access for the item, otherwise WP_Error object.
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
if ( ! is_user_logged_in() || ! current_user_can( 'edit_posts' ) ) {
|
||||
return new WP_Error( 'rest_user_cannot_view', __( 'Sorry, you are not allowed to view themes.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a collection of themes.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
// Retrieve the list of registered collection query parameters.
|
||||
$registered = $this->get_collection_params();
|
||||
$themes = array();
|
||||
|
||||
if ( isset( $registered['status'], $request['status'] ) && in_array( 'active', $request['status'], true ) ) {
|
||||
$active_theme = wp_get_theme();
|
||||
$active_theme = $this->prepare_item_for_response( $active_theme, $request );
|
||||
$themes[] = $this->prepare_response_for_collection( $active_theme );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $themes );
|
||||
|
||||
$response->header( 'X-WP-Total', count( $themes ) );
|
||||
$response->header( 'X-WP-TotalPages', count( $themes ) );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single theme output for response.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_Theme $theme Theme object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $theme, $request ) {
|
||||
$data = array();
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
if ( in_array( 'theme_supports', $fields, true ) ) {
|
||||
$formats = get_theme_support( 'post-formats' );
|
||||
$formats = is_array( $formats ) ? array_values( $formats[0] ) : array();
|
||||
$formats = array_merge( array( 'standard' ), $formats );
|
||||
$data['theme_supports']['formats'] = $formats;
|
||||
|
||||
$data['theme_supports']['post-thumbnails'] = false;
|
||||
$data['theme_supports']['responsive-embeds'] = (bool) get_theme_support( 'responsive-embeds' );
|
||||
$post_thumbnails = get_theme_support( 'post-thumbnails' );
|
||||
|
||||
if ( $post_thumbnails ) {
|
||||
// $post_thumbnails can contain a nested array of post types.
|
||||
// e.g. array( array( 'post', 'page' ) ).
|
||||
$data['theme_supports']['post-thumbnails'] = is_array( $post_thumbnails ) ? $post_thumbnails[0] : true;
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
/**
|
||||
* Filters theme data returned from the REST API.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param WP_Theme $theme Theme object used to create response.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_theme', $response, $theme, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the theme's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'theme',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'theme_supports' => array(
|
||||
'description' => __( 'Features supported by this theme.' ),
|
||||
'type' => 'array',
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'formats' => array(
|
||||
'description' => __( 'Post formats supported.' ),
|
||||
'type' => 'array',
|
||||
'readonly' => true,
|
||||
),
|
||||
'post-thumbnails' => array(
|
||||
'description' => __( 'Whether the theme supports post thumbnails.' ),
|
||||
'type' => array( 'array', 'bool' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'responsive-embeds' => array(
|
||||
'description' => __( 'Whether the theme supports responsive embedded content.' ),
|
||||
'type' => 'bool',
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the search params for the themes collection.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$query_params = parent::get_collection_params();
|
||||
|
||||
$query_params['status'] = array(
|
||||
'description' => __( 'Limit result set to themes assigned one or more statuses.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'enum' => array( 'active' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'required' => true,
|
||||
'sanitize_callback' => array( $this, 'sanitize_theme_status' ),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter collection parameters for the themes controller.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param array $query_params JSON Schema-formatted collection parameters.
|
||||
*/
|
||||
return apply_filters( 'rest_themes_collection_params', $query_params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes and validates the list of theme status.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param string|array $statuses One or more theme statuses.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @param string $parameter Additional parameter to pass to validation.
|
||||
* @return array|WP_Error A list of valid statuses, otherwise WP_Error object.
|
||||
*/
|
||||
public function sanitize_theme_status( $statuses, $request, $parameter ) {
|
||||
$statuses = wp_parse_slug_list( $statuses );
|
||||
|
||||
foreach ( $statuses as $status ) {
|
||||
$result = rest_validate_request_arg( $status, $request, $parameter );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $statuses;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Comment_Meta_Fields class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class to manage comment meta via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Meta_Fields
|
||||
*/
|
||||
class WP_REST_Comment_Meta_Fields extends WP_REST_Meta_Fields {
|
||||
|
||||
/**
|
||||
* Retrieves the object type for comment meta.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The meta type.
|
||||
*/
|
||||
protected function get_meta_type() {
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object meta subtype.
|
||||
*
|
||||
* @since 4.9.8
|
||||
*
|
||||
* @return string 'comment' There are no subtypes.
|
||||
*/
|
||||
protected function get_meta_subtype() {
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type for register_rest_field() in the context of comments.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The REST field type.
|
||||
*/
|
||||
public function get_rest_field_type() {
|
||||
return 'comment';
|
||||
}
|
||||
}
|
@ -0,0 +1,499 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Meta_Fields class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class to manage meta values for an object via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*/
|
||||
abstract class WP_REST_Meta_Fields {
|
||||
|
||||
/**
|
||||
* Retrieves the object meta type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string One of 'post', 'comment', 'term', 'user', or anything
|
||||
* else supported by `_get_meta_table()`.
|
||||
*/
|
||||
abstract protected function get_meta_type();
|
||||
|
||||
/**
|
||||
* Retrieves the object meta subtype.
|
||||
*
|
||||
* @since 4.9.8
|
||||
*
|
||||
* @return string Subtype for the meta type, or empty string if no specific subtype.
|
||||
*/
|
||||
protected function get_meta_subtype() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object type for register_rest_field().
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The REST field type, such as post type name, taxonomy name, 'comment', or `user`.
|
||||
*/
|
||||
abstract protected function get_rest_field_type();
|
||||
|
||||
/**
|
||||
* Registers the meta field.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see register_rest_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(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the meta field value.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param int $object_id Object ID to fetch meta for.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Error|object Object containing the meta values by name, otherwise WP_Error object.
|
||||
*/
|
||||
public function get_value( $object_id, $request ) {
|
||||
$fields = $this->get_registered_fields();
|
||||
$response = array();
|
||||
|
||||
foreach ( $fields as $meta_key => $args ) {
|
||||
$name = $args['name'];
|
||||
$all_values = get_metadata( $this->get_meta_type(), $object_id, $meta_key, 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 $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a meta value for a 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.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param mixed $value Meta 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates meta values.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $meta Array of meta parsed from the request.
|
||||
* @param int $object_id Object ID to fetch meta for.
|
||||
* @return WP_Error|null WP_Error if one occurs, null on success.
|
||||
*/
|
||||
public function update_value( $meta, $object_id ) {
|
||||
$fields = $this->get_registered_fields();
|
||||
foreach ( $fields as $meta_key => $args ) {
|
||||
$name = $args['name'];
|
||||
if ( ! array_key_exists( $name, $meta ) ) {
|
||||
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( $meta[ $name ] ) ) {
|
||||
$result = $this->delete_meta_value( $object_id, $meta_key, $name );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_valid = rest_validate_value_from_schema( $meta[ $name ], $args['schema'], 'meta.' . $name );
|
||||
if ( is_wp_error( $is_valid ) ) {
|
||||
$is_valid->add_data( array( 'status' => 400 ) );
|
||||
return $is_valid;
|
||||
}
|
||||
|
||||
$value = rest_sanitize_value_from_schema( $meta[ $name ], $args['schema'] );
|
||||
|
||||
if ( $args['single'] ) {
|
||||
$result = $this->update_meta_value( $object_id, $meta_key, $name, $value );
|
||||
} else {
|
||||
$result = $this->update_multi_meta_value( $object_id, $meta_key, $name, $value );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a meta value for an object.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param int $object_id Object ID the field belongs to.
|
||||
* @param string $meta_key Key for the field.
|
||||
* @param string $name Name for the field that is exposed in the REST API.
|
||||
* @return bool|WP_Error True if meta field is deleted, WP_Error otherwise.
|
||||
*/
|
||||
protected function delete_meta_value( $object_id, $meta_key, $name ) {
|
||||
$meta_type = $this->get_meta_type();
|
||||
if ( ! current_user_can( "delete_{$meta_type}_meta", $object_id, $meta_key ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_delete',
|
||||
/* translators: %s: custom field key */
|
||||
sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),
|
||||
array(
|
||||
'key' => $name,
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! delete_metadata( $meta_type, $object_id, wp_slash( $meta_key ) ) ) {
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates multiple meta values for an object.
|
||||
*
|
||||
* Alters the list of values in the database to match the list of provided values.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param int $object_id Object ID to update.
|
||||
* @param string $meta_key Key for the custom field.
|
||||
* @param string $name Name for the field that is exposed in the REST API.
|
||||
* @param array $values List of values to update to.
|
||||
* @return bool|WP_Error True if meta fields are updated, WP_Error otherwise.
|
||||
*/
|
||||
protected function update_multi_meta_value( $object_id, $meta_key, $name, $values ) {
|
||||
$meta_type = $this->get_meta_type();
|
||||
if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_update',
|
||||
/* translators: %s: custom field key */
|
||||
sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),
|
||||
array(
|
||||
'key' => $name,
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$current = get_metadata( $meta_type, $object_id, $meta_key, 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( $meta_type, $object_id, wp_slash( $meta_key ), 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( $meta_type, $object_id, wp_slash( $meta_key ), 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a meta value for an object.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param int $object_id Object ID to update.
|
||||
* @param string $meta_key Key for the custom field.
|
||||
* @param string $name Name for the field that is exposed in the REST API.
|
||||
* @param mixed $value Updated value.
|
||||
* @return bool|WP_Error True if the meta field was updated, WP_Error otherwise.
|
||||
*/
|
||||
protected function update_meta_value( $object_id, $meta_key, $name, $value ) {
|
||||
$meta_type = $this->get_meta_type();
|
||||
if ( ! current_user_can( "edit_{$meta_type}_meta", $object_id, $meta_key ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_update',
|
||||
/* translators: %s: custom field key */
|
||||
sprintf( __( 'Sorry, you are not allowed to edit the %s custom field.' ), $name ),
|
||||
array(
|
||||
'key' => $name,
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// 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 );
|
||||
$subtype = get_object_subtype( $meta_type, $object_id );
|
||||
|
||||
if ( 1 === count( $old_value ) ) {
|
||||
if ( (string) sanitize_meta( $meta_key, $value, $meta_type, $subtype ) === $old_value[0] ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! update_metadata( $meta_type, $object_id, wp_slash( $meta_key ), 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all the registered meta fields.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Registered fields.
|
||||
*/
|
||||
protected function get_registered_fields() {
|
||||
$registered = array();
|
||||
|
||||
$meta_type = $this->get_meta_type();
|
||||
$meta_subtype = $this->get_meta_subtype();
|
||||
|
||||
$meta_keys = get_registered_meta_keys( $meta_type );
|
||||
if ( ! empty( $meta_subtype ) ) {
|
||||
$meta_keys = array_merge( $meta_keys, get_registered_meta_keys( $meta_type, $meta_subtype ) );
|
||||
}
|
||||
|
||||
foreach ( $meta_keys 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'],
|
||||
'type' => ! empty( $args['type'] ) ? $args['type'] : null,
|
||||
'schema' => array(),
|
||||
'prepare_callback' => array( $this, 'prepare_value' ),
|
||||
);
|
||||
|
||||
$default_schema = array(
|
||||
'type' => $default_args['type'],
|
||||
'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'] );
|
||||
|
||||
$type = ! empty( $rest_args['type'] ) ? $rest_args['type'] : null;
|
||||
$type = ! empty( $rest_args['schema']['type'] ) ? $rest_args['schema']['type'] : $type;
|
||||
|
||||
if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number' ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $rest_args['single'] ) ) {
|
||||
$rest_args['schema']['items'] = array(
|
||||
'type' => $rest_args['type'],
|
||||
);
|
||||
$rest_args['schema']['type'] = 'array';
|
||||
}
|
||||
|
||||
$registered[ $name ] = $rest_args;
|
||||
}
|
||||
|
||||
return $registered;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object's meta schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Field schema data.
|
||||
*/
|
||||
public function get_field_schema() {
|
||||
$fields = $this->get_registered_fields();
|
||||
|
||||
$schema = array(
|
||||
'description' => __( 'Meta fields.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'properties' => array(),
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => null,
|
||||
'validate_callback' => array( $this, 'check_meta_is_array' ),
|
||||
),
|
||||
);
|
||||
|
||||
foreach ( $fields as $args ) {
|
||||
$schema['properties'][ $args['name'] ] = $args['schema'];
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a meta value for output.
|
||||
*
|
||||
* Default preparation for meta fields. Override by passing the
|
||||
* `prepare_callback` in your `show_in_rest` options.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @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. If a non-JsonSerializable object, null.
|
||||
*/
|
||||
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 'integer':
|
||||
$value = (int) $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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the 'meta' value of a request is an associative array.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param mixed $value The meta value submitted in the request.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @param string $param The parameter name.
|
||||
* @return WP_Error|string The meta array, if valid, otherwise an error.
|
||||
*/
|
||||
public function check_meta_is_array( $value, $request, $param ) {
|
||||
if ( ! is_array( $value ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
@ -0,0 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Post_Meta_Fields class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to manage meta values for posts via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Meta_Fields
|
||||
*/
|
||||
class WP_REST_Post_Meta_Fields extends WP_REST_Meta_Fields {
|
||||
|
||||
/**
|
||||
* Post type to register fields for.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var string
|
||||
*/
|
||||
protected $post_type;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param string $post_type Post type to register fields for.
|
||||
*/
|
||||
public function __construct( $post_type ) {
|
||||
$this->post_type = $post_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object meta type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The meta type.
|
||||
*/
|
||||
protected function get_meta_type() {
|
||||
return 'post';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object meta subtype.
|
||||
*
|
||||
* @since 4.9.8
|
||||
*
|
||||
* @return string Subtype for the meta type, or empty string if no specific subtype.
|
||||
*/
|
||||
protected function get_meta_subtype() {
|
||||
return $this->post_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type for register_rest_field().
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see register_rest_field()
|
||||
*
|
||||
* @return string The REST field type.
|
||||
*/
|
||||
public function get_rest_field_type() {
|
||||
return $this->post_type;
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Term_Meta_Fields class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to manage meta values for terms via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Meta_Fields
|
||||
*/
|
||||
class WP_REST_Term_Meta_Fields extends WP_REST_Meta_Fields {
|
||||
|
||||
/**
|
||||
* Taxonomy to register fields for.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var string
|
||||
*/
|
||||
protected $taxonomy;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param string $taxonomy Taxonomy to register fields for.
|
||||
*/
|
||||
public function __construct( $taxonomy ) {
|
||||
$this->taxonomy = $taxonomy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object meta type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The meta type.
|
||||
*/
|
||||
protected function get_meta_type() {
|
||||
return 'term';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object meta subtype.
|
||||
*
|
||||
* @since 4.9.8
|
||||
*
|
||||
* @return string Subtype for the meta type, or empty string if no specific subtype.
|
||||
*/
|
||||
protected function get_meta_subtype() {
|
||||
return $this->taxonomy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type for register_rest_field().
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The REST field type.
|
||||
*/
|
||||
public function get_rest_field_type() {
|
||||
return 'post_tag' === $this->taxonomy ? 'tag' : $this->taxonomy;
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_User_Meta_Fields class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to manage meta values for users via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Meta_Fields
|
||||
*/
|
||||
class WP_REST_User_Meta_Fields extends WP_REST_Meta_Fields {
|
||||
|
||||
/**
|
||||
* Retrieves the object meta type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The user meta type.
|
||||
*/
|
||||
protected function get_meta_type() {
|
||||
return 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object meta subtype.
|
||||
*
|
||||
* @since 4.9.8
|
||||
*
|
||||
* @return string 'user' There are no subtypes.
|
||||
*/
|
||||
protected function get_meta_subtype() {
|
||||
return 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the type for register_rest_field().
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The user REST field type.
|
||||
*/
|
||||
public function get_rest_field_type() {
|
||||
return 'user';
|
||||
}
|
||||
}
|
@ -0,0 +1,204 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Post_Search_Handler class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class representing a search handler for posts in the REST API.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
class WP_REST_Post_Search_Handler extends WP_REST_Search_Handler {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->type = 'post';
|
||||
|
||||
// Support all public post types except attachments.
|
||||
$this->subtypes = array_diff(
|
||||
array_values(
|
||||
get_post_types(
|
||||
array(
|
||||
'public' => true,
|
||||
'show_in_rest' => true,
|
||||
),
|
||||
'names'
|
||||
)
|
||||
),
|
||||
array( 'attachment' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the object type content for a given search request.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full REST request.
|
||||
* @return array Associative array containing an `WP_REST_Search_Handler::RESULT_IDS` containing
|
||||
* an array of found IDs and `WP_REST_Search_Handler::RESULT_TOTAL` containing the
|
||||
* total count for the matching search results.
|
||||
*/
|
||||
public function search_items( WP_REST_Request $request ) {
|
||||
|
||||
// Get the post types to search for the current request.
|
||||
$post_types = $request[ WP_REST_Search_Controller::PROP_SUBTYPE ];
|
||||
if ( in_array( WP_REST_Search_Controller::TYPE_ANY, $post_types, true ) ) {
|
||||
$post_types = $this->subtypes;
|
||||
}
|
||||
|
||||
$query_args = array(
|
||||
'post_type' => $post_types,
|
||||
'post_status' => 'publish',
|
||||
'paged' => (int) $request['page'],
|
||||
'posts_per_page' => (int) $request['per_page'],
|
||||
'ignore_sticky_posts' => true,
|
||||
'fields' => 'ids',
|
||||
);
|
||||
|
||||
if ( ! empty( $request['search'] ) ) {
|
||||
$query_args['s'] = $request['search'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the query arguments for a search request.
|
||||
*
|
||||
* Enables adding extra arguments or setting defaults for a post search request.
|
||||
*
|
||||
* @since 5.1.0
|
||||
*
|
||||
* @param array $query_args Key value array of query var to query value.
|
||||
* @param WP_REST_Request $request The request used.
|
||||
*/
|
||||
$query_args = apply_filters( 'rest_post_search_query', $query_args, $request );
|
||||
|
||||
$query = new WP_Query();
|
||||
$found_ids = $query->query( $query_args );
|
||||
$total = $query->found_posts;
|
||||
|
||||
return array(
|
||||
self::RESULT_IDS => $found_ids,
|
||||
self::RESULT_TOTAL => $total,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the search result for a given ID.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param int $id Item ID.
|
||||
* @param array $fields Fields to include for the item.
|
||||
* @return array Associative array containing all fields for the item.
|
||||
*/
|
||||
public function prepare_item( $id, array $fields ) {
|
||||
$post = get_post( $id );
|
||||
|
||||
$data = array();
|
||||
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_ID ] = (int) $post->ID;
|
||||
}
|
||||
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) {
|
||||
if ( post_type_supports( $post->post_type, 'title' ) ) {
|
||||
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
|
||||
$data[ WP_REST_Search_Controller::PROP_TITLE ] = get_the_title( $post->ID );
|
||||
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
|
||||
} else {
|
||||
$data[ WP_REST_Search_Controller::PROP_TITLE ] = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_URL ] = get_permalink( $post->ID );
|
||||
}
|
||||
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_TYPE ] = $this->type;
|
||||
}
|
||||
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_SUBTYPE, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_SUBTYPE ] = $post->post_type;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the search result of a given ID.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param int $id Item ID.
|
||||
* @return array Links for the given item.
|
||||
*/
|
||||
public function prepare_item_links( $id ) {
|
||||
$post = get_post( $id );
|
||||
|
||||
$links = array();
|
||||
|
||||
$item_route = $this->detect_rest_item_route( $post );
|
||||
if ( ! empty( $item_route ) ) {
|
||||
$links['self'] = array(
|
||||
'href' => rest_url( $item_route ),
|
||||
'embeddable' => true,
|
||||
);
|
||||
}
|
||||
|
||||
$links['about'] = array(
|
||||
'href' => rest_url( 'wp/v2/types/' . $post->post_type ),
|
||||
);
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overwrites 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.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return string Protected title format.
|
||||
*/
|
||||
public function protected_title_format() {
|
||||
return '%s';
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to detect the route to access a single item.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_Post $post Post object.
|
||||
* @return string REST route relative to the REST base URI, or empty string if unknown.
|
||||
*/
|
||||
protected function detect_rest_item_route( $post ) {
|
||||
$post_type = get_post_type_object( $post->post_type );
|
||||
if ( ! $post_type ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// It's currently impossible to detect the REST URL from a custom controller.
|
||||
if ( ! empty( $post_type->rest_controller_class ) && 'WP_REST_Posts_Controller' !== $post_type->rest_controller_class ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$namespace = 'wp/v2';
|
||||
$rest_base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
|
||||
|
||||
return sprintf( '%s/%s/%d', $namespace, $rest_base, $post->ID );
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Search_Handler class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core base class representing a search handler for an object type in the REST API.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
abstract class WP_REST_Search_Handler {
|
||||
|
||||
/**
|
||||
* Field containing the IDs in the search result.
|
||||
*/
|
||||
const RESULT_IDS = 'ids';
|
||||
|
||||
/**
|
||||
* Field containing the total count in the search result.
|
||||
*/
|
||||
const RESULT_TOTAL = 'total';
|
||||
|
||||
/**
|
||||
* Object type managed by this search handler.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @var string
|
||||
*/
|
||||
protected $type = '';
|
||||
|
||||
/**
|
||||
* Object subtypes managed by this search handler.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @var array
|
||||
*/
|
||||
protected $subtypes = array();
|
||||
|
||||
/**
|
||||
* Gets the object type managed by this search handler.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return string Object type identifier.
|
||||
*/
|
||||
public function get_type() {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the object subtypes managed by this search handler.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Array of object subtype identifiers.
|
||||
*/
|
||||
public function get_subtypes() {
|
||||
return $this->subtypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the object type content for a given search request.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full REST request.
|
||||
* @return array Associative array containing an `WP_REST_Search_Handler::RESULT_IDS` containing
|
||||
* an array of found IDs and `WP_REST_Search_Handler::RESULT_TOTAL` containing the
|
||||
* total count for the matching search results.
|
||||
*/
|
||||
abstract public function search_items( WP_REST_Request $request );
|
||||
|
||||
/**
|
||||
* Prepares the search result for a given ID.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param int $id Item ID.
|
||||
* @param array $fields Fields to include for the item.
|
||||
* @return array Associative array containing all fields for the item.
|
||||
*/
|
||||
abstract public function prepare_item( $id, array $fields );
|
||||
|
||||
/**
|
||||
* Prepares links for the search result of a given ID.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param int $id Item ID.
|
||||
* @return array Links for the given item.
|
||||
*/
|
||||
abstract public function prepare_item_links( $id );
|
||||
}
|
Reference in New Issue
Block a user