first commit
This commit is contained in:
1063
wp-includes/rest-api/class-wp-rest-request.php
Normal file
1063
wp-includes/rest-api/class-wp-rest-request.php
Normal file
File diff suppressed because it is too large
Load Diff
293
wp-includes/rest-api/class-wp-rest-response.php
Normal file
293
wp-includes/rest-api/class-wp-rest-response.php
Normal file
@ -0,0 +1,293 @@
|
||||
<?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 associative 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 REST 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 normalize 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 REST API.
|
||||
*/
|
||||
$additional = apply_filters( 'rest_response_link_curies', array() );
|
||||
|
||||
return array_merge( $curies, $additional );
|
||||
}
|
||||
}
|
1914
wp-includes/rest-api/class-wp-rest-server.php
Normal file
1914
wp-includes/rest-api/class-wp-rest-server.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,848 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Application_Passwords_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.6.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class to access a user's application passwords via the REST API.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Application_Passwords_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Application Passwords controller constructor.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'users/(?P<user_id>(?:[\d]+|me))/application-passwords';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the REST API routes for the application passwords controller.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'create_item' ),
|
||||
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema(),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::DELETABLE,
|
||||
'callback' => array( $this, 'delete_items' ),
|
||||
'permission_callback' => array( $this, 'delete_items_permissions_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/introspect',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_current_item' ),
|
||||
'permission_callback' => array( $this, 'get_current_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<uuid>[\w\-]+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'update_item' ),
|
||||
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::DELETABLE,
|
||||
'callback' => array( $this, 'delete_item' ),
|
||||
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get application passwords.
|
||||
*
|
||||
* @since 5.6.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 ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'list_app_passwords', $user->ID ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_list_application_passwords',
|
||||
__( 'Sorry, you are not allowed to list application passwords for this user.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a collection of application passwords.
|
||||
*
|
||||
* @since 5.6.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 ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$passwords = WP_Application_Passwords::get_user_application_passwords( $user->ID );
|
||||
$response = array();
|
||||
|
||||
foreach ( $passwords as $password ) {
|
||||
$response[] = $this->prepare_response_for_collection(
|
||||
$this->prepare_item_for_response( $password, $request )
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get a specific application password.
|
||||
*
|
||||
* @since 5.6.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, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'read_app_password', $user->ID, $request['uuid'] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_read_application_password',
|
||||
__( 'Sorry, you are not allowed to read this application password.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves one application password from the collection.
|
||||
*
|
||||
* @since 5.6.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 ) {
|
||||
$password = $this->get_application_password( $request );
|
||||
|
||||
if ( is_wp_error( $password ) ) {
|
||||
return $password;
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response( $password, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to create application passwords.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
|
||||
*/
|
||||
public function create_item_permissions_check( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'create_app_password', $user->ID ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_create_application_passwords',
|
||||
__( 'Sorry, you are not allowed to create application passwords for this user.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an application password.
|
||||
*
|
||||
* @since 5.6.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 ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$prepared = $this->prepare_item_for_database( $request );
|
||||
|
||||
if ( is_wp_error( $prepared ) ) {
|
||||
return $prepared;
|
||||
}
|
||||
|
||||
$created = WP_Application_Passwords::create_new_application_password( $user->ID, wp_slash( (array) $prepared ) );
|
||||
|
||||
if ( is_wp_error( $created ) ) {
|
||||
return $created;
|
||||
}
|
||||
|
||||
$password = $created[0];
|
||||
$item = WP_Application_Passwords::get_user_application_password( $user->ID, $created[1]['uuid'] );
|
||||
|
||||
$item['new_password'] = WP_Application_Passwords::chunk_password( $password );
|
||||
$fields_update = $this->update_additional_fields_for_object( $item, $request );
|
||||
|
||||
if ( is_wp_error( $fields_update ) ) {
|
||||
return $fields_update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after a single application password is completely created or updated via the REST API.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param array $item Inserted or updated password item.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @param bool $creating True when creating an application password, false when updating.
|
||||
*/
|
||||
do_action( 'rest_after_insert_application_password', $item, $request, true );
|
||||
|
||||
$request->set_param( 'context', 'edit' );
|
||||
$response = $this->prepare_item_for_response( $item, $request );
|
||||
|
||||
$response->set_status( 201 );
|
||||
$response->header( 'Location', $response->get_links()['self'][0]['href'] );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to update application passwords.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
|
||||
*/
|
||||
public function update_item_permissions_check( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'edit_app_password', $user->ID, $request['uuid'] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_edit_application_password',
|
||||
__( 'Sorry, you are not allowed to edit this application password.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an application password.
|
||||
*
|
||||
* @since 5.6.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 update_item( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$item = $this->get_application_password( $request );
|
||||
|
||||
if ( is_wp_error( $item ) ) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
$prepared = $this->prepare_item_for_database( $request );
|
||||
|
||||
if ( is_wp_error( $prepared ) ) {
|
||||
return $prepared;
|
||||
}
|
||||
|
||||
$saved = WP_Application_Passwords::update_application_password( $user->ID, $item['uuid'], wp_slash( (array) $prepared ) );
|
||||
|
||||
if ( is_wp_error( $saved ) ) {
|
||||
return $saved;
|
||||
}
|
||||
|
||||
$fields_update = $this->update_additional_fields_for_object( $item, $request );
|
||||
|
||||
if ( is_wp_error( $fields_update ) ) {
|
||||
return $fields_update;
|
||||
}
|
||||
|
||||
$item = WP_Application_Passwords::get_user_application_password( $user->ID, $item['uuid'] );
|
||||
|
||||
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php */
|
||||
do_action( 'rest_after_insert_application_password', $item, $request, false );
|
||||
|
||||
$request->set_param( 'context', 'edit' );
|
||||
return $this->prepare_item_for_response( $item, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to delete all application passwords for a user.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function delete_items_permissions_check( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'delete_app_passwords', $user->ID ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_delete_application_passwords',
|
||||
__( 'Sorry, you are not allowed to delete application passwords for this user.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all application passwords for a user.
|
||||
*
|
||||
* @since 5.6.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 delete_items( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$deleted = WP_Application_Passwords::delete_all_application_passwords( $user->ID );
|
||||
|
||||
if ( is_wp_error( $deleted ) ) {
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'deleted' => true,
|
||||
'count' => $deleted,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to delete a specific application password for a user.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function delete_item_permissions_check( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'delete_app_password', $user->ID, $request['uuid'] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_delete_application_password',
|
||||
__( 'Sorry, you are not allowed to delete this application password.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes an application password for a user.
|
||||
*
|
||||
* @since 5.6.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 delete_item( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$password = $this->get_application_password( $request );
|
||||
|
||||
if ( is_wp_error( $password ) ) {
|
||||
return $password;
|
||||
}
|
||||
|
||||
$request->set_param( 'context', 'edit' );
|
||||
$previous = $this->prepare_item_for_response( $password, $request );
|
||||
$deleted = WP_Application_Passwords::delete_application_password( $user->ID, $password['uuid'] );
|
||||
|
||||
if ( is_wp_error( $deleted ) ) {
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'deleted' => true,
|
||||
'previous' => $previous->get_data(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get the currently used application password for a user.
|
||||
*
|
||||
* @since 5.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, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_current_item_permissions_check( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
if ( get_current_user_id() !== $user->ID ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_introspect_app_password_for_non_authenticated_user',
|
||||
__( 'The authenticated application password can only be introspected for the current user.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the application password being currently used for authentication of a user.
|
||||
*
|
||||
* @since 5.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_current_item( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$uuid = rest_get_authenticated_app_password();
|
||||
|
||||
if ( ! $uuid ) {
|
||||
return new WP_Error(
|
||||
'rest_no_authenticated_app_password',
|
||||
__( 'Cannot introspect application password.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$password = WP_Application_Passwords::get_user_application_password( $user->ID, $uuid );
|
||||
|
||||
if ( ! $password ) {
|
||||
return new WP_Error(
|
||||
'rest_application_password_not_found',
|
||||
__( 'Application password not found.' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response( $password, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a permissions check for the request.
|
||||
*
|
||||
* @since 5.6.0
|
||||
* @deprecated 5.7.0 Use `edit_user` directly or one of the specific meta capabilities introduced in 5.7.0.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
protected function do_permissions_check( $request ) {
|
||||
_deprecated_function( __METHOD__, '5.7.0' );
|
||||
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'edit_user', $user->ID ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_manage_application_passwords',
|
||||
__( 'Sorry, you are not allowed to manage application passwords for this user.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares an application password for a create or update operation.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return object|WP_Error The prepared item, or WP_Error object on failure.
|
||||
*/
|
||||
protected function prepare_item_for_database( $request ) {
|
||||
$prepared = (object) array(
|
||||
'name' => $request['name'],
|
||||
);
|
||||
|
||||
if ( $request['app_id'] && ! $request['uuid'] ) {
|
||||
$prepared->app_id = $request['app_id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters an application password before it is inserted via the REST API.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param stdClass $prepared An object representing a single application password prepared for inserting or updating the database.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*/
|
||||
return apply_filters( 'rest_pre_insert_application_password', $prepared, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the application password for the REST response.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param array $item WordPress representation of the item.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
$prepared = array(
|
||||
'uuid' => $item['uuid'],
|
||||
'app_id' => empty( $item['app_id'] ) ? '' : $item['app_id'],
|
||||
'name' => $item['name'],
|
||||
'created' => gmdate( 'Y-m-d\TH:i:s', $item['created'] ),
|
||||
'last_used' => $item['last_used'] ? gmdate( 'Y-m-d\TH:i:s', $item['last_used'] ) : null,
|
||||
'last_ip' => $item['last_ip'] ? $item['last_ip'] : null,
|
||||
);
|
||||
|
||||
if ( isset( $item['new_password'] ) ) {
|
||||
$prepared['password'] = $item['new_password'];
|
||||
}
|
||||
|
||||
$prepared = $this->add_additional_fields_to_object( $prepared, $request );
|
||||
$prepared = $this->filter_response_by_context( $prepared, $request['context'] );
|
||||
|
||||
$response = new WP_REST_Response( $prepared );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $user, $item ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the REST API response for an application password.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param array $item The application password array.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_application_password', $response, $item, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_User $user The requested user.
|
||||
* @param array $item The application password.
|
||||
* @return array The list of links.
|
||||
*/
|
||||
protected function prepare_links( WP_User $user, $item ) {
|
||||
return array(
|
||||
'self' => array(
|
||||
'href' => rest_url(
|
||||
sprintf(
|
||||
'%s/users/%d/application-passwords/%s',
|
||||
$this->namespace,
|
||||
$user->ID,
|
||||
$item['uuid']
|
||||
)
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the requested user.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
* @return WP_User|WP_Error The WordPress user associated with the request, or a WP_Error if none found.
|
||||
*/
|
||||
protected function get_user( $request ) {
|
||||
if ( ! wp_is_application_passwords_available() ) {
|
||||
return new WP_Error(
|
||||
'application_passwords_disabled',
|
||||
__( 'Application passwords are not available.' ),
|
||||
array( 'status' => 501 )
|
||||
);
|
||||
}
|
||||
|
||||
$error = new WP_Error(
|
||||
'rest_user_invalid_id',
|
||||
__( 'Invalid user ID.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
|
||||
$id = $request['user_id'];
|
||||
|
||||
if ( 'me' === $id ) {
|
||||
if ( ! is_user_logged_in() ) {
|
||||
return new WP_Error(
|
||||
'rest_not_logged_in',
|
||||
__( 'You are not currently logged in.' ),
|
||||
array( 'status' => 401 )
|
||||
);
|
||||
}
|
||||
|
||||
$user = wp_get_current_user();
|
||||
} else {
|
||||
$id = (int) $id;
|
||||
|
||||
if ( $id <= 0 ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$user = get_userdata( $id );
|
||||
}
|
||||
|
||||
if ( empty( $user ) || ! $user->exists() ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
if ( is_multisite() && ! user_can( $user->ID, 'manage_sites' ) && ! is_user_member_of_blog( $user->ID ) ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
if ( ! wp_is_application_passwords_available_for_user( $user ) ) {
|
||||
return new WP_Error(
|
||||
'application_passwords_disabled_for_user',
|
||||
__( 'Application passwords are not available for your account. Please contact the site administrator for assistance.' ),
|
||||
array( 'status' => 501 )
|
||||
);
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the requested application password for a user.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request object.
|
||||
* @return array|WP_Error The application password details if found, a WP_Error otherwise.
|
||||
*/
|
||||
protected function get_application_password( $request ) {
|
||||
$user = $this->get_user( $request );
|
||||
|
||||
if ( is_wp_error( $user ) ) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$password = WP_Application_Passwords::get_user_application_password( $user->ID, $request['uuid'] );
|
||||
|
||||
if ( ! $password ) {
|
||||
return new WP_Error(
|
||||
'rest_application_password_not_found',
|
||||
__( 'Application password not found.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
return $password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the collections.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @return array Query parameters for the collection.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
return array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the application password's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$this->schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'application-password',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'uuid' => array(
|
||||
'description' => __( 'The unique identifier for the application password.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uuid',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'app_id' => array(
|
||||
'description' => __( 'A UUID provided by the application to uniquely identify it. It is recommended to use an UUID v5 with the URL or DNS namespace.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uuid',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'The name of the application password.' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'minLength' => 1,
|
||||
'pattern' => '.*\S.*',
|
||||
),
|
||||
'password' => array(
|
||||
'description' => __( 'The generated password. Only available after adding an application.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'created' => array(
|
||||
'description' => __( 'The GMT date the application password was created.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'last_used' => array(
|
||||
'description' => __( 'The GMT date the application password was last used.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'last_ip' => array(
|
||||
'description' => __( 'The IP address the application password was last used by.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'format' => 'ip',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,497 @@
|
||||
<?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_Revisions_Controller
|
||||
* @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_Revisions_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 );
|
||||
$parent_controller = $post_type_object->get_rest_controller();
|
||||
|
||||
if ( ! $parent_controller ) {
|
||||
$parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
|
||||
}
|
||||
|
||||
$this->parent_controller = $parent_controller;
|
||||
|
||||
$revisions_controller = $post_type_object->get_revisions_rest_controller();
|
||||
if ( ! $revisions_controller ) {
|
||||
$revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
|
||||
}
|
||||
$this->revisions_controller = $revisions_controller;
|
||||
$this->rest_base = 'autosaves';
|
||||
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
|
||||
$this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for autosaves.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->parent_base . '/(?P<id>[\d]+)/' . $this->rest_base,
|
||||
array(
|
||||
'args' => array(
|
||||
'parent' => array(
|
||||
'description' => __( 'The ID for the parent of the autosave.' ),
|
||||
'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->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 autosave.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'The ID for the autosave.' ),
|
||||
'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 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 ) {
|
||||
$parent = $this->get_parent( $request['id'] );
|
||||
if ( is_wp_error( $parent ) ) {
|
||||
return $parent;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( '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( 'WP_RUN_CORE_TESTS' ) && ! defined( 'DOING_AUTOSAVE' ) ) {
|
||||
define( 'DOING_AUTOSAVE', true );
|
||||
}
|
||||
|
||||
$post = $this->get_parent( $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();
|
||||
|
||||
// We need to check post lock to ensure the original author didn't leave their browser tab open.
|
||||
if ( ! function_exists( 'wp_check_post_lock' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/post.php';
|
||||
}
|
||||
|
||||
$post_lock = wp_check_post_lock( $post->ID );
|
||||
$is_draft = 'draft' === $post->post_status || 'auto-draft' === $post->post_status;
|
||||
|
||||
if ( $is_draft && (int) $post->post_author === $user_id && ! $post_lock ) {
|
||||
/*
|
||||
* 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. Pass the meta data.
|
||||
$autosave_id = $this->create_post_autosave( (array) $prepared_post, (array) $request->get_param( 'meta' ) );
|
||||
}
|
||||
|
||||
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 details 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 post parent 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 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 ) {
|
||||
$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 ( str_contains( $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() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->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,
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates autosave for the specified post.
|
||||
*
|
||||
* From wp-admin/post.php.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @since 6.4.0 The `$meta` parameter was added.
|
||||
*
|
||||
* @param array $post_data Associative array containing the post data.
|
||||
* @param array $meta Associative array containing the post meta data.
|
||||
* @return mixed The autosave revision ID or WP_Error.
|
||||
*/
|
||||
public function create_post_autosave( $post_data, array $meta = array() ) {
|
||||
|
||||
$post_id = (int) $post_data['ID'];
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
// Only create an autosave when it is different from the saved post.
|
||||
$autosave_is_different = false;
|
||||
$new_autosave = _wp_post_revision_data( $post_data, true );
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if meta values have changed.
|
||||
if ( ! empty( $meta ) ) {
|
||||
$revisioned_meta_keys = wp_post_revision_meta_keys( $post->post_type );
|
||||
foreach ( $revisioned_meta_keys as $meta_key ) {
|
||||
// get_metadata_raw is used to avoid retrieving the default value.
|
||||
$old_meta = get_metadata_raw( 'post', $post_id, $meta_key, true );
|
||||
$new_meta = isset( $meta[ $meta_key ] ) ? $meta[ $meta_key ] : '';
|
||||
|
||||
if ( $new_meta !== $old_meta ) {
|
||||
$autosave_is_different = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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 ( ! $autosave_is_different && $old_autosave ) {
|
||||
// Nothing to save, return the existing autosave.
|
||||
return $old_autosave->ID;
|
||||
}
|
||||
|
||||
if ( $old_autosave ) {
|
||||
$new_autosave['ID'] = $old_autosave->ID;
|
||||
$new_autosave['post_author'] = $user_id;
|
||||
|
||||
/** This filter is documented in wp-admin/post.php */
|
||||
do_action( 'wp_creating_autosave', $new_autosave );
|
||||
|
||||
// wp_update_post() expects escaped array.
|
||||
$revision_id = wp_update_post( wp_slash( $new_autosave ) );
|
||||
} else {
|
||||
// Create the new autosave as a special post revision.
|
||||
$revision_id = _wp_put_post_revision( $post_data, true );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $revision_id ) || 0 === $revision_id ) {
|
||||
return $revision_id;
|
||||
}
|
||||
|
||||
// Attached any passed meta values that have revisions enabled.
|
||||
if ( ! empty( $meta ) ) {
|
||||
foreach ( $revisioned_meta_keys as $meta_key ) {
|
||||
if ( isset( $meta[ $meta_key ] ) ) {
|
||||
update_metadata( 'post', $revision_id, $meta_key, wp_slash( $meta[ $meta_key ] ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $revision_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the revision for the REST response.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @param WP_Post $item Post revision object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$post = $item;
|
||||
|
||||
$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 REST 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,328 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Block_Directory_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.5.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller which provides REST endpoint for the blocks.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Block_Directory_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructs the controller.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'block-directory';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the necessary REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/search',
|
||||
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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given request has permission to install and activate plugins.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has permission, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
if ( ! current_user_can( 'install_plugins' ) || ! current_user_can( 'activate_plugins' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_block_directory_cannot_view',
|
||||
__( 'Sorry, you are not allowed to browse the block directory.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and retrieve blocks metadata
|
||||
*
|
||||
* @since 5.5.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 ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$response = plugins_api(
|
||||
'query_plugins',
|
||||
array(
|
||||
'block' => $request['term'],
|
||||
'per_page' => $request['per_page'],
|
||||
'page' => $request['page'],
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$response->add_data( array( 'status' => 500 ) );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
foreach ( $response->plugins as $plugin ) {
|
||||
// If the API returned a plugin with empty data for 'blocks', skip it.
|
||||
if ( empty( $plugin['blocks'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data = $this->prepare_item_for_response( $plugin, $request );
|
||||
$result[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $result );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse block metadata for a block, and prepare it for an API response.
|
||||
*
|
||||
* @since 5.5.0
|
||||
* @since 5.9.0 Renamed `$plugin` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @param array $item The plugin metadata.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$plugin = $item;
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
// There might be multiple blocks in a plugin. Only the first block is mapped.
|
||||
$block_data = reset( $plugin['blocks'] );
|
||||
|
||||
// A data array containing the properties we'll return.
|
||||
$block = array(
|
||||
'name' => $block_data['name'],
|
||||
'title' => ( $block_data['title'] ? $block_data['title'] : $plugin['name'] ),
|
||||
'description' => wp_trim_words( $plugin['short_description'], 30, '...' ),
|
||||
'id' => $plugin['slug'],
|
||||
'rating' => $plugin['rating'] / 20,
|
||||
'rating_count' => (int) $plugin['num_ratings'],
|
||||
'active_installs' => (int) $plugin['active_installs'],
|
||||
'author_block_rating' => $plugin['author_block_rating'] / 20,
|
||||
'author_block_count' => (int) $plugin['author_block_count'],
|
||||
'author' => wp_strip_all_tags( $plugin['author'] ),
|
||||
'icon' => ( isset( $plugin['icons']['1x'] ) ? $plugin['icons']['1x'] : 'block-default' ),
|
||||
'last_updated' => gmdate( 'Y-m-d\TH:i:s', strtotime( $plugin['last_updated'] ) ),
|
||||
'humanized_updated' => sprintf(
|
||||
/* translators: %s: Human-readable time difference. */
|
||||
__( '%s ago' ),
|
||||
human_time_diff( strtotime( $plugin['last_updated'] ) )
|
||||
),
|
||||
);
|
||||
|
||||
$this->add_additional_fields_to_object( $block, $request );
|
||||
|
||||
$response = new WP_REST_Response( $block );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $plugin ) );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a list of links to include in the response for the plugin.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param array $plugin The plugin data from WordPress.org.
|
||||
* @return array
|
||||
*/
|
||||
protected function prepare_links( $plugin ) {
|
||||
$links = array(
|
||||
'https://api.w.org/install-plugin' => array(
|
||||
'href' => add_query_arg( 'slug', urlencode( $plugin['slug'] ), rest_url( 'wp/v2/plugins' ) ),
|
||||
),
|
||||
);
|
||||
|
||||
$plugin_file = $this->find_plugin_for_slug( $plugin['slug'] );
|
||||
|
||||
if ( $plugin_file ) {
|
||||
$links['https://api.w.org/plugin'] = array(
|
||||
'href' => rest_url( 'wp/v2/plugins/' . substr( $plugin_file, 0, - 4 ) ),
|
||||
'embeddable' => true,
|
||||
);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds an installed plugin for the given slug.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $slug The WordPress.org directory slug for a plugin.
|
||||
* @return string The plugin file found matching it.
|
||||
*/
|
||||
protected function find_plugin_for_slug( $slug ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$plugin_files = get_plugins( '/' . $slug );
|
||||
|
||||
if ( ! $plugin_files ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$plugin_files = array_keys( $plugin_files );
|
||||
|
||||
return $slug . '/' . reset( $plugin_files );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the theme's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$this->schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'block-directory-item',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'The block name, in namespace/block-name format.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'title' => array(
|
||||
'description' => __( 'The block title, in human readable format.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'A short description of the block, in human readable format.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'The block slug.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'rating' => array(
|
||||
'description' => __( 'The star rating of the block.' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'rating_count' => array(
|
||||
'description' => __( 'The number of ratings.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'active_installs' => array(
|
||||
'description' => __( 'The number sites that have activated this block.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'author_block_rating' => array(
|
||||
'description' => __( 'The average rating of blocks published by the same author.' ),
|
||||
'type' => 'number',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'author_block_count' => array(
|
||||
'description' => __( 'The number of blocks published by the same author.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'author' => array(
|
||||
'description' => __( 'The WordPress.org username of the block author.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'icon' => array(
|
||||
'description' => __( 'The block icon.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'last_updated' => array(
|
||||
'description' => __( 'The date when the block was last updated.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'humanized_updated' => array(
|
||||
'description' => __( 'The date when the block was last updated, in fuzzy human readable format.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the search params for the blocks collection.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$query_params = parent::get_collection_params();
|
||||
|
||||
$query_params['context']['default'] = 'view';
|
||||
|
||||
$query_params['term'] = array(
|
||||
'description' => __( 'Limit result set to blocks matching the search term.' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'minLength' => 1,
|
||||
);
|
||||
|
||||
unset( $query_params['search'] );
|
||||
|
||||
/**
|
||||
* Filters REST API collection parameters for the block directory controller.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param array $query_params JSON Schema-formatted collection parameters.
|
||||
*/
|
||||
return apply_filters( 'rest_block_directory_collection_params', $query_params );
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Block_Pattern_Categories_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 6.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access block pattern categories via the REST API.
|
||||
*
|
||||
* @since 6.0.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Block_Pattern_Categories_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructs the controller.
|
||||
*
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'block-patterns/categories';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 6.0.0
|
||||
*/
|
||||
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' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given request has permission to read block patterns.
|
||||
*
|
||||
* @since 6.0.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 ( current_user_can( 'edit_posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
|
||||
if ( current_user_can( $post_type->cap->edit_posts ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_view',
|
||||
__( 'Sorry, you are not allowed to view the registered block pattern categories.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all block pattern categories.
|
||||
*
|
||||
* @since 6.0.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 ) {
|
||||
$response = array();
|
||||
$categories = WP_Block_Pattern_Categories_Registry::get_instance()->get_all_registered();
|
||||
foreach ( $categories as $category ) {
|
||||
$prepared_category = $this->prepare_item_for_response( $category, $request );
|
||||
$response[] = $this->prepare_response_for_collection( $prepared_category );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a raw block pattern category before it gets output in a REST API response.
|
||||
*
|
||||
* @since 6.0.0
|
||||
*
|
||||
* @param array $item Raw category as registered, before any changes.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$keys = array( 'name', 'label', 'description' );
|
||||
$data = array();
|
||||
foreach ( $keys as $key ) {
|
||||
if ( isset( $item[ $key ] ) && rest_is_field_included( $key, $fields ) ) {
|
||||
$data[ $key ] = $item[ $key ];
|
||||
}
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the block pattern category schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 6.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'block-pattern-category',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'The category name.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'label' => array(
|
||||
'description' => __( 'The category label, in human readable format.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'The category description, in human readable format.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
@ -0,0 +1,298 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Block_Patterns_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 6.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access block patterns via the REST API.
|
||||
*
|
||||
* @since 6.0.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Block_Patterns_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Defines whether remote patterns should be loaded.
|
||||
*
|
||||
* @since 6.0.0
|
||||
* @var bool
|
||||
*/
|
||||
private $remote_patterns_loaded;
|
||||
|
||||
/**
|
||||
* An array that maps old categories names to new ones.
|
||||
*
|
||||
* @since 6.2.0
|
||||
* @var array
|
||||
*/
|
||||
protected static $categories_migration = array(
|
||||
'buttons' => 'call-to-action',
|
||||
'columns' => 'text',
|
||||
'query' => 'posts',
|
||||
);
|
||||
|
||||
/**
|
||||
* Constructs the controller.
|
||||
*
|
||||
* @since 6.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'block-patterns/patterns';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 6.0.0
|
||||
*/
|
||||
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' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given request has permission to read block patterns.
|
||||
*
|
||||
* @since 6.0.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 ( current_user_can( 'edit_posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
|
||||
if ( current_user_can( $post_type->cap->edit_posts ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_view',
|
||||
__( 'Sorry, you are not allowed to view the registered block patterns.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all block patterns.
|
||||
*
|
||||
* @since 6.0.0
|
||||
* @since 6.2.0 Added migration for old core pattern categories to the new ones.
|
||||
*
|
||||
* @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 ) {
|
||||
if ( ! $this->remote_patterns_loaded ) {
|
||||
// Load block patterns from w.org.
|
||||
_load_remote_block_patterns(); // Patterns with the `core` keyword.
|
||||
_load_remote_featured_patterns(); // Patterns in the `featured` category.
|
||||
_register_remote_theme_patterns(); // Patterns requested by current theme.
|
||||
|
||||
$this->remote_patterns_loaded = true;
|
||||
}
|
||||
|
||||
$response = array();
|
||||
$patterns = WP_Block_Patterns_Registry::get_instance()->get_all_registered();
|
||||
foreach ( $patterns as $pattern ) {
|
||||
$migrated_pattern = $this->migrate_pattern_categories( $pattern );
|
||||
$prepared_pattern = $this->prepare_item_for_response( $migrated_pattern, $request );
|
||||
$response[] = $this->prepare_response_for_collection( $prepared_pattern );
|
||||
}
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrates old core pattern categories to the new categories.
|
||||
*
|
||||
* Core pattern categories are revamped. Migration is needed to ensure
|
||||
* backwards compatibility.
|
||||
*
|
||||
* @since 6.2.0
|
||||
*
|
||||
* @param array $pattern Raw pattern as registered, before applying any changes.
|
||||
* @return array Migrated pattern.
|
||||
*/
|
||||
protected function migrate_pattern_categories( $pattern ) {
|
||||
// No categories to migrate.
|
||||
if (
|
||||
! isset( $pattern['categories'] ) ||
|
||||
! is_array( $pattern['categories'] )
|
||||
) {
|
||||
return $pattern;
|
||||
}
|
||||
|
||||
foreach ( $pattern['categories'] as $index => $category ) {
|
||||
// If the category exists as a key, then it needs migration.
|
||||
if ( isset( static::$categories_migration[ $category ] ) ) {
|
||||
$pattern['categories'][ $index ] = static::$categories_migration[ $category ];
|
||||
}
|
||||
}
|
||||
|
||||
return $pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a raw block pattern before it gets output in a REST API response.
|
||||
*
|
||||
* @since 6.0.0
|
||||
* @since 6.3.0 Added `source` property.
|
||||
*
|
||||
* @param array $item Raw pattern as registered, before any changes.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$keys = array(
|
||||
'name' => 'name',
|
||||
'title' => 'title',
|
||||
'content' => 'content',
|
||||
'description' => 'description',
|
||||
'viewportWidth' => 'viewport_width',
|
||||
'inserter' => 'inserter',
|
||||
'categories' => 'categories',
|
||||
'keywords' => 'keywords',
|
||||
'blockTypes' => 'block_types',
|
||||
'postTypes' => 'post_types',
|
||||
'templateTypes' => 'template_types',
|
||||
'source' => 'source',
|
||||
);
|
||||
$data = array();
|
||||
foreach ( $keys as $item_key => $rest_key ) {
|
||||
if ( isset( $item[ $item_key ] ) && rest_is_field_included( $rest_key, $fields ) ) {
|
||||
$data[ $rest_key ] = $item[ $item_key ];
|
||||
}
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the block pattern schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 6.0.0
|
||||
* @since 6.3.0 Added `source` property.
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'block-pattern',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'The pattern name.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'title' => array(
|
||||
'description' => __( 'The pattern title, in human readable format.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'content' => array(
|
||||
'description' => __( 'The pattern content.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'The pattern detailed description.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'viewport_width' => array(
|
||||
'description' => __( 'The pattern viewport width for inserter preview.' ),
|
||||
'type' => 'number',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'inserter' => array(
|
||||
'description' => __( 'Determines whether the pattern is visible in inserter.' ),
|
||||
'type' => 'boolean',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'categories' => array(
|
||||
'description' => __( 'The pattern category slugs.' ),
|
||||
'type' => 'array',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'keywords' => array(
|
||||
'description' => __( 'The pattern keywords.' ),
|
||||
'type' => 'array',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'block_types' => array(
|
||||
'description' => __( 'Block types that the pattern is intended to be used with.' ),
|
||||
'type' => 'array',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'post_types' => array(
|
||||
'description' => __( 'An array of post types that the pattern is restricted to be used with.' ),
|
||||
'type' => 'array',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'template_types' => array(
|
||||
'description' => __( 'An array of template types where the pattern fits.' ),
|
||||
'type' => 'array',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'source' => array(
|
||||
'description' => __( 'Where the pattern comes from e.g. core' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'enum' => array(
|
||||
'core',
|
||||
'plugin',
|
||||
'theme',
|
||||
'pattern-directory/core',
|
||||
'pattern-directory/theme',
|
||||
'pattern-directory/featured',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
@ -0,0 +1,224 @@
|
||||
<?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
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<name>[a-z0-9-]+/[a-z0-9-]+)',
|
||||
array(
|
||||
'args' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'Unique registered name for the block.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => array( WP_REST_Server::READABLE, WP_REST_Server::CREATABLE ),
|
||||
'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(
|
||||
'description' => __( 'Attributes for the block.' ),
|
||||
'type' => 'object',
|
||||
'default' => array(),
|
||||
'validate_callback' => static function ( $value, $request ) {
|
||||
$block = WP_Block_Type_Registry::get_instance()->get_registered( $request['name'] );
|
||||
|
||||
if ( ! $block ) {
|
||||
// This will get rejected in ::get_item().
|
||||
return true;
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'type' => 'object',
|
||||
'properties' => $block->get_attributes(),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
|
||||
return rest_validate_value_from_schema( $value, $schema );
|
||||
},
|
||||
'sanitize_callback' => static function ( $value, $request ) {
|
||||
$block = WP_Block_Type_Registry::get_instance()->get_registered( $request['name'] );
|
||||
|
||||
if ( ! $block ) {
|
||||
// This will get rejected in ::get_item().
|
||||
return true;
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'type' => 'object',
|
||||
'properties' => $block->get_attributes(),
|
||||
'additionalProperties' => false,
|
||||
);
|
||||
|
||||
return rest_sanitize_value_from_schema( $value, $schema );
|
||||
},
|
||||
),
|
||||
'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
|
||||
*
|
||||
* @global WP_Post $post Global post object.
|
||||
*
|
||||
* @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'] ) ? (int) $request['post_id'] : 0;
|
||||
|
||||
if ( $post_id > 0 ) {
|
||||
$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
|
||||
*
|
||||
* @global WP_Post $post Global post object.
|
||||
*
|
||||
* @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'] ) ? (int) $request['post_id'] : 0;
|
||||
|
||||
if ( $post_id > 0 ) {
|
||||
$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();
|
||||
$registered = $registry->get_registered( $request['name'] );
|
||||
|
||||
if ( null === $registered || ! $registered->is_dynamic() ) {
|
||||
return new WP_Error(
|
||||
'block_invalid',
|
||||
__( 'Invalid block.' ),
|
||||
array(
|
||||
'status' => 404,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$attributes = $request->get_param( 'attributes' );
|
||||
|
||||
// Create an array representation simulating the output of parse_blocks.
|
||||
$block = array(
|
||||
'blockName' => $request['name'],
|
||||
'attrs' => $attributes,
|
||||
'innerHTML' => '',
|
||||
'innerContent' => array(),
|
||||
);
|
||||
|
||||
// Render using render_block to ensure all relevant filters are used.
|
||||
$data = array(
|
||||
'rendered' => render_block( $block ),
|
||||
);
|
||||
|
||||
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() {
|
||||
if ( $this->schema ) {
|
||||
return $this->schema;
|
||||
}
|
||||
|
||||
$this->schema = 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' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->schema;
|
||||
}
|
||||
}
|
@ -0,0 +1,827 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Block_Types_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.5.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access block types via the REST API.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Block_Types_Controller extends WP_REST_Controller {
|
||||
|
||||
const NAME_PATTERN = '^[a-z][a-z0-9-]*/[a-z][a-z0-9-]*$';
|
||||
|
||||
/**
|
||||
* Instance of WP_Block_Type_Registry.
|
||||
*
|
||||
* @since 5.5.0
|
||||
* @var WP_Block_Type_Registry
|
||||
*/
|
||||
protected $block_registry;
|
||||
|
||||
/**
|
||||
* Instance of WP_Block_Styles_Registry.
|
||||
*
|
||||
* @since 5.5.0
|
||||
* @var WP_Block_Styles_Registry
|
||||
*/
|
||||
protected $style_registry;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'block-types';
|
||||
$this->block_registry = WP_Block_Type_Registry::get_instance();
|
||||
$this->style_registry = WP_Block_Styles_Registry::get_instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for block types.
|
||||
*
|
||||
* @since 5.5.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<namespace>[a-zA-Z0-9_-]+)',
|
||||
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<namespace>[a-zA-Z0-9_-]+)/(?P<name>[a-zA-Z0-9_-]+)',
|
||||
array(
|
||||
'args' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'Block name.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'namespace' => array(
|
||||
'description' => __( 'Block namespace.' ),
|
||||
'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 block types.
|
||||
*
|
||||
* @since 5.5.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 ) {
|
||||
return $this->check_read_permission();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all post block types, depending on user context.
|
||||
*
|
||||
* @since 5.5.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 ) {
|
||||
$data = array();
|
||||
$block_types = $this->block_registry->get_all_registered();
|
||||
|
||||
// Retrieve the list of registered collection query parameters.
|
||||
$registered = $this->get_collection_params();
|
||||
$namespace = '';
|
||||
if ( isset( $registered['namespace'] ) && ! empty( $request['namespace'] ) ) {
|
||||
$namespace = $request['namespace'];
|
||||
}
|
||||
|
||||
foreach ( $block_types as $slug => $obj ) {
|
||||
if ( $namespace ) {
|
||||
list ( $block_namespace ) = explode( '/', $obj->name );
|
||||
|
||||
if ( $namespace !== $block_namespace ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$block_type = $this->prepare_item_for_response( $obj, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $block_type );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read a block type.
|
||||
*
|
||||
* @since 5.5.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, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
$check = $this->check_read_permission();
|
||||
if ( is_wp_error( $check ) ) {
|
||||
return $check;
|
||||
}
|
||||
$block_name = sprintf( '%s/%s', $request['namespace'], $request['name'] );
|
||||
$block_type = $this->get_block( $block_name );
|
||||
if ( is_wp_error( $block_type ) ) {
|
||||
return $block_type;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given block type should be visible.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @return true|WP_Error True if the block type is visible, WP_Error otherwise.
|
||||
*/
|
||||
protected function check_read_permission() {
|
||||
if ( current_user_can( 'edit_posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
|
||||
if ( current_user_can( $post_type->cap->edit_posts ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_Error( 'rest_block_type_cannot_view', __( 'Sorry, you are not allowed to manage block types.' ), array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the block, if the name is valid.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $name Block name.
|
||||
* @return WP_Block_Type|WP_Error Block type object if name is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_block( $name ) {
|
||||
$block_type = $this->block_registry->get_registered( $name );
|
||||
if ( empty( $block_type ) ) {
|
||||
return new WP_Error( 'rest_block_type_invalid', __( 'Invalid block type.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return $block_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific block type.
|
||||
*
|
||||
* @since 5.5.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 ) {
|
||||
$block_name = sprintf( '%s/%s', $request['namespace'], $request['name'] );
|
||||
$block_type = $this->get_block( $block_name );
|
||||
if ( is_wp_error( $block_type ) ) {
|
||||
return $block_type;
|
||||
}
|
||||
$data = $this->prepare_item_for_response( $block_type, $request );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a block type object for serialization.
|
||||
*
|
||||
* @since 5.5.0
|
||||
* @since 5.9.0 Renamed `$block_type` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
* @since 6.3.0 Added `selectors` field.
|
||||
* @since 6.5.0 Added `view_script_module_ids` field.
|
||||
*
|
||||
* @param WP_Block_Type $item Block type data.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Block type data.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$block_type = $item;
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( rest_is_field_included( 'attributes', $fields ) ) {
|
||||
$data['attributes'] = $block_type->get_attributes();
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'is_dynamic', $fields ) ) {
|
||||
$data['is_dynamic'] = $block_type->is_dynamic();
|
||||
}
|
||||
|
||||
$schema = $this->get_item_schema();
|
||||
// Fields deprecated in WordPress 6.1, but left in the schema for backwards compatibility.
|
||||
$deprecated_fields = array(
|
||||
'editor_script',
|
||||
'script',
|
||||
'view_script',
|
||||
'editor_style',
|
||||
'style',
|
||||
);
|
||||
$extra_fields = array_merge(
|
||||
array(
|
||||
'api_version',
|
||||
'name',
|
||||
'title',
|
||||
'description',
|
||||
'icon',
|
||||
'category',
|
||||
'keywords',
|
||||
'parent',
|
||||
'ancestor',
|
||||
'allowed_blocks',
|
||||
'provides_context',
|
||||
'uses_context',
|
||||
'selectors',
|
||||
'supports',
|
||||
'styles',
|
||||
'textdomain',
|
||||
'example',
|
||||
'editor_script_handles',
|
||||
'script_handles',
|
||||
'view_script_handles',
|
||||
'view_script_module_ids',
|
||||
'editor_style_handles',
|
||||
'style_handles',
|
||||
'view_style_handles',
|
||||
'variations',
|
||||
'block_hooks',
|
||||
),
|
||||
$deprecated_fields
|
||||
);
|
||||
foreach ( $extra_fields as $extra_field ) {
|
||||
if ( rest_is_field_included( $extra_field, $fields ) ) {
|
||||
if ( isset( $block_type->$extra_field ) ) {
|
||||
$field = $block_type->$extra_field;
|
||||
if ( in_array( $extra_field, $deprecated_fields, true ) && is_array( $field ) ) {
|
||||
// Since the schema only allows strings or null (but no arrays), we return the first array item.
|
||||
$field = ! empty( $field ) ? array_shift( $field ) : '';
|
||||
}
|
||||
} elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) {
|
||||
$field = $schema['properties'][ $extra_field ]['default'];
|
||||
} else {
|
||||
$field = '';
|
||||
}
|
||||
$data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] );
|
||||
}
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'styles', $fields ) ) {
|
||||
$styles = $this->style_registry->get_registered_styles_for_block( $block_type->name );
|
||||
$styles = array_values( $styles );
|
||||
$data['styles'] = wp_parse_args( $styles, $data['styles'] );
|
||||
$data['styles'] = array_filter( $data['styles'] );
|
||||
}
|
||||
|
||||
$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 ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $block_type ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a block type returned from the REST API.
|
||||
*
|
||||
* Allows modification of the block type data right before it is returned.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param WP_Block_Type $block_type The original block type object.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_block_type', $response, $block_type, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param WP_Block_Type $block_type Block type data.
|
||||
* @return array Links for the given block type.
|
||||
*/
|
||||
protected function prepare_links( $block_type ) {
|
||||
list( $namespace ) = explode( '/', $block_type->name );
|
||||
|
||||
$links = array(
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $block_type->name ) ),
|
||||
),
|
||||
'up' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $namespace ) ),
|
||||
),
|
||||
);
|
||||
|
||||
if ( $block_type->is_dynamic() ) {
|
||||
$links['https://api.w.org/render-block'] = array(
|
||||
'href' => add_query_arg(
|
||||
'context',
|
||||
'edit',
|
||||
rest_url( sprintf( '%s/%s/%s', 'wp/v2', 'block-renderer', $block_type->name ) )
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the block type' schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.5.0
|
||||
* @since 6.3.0 Added `selectors` field.
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
// rest_validate_value_from_schema doesn't understand $refs, pull out reused definitions for readability.
|
||||
$inner_blocks_definition = array(
|
||||
'description' => __( 'The list of inner blocks used in the example.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'The name of the inner block.' ),
|
||||
'type' => 'string',
|
||||
'pattern' => self::NAME_PATTERN,
|
||||
'required' => true,
|
||||
),
|
||||
'attributes' => array(
|
||||
'description' => __( 'The attributes of the inner block.' ),
|
||||
'type' => 'object',
|
||||
),
|
||||
'innerBlocks' => array(
|
||||
'description' => __( "A list of the inner block's own inner blocks. This is a recursive definition following the parent innerBlocks schema." ),
|
||||
'type' => 'array',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$example_definition = array(
|
||||
'description' => __( 'Block example.' ),
|
||||
'type' => array( 'object', 'null' ),
|
||||
'default' => null,
|
||||
'properties' => array(
|
||||
'attributes' => array(
|
||||
'description' => __( 'The attributes used in the example.' ),
|
||||
'type' => 'object',
|
||||
),
|
||||
'innerBlocks' => $inner_blocks_definition,
|
||||
),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
);
|
||||
|
||||
$keywords_definition = array(
|
||||
'description' => __( 'Block keywords.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'default' => array(),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
);
|
||||
|
||||
$icon_definition = array(
|
||||
'description' => __( 'Icon of block type.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
);
|
||||
|
||||
$category_definition = array(
|
||||
'description' => __( 'Block category.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
);
|
||||
|
||||
$this->schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'block-type',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'api_version' => array(
|
||||
'description' => __( 'Version of block API.' ),
|
||||
'type' => 'integer',
|
||||
'default' => 1,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'title' => array(
|
||||
'description' => __( 'Title of block type.' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Unique name identifying the block type.' ),
|
||||
'type' => 'string',
|
||||
'pattern' => self::NAME_PATTERN,
|
||||
'required' => true,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'Description of block type.' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'icon' => $icon_definition,
|
||||
'attributes' => array(
|
||||
'description' => __( 'Block attributes.' ),
|
||||
'type' => array( 'object', 'null' ),
|
||||
'properties' => array(),
|
||||
'default' => null,
|
||||
'additionalProperties' => array(
|
||||
'type' => 'object',
|
||||
),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'provides_context' => array(
|
||||
'description' => __( 'Context provided by blocks of this type.' ),
|
||||
'type' => 'object',
|
||||
'properties' => array(),
|
||||
'additionalProperties' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'default' => array(),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'uses_context' => array(
|
||||
'description' => __( 'Context values inherited by blocks of this type.' ),
|
||||
'type' => 'array',
|
||||
'default' => array(),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'selectors' => array(
|
||||
'description' => __( 'Custom CSS selectors.' ),
|
||||
'type' => 'object',
|
||||
'default' => array(),
|
||||
'properties' => array(),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'supports' => array(
|
||||
'description' => __( 'Block supports.' ),
|
||||
'type' => 'object',
|
||||
'default' => array(),
|
||||
'properties' => array(),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'category' => $category_definition,
|
||||
'is_dynamic' => array(
|
||||
'description' => __( 'Is the block dynamically rendered.' ),
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'editor_script_handles' => array(
|
||||
'description' => __( 'Editor script handles.' ),
|
||||
'type' => array( 'array' ),
|
||||
'default' => array(),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'script_handles' => array(
|
||||
'description' => __( 'Public facing and editor script handles.' ),
|
||||
'type' => array( 'array' ),
|
||||
'default' => array(),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'view_script_handles' => array(
|
||||
'description' => __( 'Public facing script handles.' ),
|
||||
'type' => array( 'array' ),
|
||||
'default' => array(),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'view_script_module_ids' => array(
|
||||
'description' => __( 'Public facing script module IDs.' ),
|
||||
'type' => array( 'array' ),
|
||||
'default' => array(),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'editor_style_handles' => array(
|
||||
'description' => __( 'Editor style handles.' ),
|
||||
'type' => array( 'array' ),
|
||||
'default' => array(),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'style_handles' => array(
|
||||
'description' => __( 'Public facing and editor style handles.' ),
|
||||
'type' => array( 'array' ),
|
||||
'default' => array(),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'view_style_handles' => array(
|
||||
'description' => __( 'Public facing style handles.' ),
|
||||
'type' => array( 'array' ),
|
||||
'default' => array(),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'styles' => array(
|
||||
'description' => __( 'Block style variations.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'Unique name identifying the style.' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'label' => array(
|
||||
'description' => __( 'The human-readable label for the style.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'inline_style' => array(
|
||||
'description' => __( 'Inline CSS code that registers the CSS class required for the style.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'style_handle' => array(
|
||||
'description' => __( 'Contains the handle that defines the block style.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'default' => array(),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'variations' => array(
|
||||
'description' => __( 'Block variations.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'The unique and machine-readable name.' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'title' => array(
|
||||
'description' => __( 'A human-readable variation title.' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'A detailed variation description.' ),
|
||||
'type' => 'string',
|
||||
'required' => false,
|
||||
),
|
||||
'category' => $category_definition,
|
||||
'icon' => $icon_definition,
|
||||
'isDefault' => array(
|
||||
'description' => __( 'Indicates whether the current variation is the default one.' ),
|
||||
'type' => 'boolean',
|
||||
'required' => false,
|
||||
'default' => false,
|
||||
),
|
||||
'attributes' => array(
|
||||
'description' => __( 'The initial values for attributes.' ),
|
||||
'type' => 'object',
|
||||
),
|
||||
'innerBlocks' => $inner_blocks_definition,
|
||||
'example' => $example_definition,
|
||||
'scope' => array(
|
||||
'description' => __( 'The list of scopes where the variation is applicable. When not provided, it assumes all available scopes.' ),
|
||||
'type' => array( 'array', 'null' ),
|
||||
'default' => null,
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'block', 'inserter', 'transform' ),
|
||||
),
|
||||
'readonly' => true,
|
||||
),
|
||||
'keywords' => $keywords_definition,
|
||||
),
|
||||
),
|
||||
'readonly' => true,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'default' => null,
|
||||
),
|
||||
'textdomain' => array(
|
||||
'description' => __( 'Public text domain.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'parent' => array(
|
||||
'description' => __( 'Parent blocks.' ),
|
||||
'type' => array( 'array', 'null' ),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'pattern' => self::NAME_PATTERN,
|
||||
),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'ancestor' => array(
|
||||
'description' => __( 'Ancestor blocks.' ),
|
||||
'type' => array( 'array', 'null' ),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'pattern' => self::NAME_PATTERN,
|
||||
),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'allowed_blocks' => array(
|
||||
'description' => __( 'Allowed child block types.' ),
|
||||
'type' => array( 'array', 'null' ),
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'pattern' => self::NAME_PATTERN,
|
||||
),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'keywords' => $keywords_definition,
|
||||
'example' => $example_definition,
|
||||
'block_hooks' => array(
|
||||
'description' => __( 'This block is automatically inserted near any occurrence of the block types used as keys of this map, into a relative position given by the corresponding value.' ),
|
||||
'type' => 'object',
|
||||
'patternProperties' => array(
|
||||
self::NAME_PATTERN => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'before', 'after', 'first_child', 'last_child' ),
|
||||
),
|
||||
),
|
||||
'default' => array(),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Properties deprecated in WordPress 6.1, but left in the schema for backwards compatibility.
|
||||
$deprecated_properties = array(
|
||||
'editor_script' => array(
|
||||
'description' => __( 'Editor script handle. DEPRECATED: Use `editor_script_handles` instead.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'script' => array(
|
||||
'description' => __( 'Public facing and editor script handle. DEPRECATED: Use `script_handles` instead.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'view_script' => array(
|
||||
'description' => __( 'Public facing script handle. DEPRECATED: Use `view_script_handles` instead.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'editor_style' => array(
|
||||
'description' => __( 'Editor style handle. DEPRECATED: Use `editor_style_handles` instead.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'style' => array(
|
||||
'description' => __( 'Public facing and editor style handle. DEPRECATED: Use `style_handles` instead.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
);
|
||||
$this->schema['properties'] = array_merge( $this->schema['properties'], $deprecated_properties );
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for collections.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
return array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
'namespace' => array(
|
||||
'description' => __( 'Block namespace.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* Synced patterns 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 synced patterns (formerly called reusable blocks).
|
||||
* Patterns 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 pattern can be read.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_Post $post Post object that backs the block.
|
||||
* @return bool Whether the pattern can be read.
|
||||
*/
|
||||
public function check_read_permission( $post ) {
|
||||
// By default the read_post capability is mapped to edit_posts.
|
||||
if ( ! current_user_can( '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
|
||||
* @since 6.3.0 Adds the `wp_pattern_sync_status` postmeta property to the top level of response.
|
||||
*
|
||||
* @param array $data Response data to filter.
|
||||
* @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 pattern 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'] );
|
||||
|
||||
// Add the core wp_pattern_sync_status meta as top level property to the response.
|
||||
$data['wp_pattern_sync_status'] = isset( $data['meta']['wp_pattern_sync_status'] ) ? $data['meta']['wp_pattern_sync_status'] : '';
|
||||
unset( $data['meta']['wp_pattern_sync_status'] );
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the pattern's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = parent::get_item_schema();
|
||||
|
||||
/*
|
||||
* Allow all contexts to access `title.raw` and `content.raw`.
|
||||
* Clients always need the raw markup of a pattern 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 pattern 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'] );
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
1915
wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
Normal file
1915
wp-includes/rest-api/endpoints/class-wp-rest-comments-controller.php
Normal file
File diff suppressed because it is too large
Load Diff
681
wp-includes/rest-api/endpoints/class-wp-rest-controller.php
Normal file
681
wp-includes/rest-api/endpoints/class-wp-rest-controller.php
Normal file
@ -0,0 +1,681 @@
|
||||
<?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
|
||||
*/
|
||||
#[AllowDynamicProperties]
|
||||
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;
|
||||
|
||||
/**
|
||||
* Cached results of get_item_schema.
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @var array
|
||||
*/
|
||||
protected $schema;
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
public function register_routes() {
|
||||
_doing_it_wrong(
|
||||
'WP_REST_Controller::register_routes',
|
||||
/* translators: %s: register_routes() */
|
||||
sprintf( __( "Method '%s' must be overridden." ), __METHOD__ ),
|
||||
'4.7.0'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get items.
|
||||
*
|
||||
* @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 ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 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 ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 details about the request.
|
||||
* @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 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 ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 details about the request.
|
||||
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
|
||||
*/
|
||||
public function create_item_permissions_check( $request ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 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 ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 details about the request.
|
||||
* @return true|WP_Error True if the request has access to update the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function update_item_permissions_check( $request ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function update_item( $request ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 details about the request.
|
||||
* @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function delete_item_permissions_check( $request ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function delete_item( $request ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 object|WP_Error The prepared item, or WP_Error object on failure.
|
||||
*/
|
||||
protected function prepare_item_for_database( $request ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
return new WP_Error(
|
||||
'invalid-method',
|
||||
/* translators: %s: Method name. */
|
||||
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 $response_data Response data to filter.
|
||||
* @param string $context Context defined in the schema.
|
||||
* @return array Filtered response.
|
||||
*/
|
||||
public function filter_response_by_context( $response_data, $context ) {
|
||||
|
||||
$schema = $this->get_item_schema();
|
||||
|
||||
return rest_filter_response_by_context( $response_data, $schema, $context );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
if ( ! empty( $schema['properties'] ) ) {
|
||||
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 $response_data Prepared response array.
|
||||
* @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( $response_data, $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 ( ! rest_is_field_included( $field_name, $requested_fields ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$response_data[ $field_name ] = call_user_func(
|
||||
$field_options['get_callback'],
|
||||
$response_data,
|
||||
$field_name,
|
||||
$request,
|
||||
$this->get_object_type()
|
||||
);
|
||||
}
|
||||
|
||||
return $response_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the values of additional fields added to a data object.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param object $data_object Data model like WP_Term or WP_Post.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True on success, WP_Error object if a field cannot be updated.
|
||||
*/
|
||||
protected function update_additional_fields_for_object( $data_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 ],
|
||||
$data_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
|
||||
*
|
||||
* @global array $wp_rest_additional_fields Holds registered fields, organized by object type.
|
||||
*
|
||||
* @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 ) {
|
||||
global $wp_rest_additional_fields;
|
||||
|
||||
if ( ! $object_type ) {
|
||||
$object_type = $this->get_object_type();
|
||||
}
|
||||
|
||||
if ( ! $object_type ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
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 string[] Fields to be included in the response.
|
||||
*/
|
||||
public function get_fields_for_response( $request ) {
|
||||
$schema = $this->get_item_schema();
|
||||
$properties = isset( $schema['properties'] ) ? $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'] ) ) {
|
||||
$properties[ $field_name ] = $field_options;
|
||||
}
|
||||
}
|
||||
|
||||
// Exclude fields that specify a different context than the request context.
|
||||
$context = $request['context'];
|
||||
if ( $context ) {
|
||||
foreach ( $properties as $name => $options ) {
|
||||
if ( ! empty( $options['context'] ) && ! in_array( $context, $options['context'], true ) ) {
|
||||
unset( $properties[ $name ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fields = array_keys( $properties );
|
||||
|
||||
/*
|
||||
* '_links' and '_embedded' are not typically part of the item schema,
|
||||
* but they can be specified in '_fields', so they are added here as a
|
||||
* convenience for checking with rest_is_field_included().
|
||||
*/
|
||||
$fields[] = '_links';
|
||||
if ( $request->has_param( '_embed' ) ) {
|
||||
$fields[] = '_embedded';
|
||||
}
|
||||
|
||||
$fields = array_unique( $fields );
|
||||
|
||||
if ( ! isset( $request['_fields'] ) ) {
|
||||
return $fields;
|
||||
}
|
||||
$requested_fields = wp_parse_list( $request['_fields'] );
|
||||
if ( 0 === count( $requested_fields ) ) {
|
||||
return $fields;
|
||||
}
|
||||
// Trim off outside whitespace from the comma delimited list.
|
||||
$requested_fields = array_map( 'trim', $requested_fields );
|
||||
// Always persist 'id', because it can be needed for add_additional_fields_to_object().
|
||||
if ( in_array( 'id', $fields, true ) ) {
|
||||
$requested_fields[] = 'id';
|
||||
}
|
||||
// Return the list of all requested fields which appear in the schema.
|
||||
return array_reduce(
|
||||
$requested_fields,
|
||||
static function ( $response_fields, $field ) use ( $fields ) {
|
||||
if ( in_array( $field, $fields, true ) ) {
|
||||
$response_fields[] = $field;
|
||||
return $response_fields;
|
||||
}
|
||||
// Check for nested fields if $field is not a direct match.
|
||||
$nested_fields = explode( '.', $field );
|
||||
/*
|
||||
* A nested field is included so long as its top-level property
|
||||
* is present in the schema.
|
||||
*/
|
||||
if ( in_array( $nested_fields[0], $fields, true ) ) {
|
||||
$response_fields[] = $field;
|
||||
}
|
||||
return $response_fields;
|
||||
},
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
return rest_get_endpoint_args_for_schema( $this->get_item_schema(), $method );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,94 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Edit_Site_Export_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller which provides REST endpoint for exporting current templates
|
||||
* and template parts.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Edit_Site_Export_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp-block-editor/v1';
|
||||
$this->rest_base = 'export';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the site export route.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'export' ),
|
||||
'permission_callback' => array( $this, 'permissions_check' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given request has permission to export.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return WP_Error|true True if the request has access, or WP_Error object.
|
||||
*/
|
||||
public function permissions_check() {
|
||||
if ( current_user_can( 'edit_theme_options' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_export_templates',
|
||||
__( 'Sorry, you are not allowed to export templates and template parts.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Output a ZIP file with an export of the current templates
|
||||
* and template parts from the site editor, and close the connection.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return WP_Error|void
|
||||
*/
|
||||
public function export() {
|
||||
// Generate the export file.
|
||||
$filename = wp_generate_block_templates_export_file();
|
||||
|
||||
if ( is_wp_error( $filename ) ) {
|
||||
$filename->add_data( array( 'status' => 500 ) );
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
$theme_name = basename( get_stylesheet() );
|
||||
header( 'Content-Type: application/zip' );
|
||||
header( 'Content-Disposition: attachment; filename=' . $theme_name . '.zip' );
|
||||
header( 'Content-Length: ' . filesize( $filename ) );
|
||||
flush();
|
||||
readfile( $filename );
|
||||
unlink( $filename );
|
||||
exit;
|
||||
}
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
<?php
|
||||
/**
|
||||
* Rest Font Collections Controller.
|
||||
*
|
||||
* This file contains the class for the REST API Font Collections Controller.
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 6.5.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Font Library Controller class.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
class WP_REST_Font_Collections_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->rest_base = 'font-collections';
|
||||
$this->namespace = 'wp/v2';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
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<slug>[\/\w-]+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the font collections available.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$collections_all = WP_Font_Library::get_instance()->get_font_collections();
|
||||
|
||||
$page = $request['page'];
|
||||
$per_page = $request['per_page'];
|
||||
$total_items = count( $collections_all );
|
||||
$max_pages = (int) ceil( $total_items / $per_page );
|
||||
|
||||
if ( $page > $max_pages && $total_items > 0 ) {
|
||||
return new WP_Error(
|
||||
'rest_post_invalid_page_number',
|
||||
__( 'The page number requested is larger than the number of pages available.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$collections_page = array_slice( $collections_all, ( $page - 1 ) * $per_page, $per_page );
|
||||
|
||||
$items = array();
|
||||
foreach ( $collections_page as $collection ) {
|
||||
$item = $this->prepare_item_for_response( $collection, $request );
|
||||
|
||||
// If there's an error loading a collection, skip it and continue loading valid collections.
|
||||
if ( is_wp_error( $item ) ) {
|
||||
continue;
|
||||
}
|
||||
$item = $this->prepare_response_for_collection( $item );
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $items );
|
||||
|
||||
$response->header( 'X-WP-Total', (int) $total_items );
|
||||
$response->header( 'X-WP-TotalPages', $max_pages );
|
||||
|
||||
$request_params = $request->get_query_params();
|
||||
$collection_url = rest_url( $this->namespace . '/' . $this->rest_base );
|
||||
$base = add_query_arg( urlencode_deep( $request_params ), $collection_url );
|
||||
|
||||
if ( $page > 1 ) {
|
||||
$prev_page = $page - 1;
|
||||
|
||||
if ( $prev_page > $max_pages ) {
|
||||
$prev_page = $max_pages;
|
||||
}
|
||||
|
||||
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
||||
$response->link_header( 'prev', $prev_link );
|
||||
}
|
||||
if ( $max_pages > $page ) {
|
||||
$next_page = $page + 1;
|
||||
$next_link = add_query_arg( 'page', $next_page, $base );
|
||||
|
||||
$response->link_header( 'next', $next_link );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a font collection.
|
||||
*
|
||||
* @since 6.5.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 ) {
|
||||
$slug = $request->get_param( 'slug' );
|
||||
$collection = WP_Font_Library::get_instance()->get_font_collection( $slug );
|
||||
|
||||
if ( ! $collection ) {
|
||||
return new WP_Error( 'rest_font_collection_not_found', __( 'Font collection not found.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response( $collection, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a single collection output for response.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_Font_Collection $item Font collection object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( rest_is_field_included( 'slug', $fields ) ) {
|
||||
$data['slug'] = $item->slug;
|
||||
}
|
||||
|
||||
// If any data fields are requested, get the collection data.
|
||||
$data_fields = array( 'name', 'description', 'font_families', 'categories' );
|
||||
if ( ! empty( array_intersect( $fields, $data_fields ) ) ) {
|
||||
$collection_data = $item->get_data();
|
||||
if ( is_wp_error( $collection_data ) ) {
|
||||
$collection_data->add_data( array( 'status' => 500 ) );
|
||||
return $collection_data;
|
||||
}
|
||||
|
||||
foreach ( $data_fields as $field ) {
|
||||
if ( rest_is_field_included( $field, $fields ) ) {
|
||||
$data[ $field ] = $collection_data[ $field ];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) ) {
|
||||
$links = $this->prepare_links( $item );
|
||||
$response->add_links( $links );
|
||||
}
|
||||
|
||||
$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 the font collection data for a REST API response.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param WP_Font_Collection $item The font collection object.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_font_collection', $response, $item, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the font collection's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'font-collection',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'slug' => array(
|
||||
'description' => __( 'Unique identifier for the font collection.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'The name for the font collection.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'The description for the font collection.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'font_families' => array(
|
||||
'description' => __( 'The font families for the font collection.' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'categories' => array(
|
||||
'description' => __( 'The categories for the font collection.' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_Font_Collection $collection Font collection data
|
||||
* @return array Links for the given font collection.
|
||||
*/
|
||||
protected function prepare_links( $collection ) {
|
||||
return array(
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $collection->slug ) ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the search params for the font collections.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$query_params = parent::get_collection_params();
|
||||
|
||||
$query_params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
|
||||
|
||||
unset( $query_params['search'] );
|
||||
|
||||
/**
|
||||
* Filters REST API collection parameters for the font collections controller.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param array $query_params JSON Schema-formatted collection parameters.
|
||||
*/
|
||||
return apply_filters( 'rest_font_collections_collection_params', $query_params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user has permissions to use the Fonts Collections.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
if ( current_user_can( 'edit_theme_options' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_read',
|
||||
__( 'Sorry, you are not allowed to access font collections.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,950 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Font_Faces_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 6.5.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Class to access font faces through the REST API.
|
||||
*/
|
||||
class WP_REST_Font_Faces_Controller extends WP_REST_Posts_Controller {
|
||||
|
||||
/**
|
||||
* The latest version of theme.json schema supported by the controller.
|
||||
*
|
||||
* @since 6.5.0
|
||||
* @var int
|
||||
*/
|
||||
const LATEST_THEME_JSON_VERSION_SUPPORTED = 2;
|
||||
|
||||
/**
|
||||
* Whether the controller supports batching.
|
||||
*
|
||||
* @since 6.5.0
|
||||
* @var false
|
||||
*/
|
||||
protected $allow_batch = false;
|
||||
|
||||
/**
|
||||
* Registers the routes for posts.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
'args' => array(
|
||||
'font_family_id' => array(
|
||||
'description' => __( 'The ID for the parent font family of the font face.' ),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'create_item' ),
|
||||
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
||||
'args' => $this->get_create_params(),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<id>[\d]+)',
|
||||
array(
|
||||
'args' => array(
|
||||
'font_family_id' => array(
|
||||
'description' => __( 'The ID for the parent font family of the font face.' ),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the font face.' ),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
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' => __( 'Whether to bypass Trash and force deletion.', 'default' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to font faces.
|
||||
*
|
||||
* @since 6.5.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 ) {
|
||||
$post_type = get_post_type_object( $this->post_type );
|
||||
|
||||
if ( ! current_user_can( $post_type->cap->read ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_read',
|
||||
__( 'Sorry, you are not allowed to access font faces.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to a font face.
|
||||
*
|
||||
* @since 6.5.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_item_permissions_check( $request ) {
|
||||
$post = $this->get_post( $request['id'] );
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'read_post', $post->ID ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_read',
|
||||
__( 'Sorry, you are not allowed to access this font face.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates settings when creating a font face.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $value Encoded JSON string of font face settings.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return true|WP_Error True if the settings are valid, otherwise a WP_Error object.
|
||||
*/
|
||||
public function validate_create_font_face_settings( $value, $request ) {
|
||||
$settings = json_decode( $value, true );
|
||||
|
||||
// Check settings string is valid JSON.
|
||||
if ( null === $settings ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
__( 'font_face_settings parameter must be a valid JSON string.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
// Check that the font face settings match the theme.json schema.
|
||||
$schema = $this->get_item_schema()['properties']['font_face_settings'];
|
||||
$has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_face_settings' );
|
||||
|
||||
if ( is_wp_error( $has_valid_settings ) ) {
|
||||
$has_valid_settings->add_data( array( 'status' => 400 ) );
|
||||
return $has_valid_settings;
|
||||
}
|
||||
|
||||
// Check that none of the required settings are empty values.
|
||||
$required = $schema['required'];
|
||||
foreach ( $required as $key ) {
|
||||
if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
/* translators: %s: Name of the missing font face settings parameter, e.g. "font_face_settings[src]". */
|
||||
sprintf( __( '%s cannot be empty.' ), "font_face_setting[ $key ]" ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$srcs = is_array( $settings['src'] ) ? $settings['src'] : array( $settings['src'] );
|
||||
$files = $request->get_file_params();
|
||||
|
||||
foreach ( $srcs as $src ) {
|
||||
// Check that each src is a non-empty string.
|
||||
$src = ltrim( $src );
|
||||
if ( empty( $src ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
/* translators: %s: Font face source parameter name: "font_face_settings[src]". */
|
||||
sprintf( __( '%s values must be non-empty strings.' ), 'font_face_settings[src]' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
// Check that srcs are valid URLs or file references.
|
||||
if ( false === wp_http_validate_url( $src ) && ! isset( $files[ $src ] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
/* translators: 1: Font face source parameter name: "font_face_settings[src]", 2: The invalid src value. */
|
||||
sprintf( __( '%1$s value "%2$s" must be a valid URL or file reference.' ), 'font_face_settings[src]', $src ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that each file in the request references a src in the settings.
|
||||
foreach ( array_keys( $files ) as $file ) {
|
||||
if ( ! in_array( $file, $srcs, true ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
/* translators: 1: File key (e.g. "file-0") in the request data, 2: Font face source parameter name: "font_face_settings[src]". */
|
||||
sprintf( __( 'File %1$s must be used in %2$s.' ), $file, 'font_face_settings[src]' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the font face settings when creating a font face.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $value Encoded JSON string of font face settings.
|
||||
* @return array Decoded and sanitized array of font face settings.
|
||||
*/
|
||||
public function sanitize_font_face_settings( $value ) {
|
||||
// Settings arrive as stringified JSON, since this is a multipart/form-data request.
|
||||
$settings = json_decode( $value, true );
|
||||
$schema = $this->get_item_schema()['properties']['font_face_settings']['properties'];
|
||||
|
||||
// Sanitize settings based on callbacks in the schema.
|
||||
foreach ( $settings as $key => $value ) {
|
||||
$sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback'];
|
||||
$settings[ $key ] = call_user_func( $sanitize_callback, $value );
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a collection of font faces within the parent font family.
|
||||
*
|
||||
* @since 6.5.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 ) {
|
||||
$font_family = $this->get_parent_font_family_post( $request['font_family_id'] );
|
||||
if ( is_wp_error( $font_family ) ) {
|
||||
return $font_family;
|
||||
}
|
||||
|
||||
return parent::get_items( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single font face within the parent font family.
|
||||
*
|
||||
* @since 6.5.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 ) {
|
||||
$post = $this->get_post( $request['id'] );
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
// Check that the font face has a valid parent font family.
|
||||
$font_family = $this->get_parent_font_family_post( $request['font_family_id'] );
|
||||
if ( is_wp_error( $font_family ) ) {
|
||||
return $font_family;
|
||||
}
|
||||
|
||||
if ( (int) $font_family->ID !== (int) $post->post_parent ) {
|
||||
return new WP_Error(
|
||||
'rest_font_face_parent_id_mismatch',
|
||||
/* translators: %d: A post id. */
|
||||
sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
return parent::get_item( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a font face for the parent font family.
|
||||
*
|
||||
* @since 6.5.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 ) {
|
||||
$font_family = $this->get_parent_font_family_post( $request['font_family_id'] );
|
||||
if ( is_wp_error( $font_family ) ) {
|
||||
return $font_family;
|
||||
}
|
||||
|
||||
// Settings have already been decoded by ::sanitize_font_face_settings().
|
||||
$settings = $request->get_param( 'font_face_settings' );
|
||||
$file_params = $request->get_file_params();
|
||||
|
||||
// Check that the necessary font face properties are unique.
|
||||
$query = new WP_Query(
|
||||
array(
|
||||
'post_type' => $this->post_type,
|
||||
'posts_per_page' => 1,
|
||||
'title' => WP_Font_Utils::get_font_face_slug( $settings ),
|
||||
'update_post_meta_cache' => false,
|
||||
'update_post_term_cache' => false,
|
||||
)
|
||||
);
|
||||
if ( ! empty( $query->posts ) ) {
|
||||
return new WP_Error(
|
||||
'rest_duplicate_font_face',
|
||||
__( 'A font face matching those settings already exists.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
// Move the uploaded font asset from the temp folder to the fonts directory.
|
||||
if ( ! function_exists( 'wp_handle_upload' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
}
|
||||
|
||||
$srcs = is_string( $settings['src'] ) ? array( $settings['src'] ) : $settings['src'];
|
||||
$processed_srcs = array();
|
||||
$font_file_meta = array();
|
||||
|
||||
foreach ( $srcs as $src ) {
|
||||
// If src not a file reference, use it as is.
|
||||
if ( ! isset( $file_params[ $src ] ) ) {
|
||||
$processed_srcs[] = $src;
|
||||
continue;
|
||||
}
|
||||
|
||||
$file = $file_params[ $src ];
|
||||
$font_file = $this->handle_font_file_upload( $file );
|
||||
if ( is_wp_error( $font_file ) ) {
|
||||
return $font_file;
|
||||
}
|
||||
|
||||
$processed_srcs[] = $font_file['url'];
|
||||
$font_file_meta[] = $this->relative_fonts_path( $font_file['file'] );
|
||||
}
|
||||
|
||||
// Store the updated settings for prepare_item_for_database to use.
|
||||
$settings['src'] = count( $processed_srcs ) === 1 ? $processed_srcs[0] : $processed_srcs;
|
||||
$request->set_param( 'font_face_settings', $settings );
|
||||
|
||||
// Ensure that $settings data is slashed, so values with quotes are escaped.
|
||||
// WP_REST_Posts_Controller::create_item uses wp_slash() on the post_content.
|
||||
$font_face_post = parent::create_item( $request );
|
||||
|
||||
if ( is_wp_error( $font_face_post ) ) {
|
||||
return $font_face_post;
|
||||
}
|
||||
|
||||
$font_face_id = $font_face_post->data['id'];
|
||||
|
||||
foreach ( $font_file_meta as $font_file_path ) {
|
||||
add_post_meta( $font_face_id, '_wp_font_face_file', $font_file_path );
|
||||
}
|
||||
|
||||
return $font_face_post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single font face.
|
||||
*
|
||||
* @since 6.5.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 delete_item( $request ) {
|
||||
$post = $this->get_post( $request['id'] );
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
$font_family = $this->get_parent_font_family_post( $request['font_family_id'] );
|
||||
if ( is_wp_error( $font_family ) ) {
|
||||
return $font_family;
|
||||
}
|
||||
|
||||
if ( (int) $font_family->ID !== (int) $post->post_parent ) {
|
||||
return new WP_Error(
|
||||
'rest_font_face_parent_id_mismatch',
|
||||
/* translators: %d: A post id. */
|
||||
sprintf( __( 'The font face does not belong to the specified font family with id of "%d".' ), $font_family->ID ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
|
||||
|
||||
// We don't support trashing for font faces.
|
||||
if ( ! $force ) {
|
||||
return new WP_Error(
|
||||
'rest_trash_not_supported',
|
||||
/* translators: %s: force=true */
|
||||
sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ),
|
||||
array( 'status' => 501 )
|
||||
);
|
||||
}
|
||||
|
||||
return parent::delete_item( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single font face output for response.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_Post $item Post object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( rest_is_field_included( 'id', $fields ) ) {
|
||||
$data['id'] = $item->ID;
|
||||
}
|
||||
if ( rest_is_field_included( 'theme_json_version', $fields ) ) {
|
||||
$data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'parent', $fields ) ) {
|
||||
$data['parent'] = $item->post_parent;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'font_face_settings', $fields ) ) {
|
||||
$data['font_face_settings'] = $this->get_settings_from_post( $item );
|
||||
}
|
||||
|
||||
$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 ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$links = $this->prepare_links( $item );
|
||||
$response->add_links( $links );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the font face data for a REST API response.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param WP_Post $post Font face post object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_wp_font_face', $response, $item, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => $this->post_type,
|
||||
'type' => 'object',
|
||||
// Base properties for every Post.
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the post.', 'default' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'theme_json_version' => array(
|
||||
'description' => __( 'Version of the theme.json schema used for the typography settings.' ),
|
||||
'type' => 'integer',
|
||||
'default' => static::LATEST_THEME_JSON_VERSION_SUPPORTED,
|
||||
'minimum' => 2,
|
||||
'maximum' => static::LATEST_THEME_JSON_VERSION_SUPPORTED,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'parent' => array(
|
||||
'description' => __( 'The ID for the parent font family of the font face.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
// Font face settings come directly from theme.json schema
|
||||
// See https://schemas.wp.org/trunk/theme.json
|
||||
'font_face_settings' => array(
|
||||
'description' => __( 'font-face declaration in theme.json format.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'properties' => array(
|
||||
'fontFamily' => array(
|
||||
'description' => __( 'CSS font-family value.' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ),
|
||||
),
|
||||
),
|
||||
'fontStyle' => array(
|
||||
'description' => __( 'CSS font-style value.' ),
|
||||
'type' => 'string',
|
||||
'default' => 'normal',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'fontWeight' => array(
|
||||
'description' => __( 'List of available font weights, separated by a space.' ),
|
||||
'default' => '400',
|
||||
// Changed from `oneOf` to avoid errors from loose type checking.
|
||||
// e.g. a fontWeight of "400" validates as both a string and an integer due to is_numeric check.
|
||||
'type' => array( 'string', 'integer' ),
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'fontDisplay' => array(
|
||||
'description' => __( 'CSS font-display value.' ),
|
||||
'type' => 'string',
|
||||
'default' => 'fallback',
|
||||
'enum' => array(
|
||||
'auto',
|
||||
'block',
|
||||
'fallback',
|
||||
'swap',
|
||||
'optional',
|
||||
),
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'src' => array(
|
||||
'description' => __( 'Paths or URLs to the font files.' ),
|
||||
// Changed from `oneOf` to `anyOf` due to rest_sanitize_array converting a string into an array,
|
||||
// and causing a "matches more than one of the expected formats" error.
|
||||
'anyOf' => array(
|
||||
array(
|
||||
'type' => 'string',
|
||||
),
|
||||
array(
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'default' => array(),
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
return is_array( $value ) ? array_map( array( $this, 'sanitize_src' ), $value ) : $this->sanitize_src( $value );
|
||||
},
|
||||
),
|
||||
),
|
||||
'fontStretch' => array(
|
||||
'description' => __( 'CSS font-stretch value.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'ascentOverride' => array(
|
||||
'description' => __( 'CSS ascent-override value.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'descentOverride' => array(
|
||||
'description' => __( 'CSS descent-override value.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'fontVariant' => array(
|
||||
'description' => __( 'CSS font-variant value.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'fontFeatureSettings' => array(
|
||||
'description' => __( 'CSS font-feature-settings value.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'fontVariationSettings' => array(
|
||||
'description' => __( 'CSS font-variation-settings value.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'lineGapOverride' => array(
|
||||
'description' => __( 'CSS line-gap-override value.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'sizeAdjust' => array(
|
||||
'description' => __( 'CSS size-adjust value.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'unicodeRange' => array(
|
||||
'description' => __( 'CSS unicode-range value.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'preview' => array(
|
||||
'description' => __( 'URL to a preview image of the font face.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'default' => '',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_url',
|
||||
),
|
||||
),
|
||||
),
|
||||
'required' => array( 'fontFamily', 'src' ),
|
||||
'additionalProperties' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item's schema for display / public consumption purposes.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array Public item schema data.
|
||||
*/
|
||||
public function get_public_item_schema() {
|
||||
|
||||
$schema = parent::get_public_item_schema();
|
||||
|
||||
// Also remove `arg_options' from child font_family_settings properties, since the parent
|
||||
// controller only handles the top level properties.
|
||||
foreach ( $schema['properties']['font_face_settings']['properties'] as &$property ) {
|
||||
unset( $property['arg_options'] );
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the font face collection.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$query_params = parent::get_collection_params();
|
||||
|
||||
// Remove unneeded params.
|
||||
unset(
|
||||
$query_params['after'],
|
||||
$query_params['modified_after'],
|
||||
$query_params['before'],
|
||||
$query_params['modified_before'],
|
||||
$query_params['search'],
|
||||
$query_params['search_columns'],
|
||||
$query_params['slug'],
|
||||
$query_params['status']
|
||||
);
|
||||
|
||||
$query_params['orderby']['default'] = 'id';
|
||||
$query_params['orderby']['enum'] = array( 'id', 'include' );
|
||||
|
||||
/**
|
||||
* Filters collection parameters for the font face controller.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param array $query_params JSON Schema-formatted collection parameters.
|
||||
*/
|
||||
return apply_filters( 'rest_wp_font_face_collection_params', $query_params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the params used when creating a new font face.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array Font face create arguments.
|
||||
*/
|
||||
public function get_create_params() {
|
||||
$properties = $this->get_item_schema()['properties'];
|
||||
return array(
|
||||
'theme_json_version' => $properties['theme_json_version'],
|
||||
// When creating, font_face_settings is stringified JSON, to work with multipart/form-data used
|
||||
// when uploading font files.
|
||||
'font_face_settings' => array(
|
||||
'description' => __( 'font-face declaration in theme.json format, encoded as a string.' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'validate_callback' => array( $this, 'validate_create_font_face_settings' ),
|
||||
'sanitize_callback' => array( $this, 'sanitize_font_face_settings' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent font family, if the ID is valid.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param int $font_family_id Supplied ID.
|
||||
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_parent_font_family_post( $font_family_id ) {
|
||||
$error = new WP_Error(
|
||||
'rest_post_invalid_parent',
|
||||
__( 'Invalid post parent ID.', 'default' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
|
||||
if ( (int) $font_family_id <= 0 ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$font_family_post = get_post( (int) $font_family_id );
|
||||
|
||||
if ( empty( $font_family_post ) || empty( $font_family_post->ID )
|
||||
|| 'wp_font_family' !== $font_family_post->post_type
|
||||
) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
return $font_family_post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_Post $post Post object.
|
||||
* @return array Links for the given post.
|
||||
*/
|
||||
protected function prepare_links( $post ) {
|
||||
// Entity meta.
|
||||
return array(
|
||||
'self' => array(
|
||||
'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces/' . $post->ID ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent . '/font-faces' ),
|
||||
),
|
||||
'parent' => array(
|
||||
'href' => rest_url( $this->namespace . '/font-families/' . $post->post_parent ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single font face post for creation.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return stdClass Post object.
|
||||
*/
|
||||
protected function prepare_item_for_database( $request ) {
|
||||
$prepared_post = new stdClass();
|
||||
|
||||
// Settings have already been decoded by ::sanitize_font_face_settings().
|
||||
$settings = $request->get_param( 'font_face_settings' );
|
||||
|
||||
// Store this "slug" as the post_title rather than post_name, since it uses the fontFamily setting,
|
||||
// which may contain multibyte characters.
|
||||
$title = WP_Font_Utils::get_font_face_slug( $settings );
|
||||
|
||||
$prepared_post->post_type = $this->post_type;
|
||||
$prepared_post->post_parent = $request['font_family_id'];
|
||||
$prepared_post->post_status = 'publish';
|
||||
$prepared_post->post_title = $title;
|
||||
$prepared_post->post_name = sanitize_title( $title );
|
||||
$prepared_post->post_content = wp_json_encode( $settings );
|
||||
|
||||
return $prepared_post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes a single src value for a font face.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $value Font face src that is a URL or the key for a $_FILES array item.
|
||||
* @return string Sanitized value.
|
||||
*/
|
||||
protected function sanitize_src( $value ) {
|
||||
$value = ltrim( $value );
|
||||
return false === wp_http_validate_url( $value ) ? (string) $value : sanitize_url( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the upload of a font file using wp_handle_upload().
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param array $file Single file item from $_FILES.
|
||||
* @return array|WP_Error Array containing uploaded file attributes on success, or WP_Error object on failure.
|
||||
*/
|
||||
protected function handle_font_file_upload( $file ) {
|
||||
add_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) );
|
||||
// Filter the upload directory to return the fonts directory.
|
||||
add_filter( 'upload_dir', '_wp_filter_font_directory' );
|
||||
|
||||
$overrides = array(
|
||||
'upload_error_handler' => array( $this, 'handle_font_file_upload_error' ),
|
||||
// Not testing a form submission.
|
||||
'test_form' => false,
|
||||
// Only allow uploading font files for this request.
|
||||
'mimes' => WP_Font_Utils::get_allowed_font_mime_types(),
|
||||
);
|
||||
|
||||
// Bypasses is_uploaded_file() when running unit tests.
|
||||
if ( defined( 'DIR_TESTDATA' ) && DIR_TESTDATA ) {
|
||||
$overrides['action'] = 'wp_handle_mock_upload';
|
||||
}
|
||||
|
||||
$uploaded_file = wp_handle_upload( $file, $overrides );
|
||||
|
||||
remove_filter( 'upload_dir', '_wp_filter_font_directory' );
|
||||
remove_filter( 'upload_mimes', array( 'WP_Font_Utils', 'get_allowed_font_mime_types' ) );
|
||||
|
||||
return $uploaded_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles file upload error.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param array $file File upload data.
|
||||
* @param string $message Error message from wp_handle_upload().
|
||||
* @return WP_Error WP_Error object.
|
||||
*/
|
||||
public function handle_font_file_upload_error( $file, $message ) {
|
||||
$status = 500;
|
||||
$code = 'rest_font_upload_unknown_error';
|
||||
|
||||
if ( __( 'Sorry, you are not allowed to upload this file type.' ) === $message ) {
|
||||
$status = 400;
|
||||
$code = 'rest_font_upload_invalid_file_type';
|
||||
}
|
||||
|
||||
return new WP_Error( $code, $message, array( 'status' => $status ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns relative path to an uploaded font file.
|
||||
*
|
||||
* The path is relative to the current fonts directory.
|
||||
*
|
||||
* @since 6.5.0
|
||||
* @access private
|
||||
*
|
||||
* @param string $path Full path to the file.
|
||||
* @return string Relative path on success, unchanged path on failure.
|
||||
*/
|
||||
protected function relative_fonts_path( $path ) {
|
||||
$new_path = $path;
|
||||
|
||||
$fonts_dir = wp_get_font_dir();
|
||||
if ( str_starts_with( $new_path, $fonts_dir['path'] ) ) {
|
||||
$new_path = str_replace( $fonts_dir, '', $new_path );
|
||||
$new_path = ltrim( $new_path, '/' );
|
||||
}
|
||||
|
||||
return $new_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the font face's settings from the post.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_Post $post Font face post object.
|
||||
* @return array Font face settings array.
|
||||
*/
|
||||
protected function get_settings_from_post( $post ) {
|
||||
$settings = json_decode( $post->post_content, true );
|
||||
$properties = $this->get_item_schema()['properties']['font_face_settings']['properties'];
|
||||
|
||||
// Provide required, empty settings if needed.
|
||||
if ( null === $settings ) {
|
||||
$settings = array(
|
||||
'fontFamily' => '',
|
||||
'src' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
// Only return the properties defined in the schema.
|
||||
return array_intersect_key( $settings, $properties );
|
||||
}
|
||||
}
|
@ -0,0 +1,564 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Font_Families_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 6.5.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Font Families Controller class.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*/
|
||||
class WP_REST_Font_Families_Controller extends WP_REST_Posts_Controller {
|
||||
|
||||
/**
|
||||
* The latest version of theme.json schema supported by the controller.
|
||||
*
|
||||
* @since 6.5.0
|
||||
* @var int
|
||||
*/
|
||||
const LATEST_THEME_JSON_VERSION_SUPPORTED = 2;
|
||||
|
||||
/**
|
||||
* Whether the controller supports batching.
|
||||
*
|
||||
* @since 6.5.0
|
||||
* @var false
|
||||
*/
|
||||
protected $allow_batch = false;
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to font families.
|
||||
*
|
||||
* @since 6.5.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 ) {
|
||||
$post_type = get_post_type_object( $this->post_type );
|
||||
|
||||
if ( ! current_user_can( $post_type->cap->read ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_read',
|
||||
__( 'Sorry, you are not allowed to access font families.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to a font family.
|
||||
*
|
||||
* @since 6.5.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_item_permissions_check( $request ) {
|
||||
$post = $this->get_post( $request['id'] );
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'read_post', $post->ID ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_read',
|
||||
__( 'Sorry, you are not allowed to access this font family.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates settings when creating or updating a font family.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $value Encoded JSON string of font family settings.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return true|WP_Error True if the settings are valid, otherwise a WP_Error object.
|
||||
*/
|
||||
public function validate_font_family_settings( $value, $request ) {
|
||||
$settings = json_decode( $value, true );
|
||||
|
||||
// Check settings string is valid JSON.
|
||||
if ( null === $settings ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
/* translators: %s: Parameter name: "font_family_settings". */
|
||||
sprintf( __( '%s parameter must be a valid JSON string.' ), 'font_family_settings' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$schema = $this->get_item_schema()['properties']['font_family_settings'];
|
||||
$required = $schema['required'];
|
||||
|
||||
if ( isset( $request['id'] ) ) {
|
||||
// Allow sending individual properties if we are updating an existing font family.
|
||||
unset( $schema['required'] );
|
||||
|
||||
// But don't allow updating the slug, since it is used as a unique identifier.
|
||||
if ( isset( $settings['slug'] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
/* translators: %s: Name of parameter being updated: font_family_settings[slug]". */
|
||||
sprintf( __( '%s cannot be updated.' ), 'font_family_settings[slug]' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that the font face settings match the theme.json schema.
|
||||
$has_valid_settings = rest_validate_value_from_schema( $settings, $schema, 'font_family_settings' );
|
||||
|
||||
if ( is_wp_error( $has_valid_settings ) ) {
|
||||
$has_valid_settings->add_data( array( 'status' => 400 ) );
|
||||
return $has_valid_settings;
|
||||
}
|
||||
|
||||
// Check that none of the required settings are empty values.
|
||||
foreach ( $required as $key ) {
|
||||
if ( isset( $settings[ $key ] ) && ! $settings[ $key ] ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
/* translators: %s: Name of the empty font family setting parameter, e.g. "font_family_settings[slug]". */
|
||||
sprintf( __( '%s cannot be empty.' ), "font_family_settings[ $key ]" ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the font family settings when creating or updating a font family.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param string $value Encoded JSON string of font family settings.
|
||||
* @return array Decoded array of font family settings.
|
||||
*/
|
||||
public function sanitize_font_family_settings( $value ) {
|
||||
// Settings arrive as stringified JSON, since this is a multipart/form-data request.
|
||||
$settings = json_decode( $value, true );
|
||||
$schema = $this->get_item_schema()['properties']['font_family_settings']['properties'];
|
||||
|
||||
// Sanitize settings based on callbacks in the schema.
|
||||
foreach ( $settings as $key => $value ) {
|
||||
$sanitize_callback = $schema[ $key ]['arg_options']['sanitize_callback'];
|
||||
$settings[ $key ] = call_user_func( $sanitize_callback, $value );
|
||||
}
|
||||
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single font family.
|
||||
*
|
||||
* @since 6.5.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 ) {
|
||||
$settings = $request->get_param( 'font_family_settings' );
|
||||
|
||||
// Check that the font family slug is unique.
|
||||
$query = new WP_Query(
|
||||
array(
|
||||
'post_type' => $this->post_type,
|
||||
'posts_per_page' => 1,
|
||||
'name' => $settings['slug'],
|
||||
'update_post_meta_cache' => false,
|
||||
'update_post_term_cache' => false,
|
||||
)
|
||||
);
|
||||
if ( ! empty( $query->posts ) ) {
|
||||
return new WP_Error(
|
||||
'rest_duplicate_font_family',
|
||||
/* translators: %s: Font family slug. */
|
||||
sprintf( __( 'A font family with slug "%s" already exists.' ), $settings['slug'] ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
return parent::create_item( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single font family.
|
||||
*
|
||||
* @since 6.5.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 delete_item( $request ) {
|
||||
$force = isset( $request['force'] ) ? (bool) $request['force'] : false;
|
||||
|
||||
// We don't support trashing for font families.
|
||||
if ( ! $force ) {
|
||||
return new WP_Error(
|
||||
'rest_trash_not_supported',
|
||||
/* translators: %s: force=true */
|
||||
sprintf( __( 'Font faces do not support trashing. Set "%s" to delete.' ), 'force=true' ),
|
||||
array( 'status' => 501 )
|
||||
);
|
||||
}
|
||||
|
||||
return parent::delete_item( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single font family output for response.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_Post $item Post object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( rest_is_field_included( 'id', $fields ) ) {
|
||||
$data['id'] = $item->ID;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'theme_json_version', $fields ) ) {
|
||||
$data['theme_json_version'] = static::LATEST_THEME_JSON_VERSION_SUPPORTED;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'font_faces', $fields ) ) {
|
||||
$data['font_faces'] = $this->get_font_face_ids( $item->ID );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'font_family_settings', $fields ) ) {
|
||||
$data['font_family_settings'] = $this->get_settings_from_post( $item );
|
||||
}
|
||||
|
||||
$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 ( rest_is_field_included( '_links', $fields ) ) {
|
||||
$links = $this->prepare_links( $item );
|
||||
$response->add_links( $links );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the font family data for a REST API response.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param WP_Post $post Font family post object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_wp_font_family', $response, $item, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => $this->post_type,
|
||||
'type' => 'object',
|
||||
// Base properties for every Post.
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the post.', 'default' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'theme_json_version' => array(
|
||||
'description' => __( 'Version of the theme.json schema used for the typography settings.' ),
|
||||
'type' => 'integer',
|
||||
'default' => static::LATEST_THEME_JSON_VERSION_SUPPORTED,
|
||||
'minimum' => 2,
|
||||
'maximum' => static::LATEST_THEME_JSON_VERSION_SUPPORTED,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'font_faces' => array(
|
||||
'description' => __( 'The IDs of the child font faces in the font family.' ),
|
||||
'type' => 'array',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'items' => array(
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
// Font family settings come directly from theme.json schema
|
||||
// See https://schemas.wp.org/trunk/theme.json
|
||||
'font_family_settings' => array(
|
||||
'description' => __( 'font-face definition in theme.json format.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'properties' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'Name of the font family preset, translatable.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
),
|
||||
),
|
||||
'slug' => array(
|
||||
'description' => __( 'Kebab-case unique identifier for the font family preset.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_title',
|
||||
),
|
||||
),
|
||||
'fontFamily' => array(
|
||||
'description' => __( 'CSS font-family value.' ),
|
||||
'type' => 'string',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ),
|
||||
),
|
||||
),
|
||||
'preview' => array(
|
||||
'description' => __( 'URL to a preview image of the font family.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'default' => '',
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => 'sanitize_url',
|
||||
),
|
||||
),
|
||||
),
|
||||
'required' => array( 'name', 'slug', 'fontFamily' ),
|
||||
'additionalProperties' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item's schema for display / public consumption purposes.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array Public item schema data.
|
||||
*/
|
||||
public function get_public_item_schema() {
|
||||
|
||||
$schema = parent::get_public_item_schema();
|
||||
|
||||
// Also remove `arg_options' from child font_family_settings properties, since the parent
|
||||
// controller only handles the top level properties.
|
||||
foreach ( $schema['properties']['font_family_settings']['properties'] as &$property ) {
|
||||
unset( $property['arg_options'] );
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the font family collection.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$query_params = parent::get_collection_params();
|
||||
|
||||
// Remove unneeded params.
|
||||
unset(
|
||||
$query_params['after'],
|
||||
$query_params['modified_after'],
|
||||
$query_params['before'],
|
||||
$query_params['modified_before'],
|
||||
$query_params['search'],
|
||||
$query_params['search_columns'],
|
||||
$query_params['status']
|
||||
);
|
||||
|
||||
$query_params['orderby']['default'] = 'id';
|
||||
$query_params['orderby']['enum'] = array( 'id', 'include' );
|
||||
|
||||
/**
|
||||
* Filters collection parameters for the font family controller.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param array $query_params JSON Schema-formatted collection parameters.
|
||||
*/
|
||||
return apply_filters( 'rest_wp_font_family_collection_params', $query_params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the arguments used when creating or updating a font family.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @return array Font family create/edit arguments.
|
||||
*/
|
||||
public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
|
||||
if ( WP_REST_Server::CREATABLE === $method || WP_REST_Server::EDITABLE === $method ) {
|
||||
$properties = $this->get_item_schema()['properties'];
|
||||
return array(
|
||||
'theme_json_version' => $properties['theme_json_version'],
|
||||
// When creating or updating, font_family_settings is stringified JSON, to work with multipart/form-data.
|
||||
// Font families don't currently support file uploads, but may accept preview files in the future.
|
||||
'font_family_settings' => array(
|
||||
'description' => __( 'font-family declaration in theme.json format, encoded as a string.' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'validate_callback' => array( $this, 'validate_font_family_settings' ),
|
||||
'sanitize_callback' => array( $this, 'sanitize_font_family_settings' ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return parent::get_endpoint_args_for_item_schema( $method );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the child font face post IDs.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param int $font_family_id Font family post ID.
|
||||
* @return int[] Array of child font face post IDs.
|
||||
*/
|
||||
protected function get_font_face_ids( $font_family_id ) {
|
||||
$query = new WP_Query(
|
||||
array(
|
||||
'fields' => 'ids',
|
||||
'post_parent' => $font_family_id,
|
||||
'post_type' => 'wp_font_face',
|
||||
'posts_per_page' => 99,
|
||||
'order' => 'ASC',
|
||||
'orderby' => 'id',
|
||||
'update_post_meta_cache' => false,
|
||||
'update_post_term_cache' => false,
|
||||
)
|
||||
);
|
||||
|
||||
return $query->posts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares font family links for the request.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_Post $post Post object.
|
||||
* @return array Links for the given post.
|
||||
*/
|
||||
protected function prepare_links( $post ) {
|
||||
// Entity meta.
|
||||
$links = parent::prepare_links( $post );
|
||||
|
||||
return array(
|
||||
'self' => $links['self'],
|
||||
'collection' => $links['collection'],
|
||||
'font_faces' => $this->prepare_font_face_links( $post->ID ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares child font face links for the request.
|
||||
*
|
||||
* @param int $font_family_id Font family post ID.
|
||||
* @return array Links for the child font face posts.
|
||||
*/
|
||||
protected function prepare_font_face_links( $font_family_id ) {
|
||||
$font_face_ids = $this->get_font_face_ids( $font_family_id );
|
||||
$links = array();
|
||||
foreach ( $font_face_ids as $font_face_id ) {
|
||||
$links[] = array(
|
||||
'embeddable' => true,
|
||||
'href' => rest_url( sprintf( '%s/%s/%s/font-faces/%s', $this->namespace, $this->rest_base, $font_family_id, $font_face_id ) ),
|
||||
);
|
||||
}
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single font family post for create or update.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return stdClass|WP_Error Post object or WP_Error.
|
||||
*/
|
||||
protected function prepare_item_for_database( $request ) {
|
||||
$prepared_post = new stdClass();
|
||||
// Settings have already been decoded by ::sanitize_font_family_settings().
|
||||
$settings = $request->get_param( 'font_family_settings' );
|
||||
|
||||
// This is an update and we merge with the existing font family.
|
||||
if ( isset( $request['id'] ) ) {
|
||||
$existing_post = $this->get_post( $request['id'] );
|
||||
if ( is_wp_error( $existing_post ) ) {
|
||||
return $existing_post;
|
||||
}
|
||||
|
||||
$prepared_post->ID = $existing_post->ID;
|
||||
$existing_settings = $this->get_settings_from_post( $existing_post );
|
||||
$settings = array_merge( $existing_settings, $settings );
|
||||
}
|
||||
|
||||
$prepared_post->post_type = $this->post_type;
|
||||
$prepared_post->post_status = 'publish';
|
||||
$prepared_post->post_title = $settings['name'];
|
||||
$prepared_post->post_name = sanitize_title( $settings['slug'] );
|
||||
|
||||
// Remove duplicate information from settings.
|
||||
unset( $settings['name'] );
|
||||
unset( $settings['slug'] );
|
||||
|
||||
$prepared_post->post_content = wp_json_encode( $settings );
|
||||
|
||||
return $prepared_post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the font family's settings from the post.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @param WP_Post $post Font family post object.
|
||||
* @return array Font family settings array.
|
||||
*/
|
||||
protected function get_settings_from_post( $post ) {
|
||||
$settings_json = json_decode( $post->post_content, true );
|
||||
|
||||
// Default to empty strings if the settings are missing.
|
||||
return array(
|
||||
'name' => isset( $post->post_title ) && $post->post_title ? $post->post_title : '',
|
||||
'slug' => isset( $post->post_name ) && $post->post_name ? $post->post_name : '',
|
||||
'fontFamily' => isset( $settings_json['fontFamily'] ) && $settings_json['fontFamily'] ? $settings_json['fontFamily'] : '',
|
||||
'preview' => isset( $settings_json['preview'] ) && $settings_json['preview'] ? $settings_json['preview'] : '',
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,708 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Global_Styles_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.9.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base Global Styles REST API Controller.
|
||||
*/
|
||||
class WP_REST_Global_Styles_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Post type.
|
||||
*
|
||||
* @since 5.9.0
|
||||
* @var string
|
||||
*/
|
||||
protected $post_type;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @since 5.9.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'global-styles';
|
||||
$this->post_type = 'wp_global_styles';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the controllers routes.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/themes/(?P<stylesheet>[\/\s%\w\.\(\)\[\]\@_\-]+)/variations',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_theme_items' ),
|
||||
'permission_callback' => array( $this, 'get_theme_items_permissions_check' ),
|
||||
'args' => array(
|
||||
'stylesheet' => array(
|
||||
'description' => __( 'The theme identifier' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// List themes global styles.
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
// The route.
|
||||
sprintf(
|
||||
'/%s/themes/(?P<stylesheet>%s)',
|
||||
$this->rest_base,
|
||||
/*
|
||||
* Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
|
||||
* Excludes invalid directory name characters: `/:<>*?"|`.
|
||||
*/
|
||||
'[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?'
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_theme_item' ),
|
||||
'permission_callback' => array( $this, 'get_theme_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'stylesheet' => array(
|
||||
'description' => __( 'The theme identifier' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( $this, '_sanitize_global_styles_callback' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Lists/updates a single global style variation based on the given id.
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<id>[\/\w-]+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'The id of a template' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( $this, '_sanitize_global_styles_callback' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'update_item' ),
|
||||
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the global styles ID or stylesheet to decode endpoint.
|
||||
* For example, `wp/v2/global-styles/twentytwentytwo%200.4.0`
|
||||
* would be decoded to `twentytwentytwo 0.4.0`.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $id_or_stylesheet Global styles ID or stylesheet.
|
||||
* @return string Sanitized global styles ID or stylesheet.
|
||||
*/
|
||||
public function _sanitize_global_styles_callback( $id_or_stylesheet ) {
|
||||
return urldecode( $id_or_stylesheet );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the post, if the ID is valid.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param int $id Supplied ID.
|
||||
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_post( $id ) {
|
||||
$error = new WP_Error(
|
||||
'rest_global_styles_not_found',
|
||||
__( 'No global styles config exist with that id.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
|
||||
$id = (int) $id;
|
||||
if ( $id <= 0 ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$post = get_post( $id );
|
||||
if ( empty( $post ) || empty( $post->ID ) || $this->post_type !== $post->post_type ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read a single global style.
|
||||
*
|
||||
* @since 5.9.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_item_permissions_check( $request ) {
|
||||
$post = $this->get_post( $request['id'] );
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
if ( 'edit' === $request['context'] && $post && ! $this->check_update_permission( $post ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden_context',
|
||||
__( 'Sorry, you are not allowed to edit this global style.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $this->check_read_permission( $post ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_view',
|
||||
__( 'Sorry, you are not allowed to view this global style.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a global style can be read.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_Post $post Post object.
|
||||
* @return bool Whether the post can be read.
|
||||
*/
|
||||
protected function check_read_permission( $post ) {
|
||||
return current_user_can( 'read_post', $post->ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given global styles config.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request instance.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
$post = $this->get_post( $request['id'] );
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response( $post, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to write a single global styles config.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has write access for the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function update_item_permissions_check( $request ) {
|
||||
$post = $this->get_post( $request['id'] );
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
if ( $post && ! $this->check_update_permission( $post ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_edit',
|
||||
__( 'Sorry, you are not allowed to edit this global style.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a global style can be edited.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_Post $post Post object.
|
||||
* @return bool Whether the post can be edited.
|
||||
*/
|
||||
protected function check_update_permission( $post ) {
|
||||
return current_user_can( 'edit_post', $post->ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single global style config.
|
||||
*
|
||||
* @since 5.9.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 update_item( $request ) {
|
||||
$post_before = $this->get_post( $request['id'] );
|
||||
if ( is_wp_error( $post_before ) ) {
|
||||
return $post_before;
|
||||
}
|
||||
|
||||
$changes = $this->prepare_item_for_database( $request );
|
||||
if ( is_wp_error( $changes ) ) {
|
||||
return $changes;
|
||||
}
|
||||
|
||||
$result = wp_update_post( wp_slash( (array) $changes ), true, false );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$post = get_post( $request['id'] );
|
||||
$fields_update = $this->update_additional_fields_for_object( $post, $request );
|
||||
if ( is_wp_error( $fields_update ) ) {
|
||||
return $fields_update;
|
||||
}
|
||||
|
||||
wp_after_insert_post( $post, true, $post_before );
|
||||
|
||||
$response = $this->prepare_item_for_response( $post, $request );
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single global styles config for update.
|
||||
*
|
||||
* @since 5.9.0
|
||||
* @since 6.2.0 Added validation of styles.css property.
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return stdClass|WP_Error Prepared item on success. WP_Error on when the custom CSS is not valid.
|
||||
*/
|
||||
protected function prepare_item_for_database( $request ) {
|
||||
$changes = new stdClass();
|
||||
$changes->ID = $request['id'];
|
||||
|
||||
$post = get_post( $request['id'] );
|
||||
$existing_config = array();
|
||||
if ( $post ) {
|
||||
$existing_config = json_decode( $post->post_content, true );
|
||||
$json_decoding_error = json_last_error();
|
||||
if ( JSON_ERROR_NONE !== $json_decoding_error || ! isset( $existing_config['isGlobalStylesUserThemeJSON'] ) ||
|
||||
! $existing_config['isGlobalStylesUserThemeJSON'] ) {
|
||||
$existing_config = array();
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $request['styles'] ) || isset( $request['settings'] ) ) {
|
||||
$config = array();
|
||||
if ( isset( $request['styles'] ) ) {
|
||||
if ( isset( $request['styles']['css'] ) ) {
|
||||
$css_validation_result = $this->validate_custom_css( $request['styles']['css'] );
|
||||
if ( is_wp_error( $css_validation_result ) ) {
|
||||
return $css_validation_result;
|
||||
}
|
||||
}
|
||||
$config['styles'] = $request['styles'];
|
||||
} elseif ( isset( $existing_config['styles'] ) ) {
|
||||
$config['styles'] = $existing_config['styles'];
|
||||
}
|
||||
if ( isset( $request['settings'] ) ) {
|
||||
$config['settings'] = $request['settings'];
|
||||
} elseif ( isset( $existing_config['settings'] ) ) {
|
||||
$config['settings'] = $existing_config['settings'];
|
||||
}
|
||||
$config['isGlobalStylesUserThemeJSON'] = true;
|
||||
$config['version'] = WP_Theme_JSON::LATEST_SCHEMA;
|
||||
$changes->post_content = wp_json_encode( $config );
|
||||
}
|
||||
|
||||
// Post title.
|
||||
if ( isset( $request['title'] ) ) {
|
||||
if ( is_string( $request['title'] ) ) {
|
||||
$changes->post_title = $request['title'];
|
||||
} elseif ( ! empty( $request['title']['raw'] ) ) {
|
||||
$changes->post_title = $request['title']['raw'];
|
||||
}
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a global styles config output for response.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_Post $post Global Styles post object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $post, $request ) {
|
||||
$raw_config = json_decode( $post->post_content, true );
|
||||
$is_global_styles_user_theme_json = isset( $raw_config['isGlobalStylesUserThemeJSON'] ) && true === $raw_config['isGlobalStylesUserThemeJSON'];
|
||||
$config = array();
|
||||
if ( $is_global_styles_user_theme_json ) {
|
||||
$config = ( new WP_Theme_JSON( $raw_config, 'custom' ) )->get_raw_data();
|
||||
}
|
||||
|
||||
// Base fields for every post.
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( rest_is_field_included( 'id', $fields ) ) {
|
||||
$data['id'] = $post->ID;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'title', $fields ) ) {
|
||||
$data['title'] = array();
|
||||
}
|
||||
if ( rest_is_field_included( 'title.raw', $fields ) ) {
|
||||
$data['title']['raw'] = $post->post_title;
|
||||
}
|
||||
if ( rest_is_field_included( 'title.rendered', $fields ) ) {
|
||||
add_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
|
||||
|
||||
$data['title']['rendered'] = get_the_title( $post->ID );
|
||||
|
||||
remove_filter( 'protected_title_format', array( $this, 'protected_title_format' ) );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'settings', $fields ) ) {
|
||||
$data['settings'] = ! empty( $config['settings'] ) && $is_global_styles_user_theme_json ? $config['settings'] : new stdClass();
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'styles', $fields ) ) {
|
||||
$data['styles'] = ! empty( $config['styles'] ) && $is_global_styles_user_theme_json ? $config['styles'] : new stdClass();
|
||||
}
|
||||
|
||||
$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 );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$links = $this->prepare_links( $post->ID );
|
||||
$response->add_links( $links );
|
||||
if ( ! empty( $links['self']['href'] ) ) {
|
||||
$actions = $this->get_available_actions();
|
||||
$self = $links['self']['href'];
|
||||
foreach ( $actions as $rel ) {
|
||||
$response->add_link( $rel, $self );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 5.9.0
|
||||
* @since 6.3.0 Adds revisions count and rest URL href to version-history.
|
||||
*
|
||||
* @param integer $id ID.
|
||||
* @return array Links for the given post.
|
||||
*/
|
||||
protected function prepare_links( $id ) {
|
||||
$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
|
||||
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( trailingslashit( $base ) . $id ),
|
||||
),
|
||||
);
|
||||
|
||||
if ( post_type_supports( $this->post_type, 'revisions' ) ) {
|
||||
$revisions = wp_get_latest_revision_id_and_total_count( $id );
|
||||
$revisions_count = ! is_wp_error( $revisions ) ? $revisions['count'] : 0;
|
||||
$revisions_base = sprintf( '/%s/%d/revisions', $base, $id );
|
||||
$links['version-history'] = array(
|
||||
'href' => rest_url( $revisions_base ),
|
||||
'count' => $revisions_count,
|
||||
);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link relations available for the post and current user.
|
||||
*
|
||||
* @since 5.9.0
|
||||
* @since 6.2.0 Added 'edit-css' action.
|
||||
*
|
||||
* @return array List of link relations.
|
||||
*/
|
||||
protected function get_available_actions() {
|
||||
$rels = array();
|
||||
|
||||
$post_type = get_post_type_object( $this->post_type );
|
||||
if ( current_user_can( $post_type->cap->publish_posts ) ) {
|
||||
$rels[] = 'https://api.w.org/action-publish';
|
||||
}
|
||||
|
||||
if ( current_user_can( 'edit_css' ) ) {
|
||||
$rels[] = 'https://api.w.org/action-edit-css';
|
||||
}
|
||||
|
||||
return $rels;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.9.0
|
||||
*
|
||||
* @return string Protected title format.
|
||||
*/
|
||||
public function protected_title_format() {
|
||||
return '%s';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the global styles collection.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the global styles type' schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => $this->post_type,
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'ID of global styles config.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'styles' => array(
|
||||
'description' => __( 'Global styles.' ),
|
||||
'type' => array( 'object' ),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'settings' => array(
|
||||
'description' => __( 'Global settings.' ),
|
||||
'type' => array( 'object' ),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'title' => array(
|
||||
'description' => __( 'Title of the global styles variation.' ),
|
||||
'type' => array( 'object', 'string' ),
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'description' => __( 'Title for the global styles variation, as it exists in the database.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'HTML title for the post, transformed for display.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read a single theme global styles config.
|
||||
*
|
||||
* @since 5.9.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, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_theme_item_permissions_check( $request ) {
|
||||
/*
|
||||
* Verify if the current user has edit_theme_options capability.
|
||||
* This capability is required to edit/view/delete templates.
|
||||
*/
|
||||
if ( ! current_user_can( 'edit_theme_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_manage_global_styles',
|
||||
__( 'Sorry, you are not allowed to access the global styles on this site.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given theme global styles config.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request instance.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_theme_item( $request ) {
|
||||
if ( get_stylesheet() !== $request['stylesheet'] ) {
|
||||
// This endpoint only supports the active theme for now.
|
||||
return new WP_Error(
|
||||
'rest_theme_not_found',
|
||||
__( 'Theme not found.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$theme = WP_Theme_JSON_Resolver::get_merged_data( 'theme' );
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( rest_is_field_included( 'settings', $fields ) ) {
|
||||
$data['settings'] = $theme->get_settings();
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'styles', $fields ) ) {
|
||||
$raw_data = $theme->get_raw_data();
|
||||
$data['styles'] = isset( $raw_data['styles'] ) ? $raw_data['styles'] : array();
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s/themes/%s', $this->namespace, $this->rest_base, $request['stylesheet'] ) ),
|
||||
),
|
||||
);
|
||||
$response->add_links( $links );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read a single theme global styles config.
|
||||
*
|
||||
* @since 6.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, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_theme_items_permissions_check( $request ) {
|
||||
/*
|
||||
* Verify if the current user has edit_theme_options capability.
|
||||
* This capability is required to edit/view/delete templates.
|
||||
*/
|
||||
if ( ! current_user_can( 'edit_theme_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_manage_global_styles',
|
||||
__( 'Sorry, you are not allowed to access the global styles on this site.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given theme global styles variations.
|
||||
*
|
||||
* @since 6.0.0
|
||||
* @since 6.2.0 Returns parent theme variations, if they exist.
|
||||
*
|
||||
* @param WP_REST_Request $request The request instance.
|
||||
*
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_theme_items( $request ) {
|
||||
if ( get_stylesheet() !== $request['stylesheet'] ) {
|
||||
// This endpoint only supports the active theme for now.
|
||||
return new WP_Error(
|
||||
'rest_theme_not_found',
|
||||
__( 'Theme not found.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$variations = WP_Theme_JSON_Resolver::get_style_variations();
|
||||
|
||||
return rest_ensure_response( $variations );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate style.css as valid CSS.
|
||||
*
|
||||
* Currently just checks for invalid markup.
|
||||
*
|
||||
* @since 6.2.0
|
||||
* @since 6.4.0 Changed method visibility to protected.
|
||||
*
|
||||
* @param string $css CSS to validate.
|
||||
* @return true|WP_Error True if the input was validated, otherwise WP_Error.
|
||||
*/
|
||||
protected function validate_custom_css( $css ) {
|
||||
if ( preg_match( '#</?\w+#', $css ) ) {
|
||||
return new WP_Error(
|
||||
'rest_custom_css_illegal_markup',
|
||||
__( 'Markup is not allowed in CSS.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -0,0 +1,551 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Global_Styles_Revisions_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 6.3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access global styles revisions via the REST API.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Global_Styles_Revisions_Controller extends WP_REST_Controller {
|
||||
/**
|
||||
* Parent post type.
|
||||
*
|
||||
* @since 6.3.0
|
||||
* @var string
|
||||
*/
|
||||
protected $parent_post_type;
|
||||
|
||||
/**
|
||||
* The base of the parent controller's route.
|
||||
*
|
||||
* @since 6.3.0
|
||||
* @var string
|
||||
*/
|
||||
protected $parent_base;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->parent_post_type = 'wp_global_styles';
|
||||
$this->rest_base = 'revisions';
|
||||
$this->parent_base = 'global-styles';
|
||||
$this->namespace = 'wp/v2';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the controller's routes.
|
||||
*
|
||||
* @since 6.3.0
|
||||
* @since 6.5.0 Added route to fetch individual global styles revisions.
|
||||
*/
|
||||
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 revision.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_item_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 global styles revision.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the global styles revision.' ),
|
||||
'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' ) ),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for collections.
|
||||
*
|
||||
* Inherits from WP_REST_Controller::get_collection_params(),
|
||||
* also reflects changes to return value WP_REST_Revisions_Controller::get_collection_params().
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$collection_params = parent::get_collection_params();
|
||||
$collection_params['context']['default'] = 'view';
|
||||
$collection_params['offset'] = array(
|
||||
'description' => __( 'Offset the result set by a specific number of items.' ),
|
||||
'type' => 'integer',
|
||||
);
|
||||
unset( $collection_params['search'] );
|
||||
unset( $collection_params['per_page']['default'] );
|
||||
|
||||
return $collection_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns decoded JSON from post content string,
|
||||
* or a 404 if not found.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @param string $raw_json Encoded JSON from global styles custom post content.
|
||||
* @return Array|WP_Error
|
||||
*/
|
||||
protected function get_decoded_global_styles_json( $raw_json ) {
|
||||
$decoded_json = json_decode( $raw_json, true );
|
||||
|
||||
if ( is_array( $decoded_json ) && isset( $decoded_json['isGlobalStylesUserThemeJSON'] ) && true === $decoded_json['isGlobalStylesUserThemeJSON'] ) {
|
||||
return $decoded_json;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_global_styles_not_found',
|
||||
__( 'Cannot find user global styles revisions.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns paginated revisions of the given global styles config custom post type.
|
||||
*
|
||||
* The bulk of the body is taken from WP_REST_Revisions_Controller->get_items,
|
||||
* but global styles does not require as many parameters.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request instance.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$parent = $this->get_parent( $request['parent'] );
|
||||
|
||||
if ( is_wp_error( $parent ) ) {
|
||||
return $parent;
|
||||
}
|
||||
|
||||
$global_styles_config = $this->get_decoded_global_styles_json( $parent->post_content );
|
||||
|
||||
if ( is_wp_error( $global_styles_config ) ) {
|
||||
return $global_styles_config;
|
||||
}
|
||||
|
||||
if ( wp_revisions_enabled( $parent ) ) {
|
||||
$registered = $this->get_collection_params();
|
||||
$query_args = array(
|
||||
'post_parent' => $parent->ID,
|
||||
'post_type' => 'revision',
|
||||
'post_status' => 'inherit',
|
||||
'posts_per_page' => -1,
|
||||
'orderby' => 'date ID',
|
||||
'order' => 'DESC',
|
||||
);
|
||||
|
||||
$parameter_mappings = array(
|
||||
'offset' => 'offset',
|
||||
'page' => 'paged',
|
||||
'per_page' => 'posts_per_page',
|
||||
);
|
||||
|
||||
foreach ( $parameter_mappings as $api_param => $wp_param ) {
|
||||
if ( isset( $registered[ $api_param ], $request[ $api_param ] ) ) {
|
||||
$query_args[ $wp_param ] = $request[ $api_param ];
|
||||
}
|
||||
}
|
||||
|
||||
$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 = (int) 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_path = rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) );
|
||||
$base = add_query_arg( urlencode_deep( $request_params ), $base_path );
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves one global styles revision from the collection.
|
||||
*
|
||||
* @since 6.5.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 ) {
|
||||
$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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the global styles revision, if the ID is valid.
|
||||
*
|
||||
* @since 6.5.0
|
||||
*
|
||||
* @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 global styles 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the post_date_gmt or modified_gmt and prepare any post or
|
||||
* modified date for single post output.
|
||||
*
|
||||
* Duplicate of WP_REST_Revisions_Controller::prepare_date_response.
|
||||
*
|
||||
* @since 6.3.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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the revision for the REST response.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @param WP_Post $post Post revision object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $post, $request ) {
|
||||
$parent = $this->get_parent( $request['parent'] );
|
||||
$global_styles_config = $this->get_decoded_global_styles_json( $post->post_content );
|
||||
|
||||
if ( is_wp_error( $global_styles_config ) ) {
|
||||
return $global_styles_config;
|
||||
}
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( ! empty( $global_styles_config['styles'] ) || ! empty( $global_styles_config['settings'] ) ) {
|
||||
$global_styles_config = ( new WP_Theme_JSON( $global_styles_config, 'custom' ) )->get_raw_data();
|
||||
if ( rest_is_field_included( 'settings', $fields ) ) {
|
||||
$data['settings'] = ! empty( $global_styles_config['settings'] ) ? $global_styles_config['settings'] : new stdClass();
|
||||
}
|
||||
if ( rest_is_field_included( 'styles', $fields ) ) {
|
||||
$data['styles'] = ! empty( $global_styles_config['styles'] ) ? $global_styles_config['styles'] : new stdClass();
|
||||
}
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'author', $fields ) ) {
|
||||
$data['author'] = (int) $post->post_author;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'date', $fields ) ) {
|
||||
$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'date_gmt', $fields ) ) {
|
||||
$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'id', $fields ) ) {
|
||||
$data['id'] = (int) $post->ID;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'modified', $fields ) ) {
|
||||
$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'modified_gmt', $fields ) ) {
|
||||
$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'parent', $fields ) ) {
|
||||
$data['parent'] = (int) $parent->ID;
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the revision's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->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(
|
||||
|
||||
/*
|
||||
* Adds settings and styles from the WP_REST_Revisions_Controller item fields.
|
||||
* Leaves out GUID as global styles shouldn't be accessible via URL.
|
||||
*/
|
||||
'author' => array(
|
||||
'description' => __( 'The ID for the author of the revision.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'date' => array(
|
||||
'description' => __( "The date the revision was published, in the site's timezone." ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'date_gmt' => array(
|
||||
'description' => __( 'The date the revision was published, as GMT.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the revision.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'modified' => array(
|
||||
'description' => __( "The date the revision was last modified, in the site's timezone." ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'modified_gmt' => array(
|
||||
'description' => __( 'The date the revision was last modified, as GMT.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'parent' => array(
|
||||
'description' => __( 'The ID for the parent of the revision.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
|
||||
// Adds settings and styles from the WP_REST_Global_Styles_Controller parent schema.
|
||||
'styles' => array(
|
||||
'description' => __( 'Global styles.' ),
|
||||
'type' => array( 'object' ),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'settings' => array(
|
||||
'description' => __( 'Global settings.' ),
|
||||
'type' => array( 'object' ),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read a single global style.
|
||||
*
|
||||
* @since 6.3.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_item_permissions_check( $request ) {
|
||||
$post = $this->get_parent( $request['parent'] );
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
/*
|
||||
* The same check as WP_REST_Global_Styles_Controller::get_item_permissions_check.
|
||||
*/
|
||||
if ( ! current_user_can( 'read_post', $post->ID ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_view',
|
||||
__( 'Sorry, you are not allowed to view revisions for this global style.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent post, if the ID is valid.
|
||||
*
|
||||
* Duplicate of WP_REST_Revisions_Controller::get_parent.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @param int $parent_post_id Supplied ID.
|
||||
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_parent( $parent_post_id ) {
|
||||
$error = new WP_Error(
|
||||
'rest_post_invalid_parent',
|
||||
__( 'Invalid post parent ID.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
|
||||
if ( (int) $parent_post_id <= 0 ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$parent_post = get_post( (int) $parent_post_id );
|
||||
|
||||
if ( empty( $parent_post ) || empty( $parent_post->ID )
|
||||
|| $this->parent_post_type !== $parent_post->post_type
|
||||
) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
return $parent_post;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,304 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Menu_Locations_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.9.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access menu locations via the REST API.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Menu_Locations_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Menu Locations Constructor.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'menu-locations';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the objects of the controller.
|
||||
*
|
||||
* @since 5.9.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<location>[\w-]+)',
|
||||
array(
|
||||
'args' => array(
|
||||
'location' => array(
|
||||
'description' => __( 'An alphanumeric identifier for the menu location.' ),
|
||||
'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 menu locations.
|
||||
*
|
||||
* @since 5.9.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 ( ! current_user_can( 'edit_theme_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_view',
|
||||
__( 'Sorry, you are not allowed to view menu locations.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all menu locations, depending on user context.
|
||||
*
|
||||
* @since 5.9.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_registered_nav_menus() as $name => $description ) {
|
||||
$location = new stdClass();
|
||||
$location->name = $name;
|
||||
$location->description = $description;
|
||||
|
||||
$location = $this->prepare_item_for_response( $location, $request );
|
||||
$data[ $name ] = $this->prepare_response_for_collection( $location );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read a menu location.
|
||||
*
|
||||
* @since 5.9.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, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
if ( ! current_user_can( 'edit_theme_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_view',
|
||||
__( 'Sorry, you are not allowed to view menu locations.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a specific menu location.
|
||||
*
|
||||
* @since 5.9.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 ) {
|
||||
$registered_menus = get_registered_nav_menus();
|
||||
if ( ! array_key_exists( $request['location'], $registered_menus ) ) {
|
||||
return new WP_Error( 'rest_menu_location_invalid', __( 'Invalid menu location.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$location = new stdClass();
|
||||
$location->name = $request['location'];
|
||||
$location->description = $registered_menus[ $location->name ];
|
||||
|
||||
$data = $this->prepare_item_for_response( $location, $request );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a menu location object for serialization.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param stdClass $item Post status data.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Menu location data.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$location = $item;
|
||||
|
||||
$locations = get_nav_menu_locations();
|
||||
$menu = isset( $locations[ $location->name ] ) ? $locations[ $location->name ] : 0;
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( rest_is_field_included( 'name', $fields ) ) {
|
||||
$data['name'] = $location->name;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'description', $fields ) ) {
|
||||
$data['description'] = $location->description;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'menu', $fields ) ) {
|
||||
$data['menu'] = (int) $menu;
|
||||
}
|
||||
|
||||
$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 ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $location ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters menu location data returned from the REST API.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param object $location The original location object.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_menu_location', $response, $location, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param stdClass $location Menu location.
|
||||
* @return array Links for the given menu location.
|
||||
*/
|
||||
protected function prepare_links( $location ) {
|
||||
$base = sprintf( '%s/%s', $this->namespace, $this->rest_base );
|
||||
|
||||
// Entity meta.
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( trailingslashit( $base ) . $location->name ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( $base ),
|
||||
),
|
||||
);
|
||||
|
||||
$locations = get_nav_menu_locations();
|
||||
$menu = isset( $locations[ $location->name ] ) ? $locations[ $location->name ] : 0;
|
||||
if ( $menu ) {
|
||||
$path = rest_get_route_for_term( $menu );
|
||||
if ( $path ) {
|
||||
$url = rest_url( $path );
|
||||
|
||||
$links['https://api.w.org/menu'][] = array(
|
||||
'href' => $url,
|
||||
'embeddable' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the menu location's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$this->schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'menu-location',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'The name of the menu location.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'The description of the menu location.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'menu' => array(
|
||||
'description' => __( 'The ID of the assigned menu.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for collections.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
return array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,577 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Menus_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.9.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to managed menu terms associated via the REST API.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Menus_Controller extends WP_REST_Terms_Controller {
|
||||
|
||||
/**
|
||||
* Checks if a request has access to read menus.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return bool|WP_Error True if the request has read access, otherwise false or WP_Error object.
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
$has_permission = parent::get_items_permissions_check( $request );
|
||||
|
||||
if ( true !== $has_permission ) {
|
||||
return $has_permission;
|
||||
}
|
||||
|
||||
return $this->check_has_read_only_access( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a request has access to read or edit the specified menu.
|
||||
*
|
||||
* @since 5.9.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_item_permissions_check( $request ) {
|
||||
$has_permission = parent::get_item_permissions_check( $request );
|
||||
|
||||
if ( true !== $has_permission ) {
|
||||
return $has_permission;
|
||||
}
|
||||
|
||||
return $this->check_has_read_only_access( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the term, if the ID is valid.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param int $id Supplied ID.
|
||||
* @return WP_Term|WP_Error Term object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_term( $id ) {
|
||||
$term = parent::get_term( $id );
|
||||
|
||||
if ( is_wp_error( $term ) ) {
|
||||
return $term;
|
||||
}
|
||||
|
||||
$nav_term = wp_get_nav_menu_object( $term );
|
||||
$nav_term->auto_add = $this->get_menu_auto_add( $nav_term->term_id );
|
||||
|
||||
return $nav_term;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current user has read permission for the endpoint.
|
||||
*
|
||||
* This allows for any user that can `edit_theme_options` or edit any REST API available post type.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the current user has permission, WP_Error object otherwise.
|
||||
*/
|
||||
protected function check_has_read_only_access( $request ) {
|
||||
if ( current_user_can( 'edit_theme_options' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( current_user_can( 'edit_posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
|
||||
if ( current_user_can( $post_type->cap->edit_posts ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_view',
|
||||
__( 'Sorry, you are not allowed to view menus.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single term output for response.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_Term $term Term object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $term, $request ) {
|
||||
$nav_menu = wp_get_nav_menu_object( $term );
|
||||
$response = parent::prepare_item_for_response( $nav_menu, $request );
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
if ( rest_is_field_included( 'locations', $fields ) ) {
|
||||
$data['locations'] = $this->get_menu_locations( $nav_menu->term_id );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'auto_add', $fields ) ) {
|
||||
$data['auto_add'] = $this->get_menu_auto_add( $nav_menu->term_id );
|
||||
}
|
||||
|
||||
$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 ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $term ) );
|
||||
}
|
||||
|
||||
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
|
||||
return apply_filters( "rest_prepare_{$this->taxonomy}", $response, $term, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_Term $term Term object.
|
||||
* @return array Links for the given term.
|
||||
*/
|
||||
protected function prepare_links( $term ) {
|
||||
$links = parent::prepare_links( $term );
|
||||
|
||||
$locations = $this->get_menu_locations( $term->term_id );
|
||||
foreach ( $locations as $location ) {
|
||||
$url = rest_url( sprintf( 'wp/v2/menu-locations/%s', $location ) );
|
||||
|
||||
$links['https://api.w.org/menu-location'][] = array(
|
||||
'href' => $url,
|
||||
'embeddable' => true,
|
||||
);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single term for create or update.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return object Prepared term data.
|
||||
*/
|
||||
public function prepare_item_for_database( $request ) {
|
||||
$prepared_term = parent::prepare_item_for_database( $request );
|
||||
|
||||
$schema = $this->get_item_schema();
|
||||
|
||||
if ( isset( $request['name'] ) && ! empty( $schema['properties']['name'] ) ) {
|
||||
$prepared_term->{'menu-name'} = $request['name'];
|
||||
}
|
||||
|
||||
return $prepared_term;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single term in a taxonomy.
|
||||
*
|
||||
* @since 5.9.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 ( isset( $request['parent'] ) ) {
|
||||
if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
|
||||
return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$parent = wp_get_nav_menu_object( (int) $request['parent'] );
|
||||
|
||||
if ( ! $parent ) {
|
||||
return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
}
|
||||
|
||||
$prepared_term = $this->prepare_item_for_database( $request );
|
||||
|
||||
$term = wp_update_nav_menu_object( 0, wp_slash( (array) $prepared_term ) );
|
||||
|
||||
if ( is_wp_error( $term ) ) {
|
||||
/*
|
||||
* If we're going to inform the client that the term already exists,
|
||||
* give them the identifier for future use.
|
||||
*/
|
||||
|
||||
if ( in_array( 'menu_exists', $term->get_error_codes(), true ) ) {
|
||||
$existing_term = get_term_by( 'name', $prepared_term->{'menu-name'}, $this->taxonomy );
|
||||
$term->add_data( $existing_term->term_id, 'menu_exists' );
|
||||
$term->add_data(
|
||||
array(
|
||||
'status' => 400,
|
||||
'term_id' => $existing_term->term_id,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$term->add_data( array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
return $term;
|
||||
}
|
||||
|
||||
$term = $this->get_term( $term );
|
||||
|
||||
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
|
||||
do_action( "rest_insert_{$this->taxonomy}", $term, $request, true );
|
||||
|
||||
$schema = $this->get_item_schema();
|
||||
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
|
||||
$meta_update = $this->meta->update_value( $request['meta'], $term->term_id );
|
||||
|
||||
if ( is_wp_error( $meta_update ) ) {
|
||||
return $meta_update;
|
||||
}
|
||||
}
|
||||
|
||||
$locations_update = $this->handle_locations( $term->term_id, $request );
|
||||
|
||||
if ( is_wp_error( $locations_update ) ) {
|
||||
return $locations_update;
|
||||
}
|
||||
|
||||
$this->handle_auto_add( $term->term_id, $request );
|
||||
|
||||
$fields_update = $this->update_additional_fields_for_object( $term, $request );
|
||||
|
||||
if ( is_wp_error( $fields_update ) ) {
|
||||
return $fields_update;
|
||||
}
|
||||
|
||||
$request->set_param( 'context', 'view' );
|
||||
|
||||
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
|
||||
do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, true );
|
||||
|
||||
$response = $this->prepare_item_for_response( $term, $request );
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
$response->set_status( 201 );
|
||||
$response->header( 'Location', rest_url( $this->namespace . '/' . $this->rest_base . '/' . $term->term_id ) );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single term from a taxonomy.
|
||||
*
|
||||
* @since 5.9.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 update_item( $request ) {
|
||||
$term = $this->get_term( $request['id'] );
|
||||
if ( is_wp_error( $term ) ) {
|
||||
return $term;
|
||||
}
|
||||
|
||||
if ( isset( $request['parent'] ) ) {
|
||||
if ( ! is_taxonomy_hierarchical( $this->taxonomy ) ) {
|
||||
return new WP_Error( 'rest_taxonomy_not_hierarchical', __( 'Cannot set parent term, taxonomy is not hierarchical.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$parent = get_term( (int) $request['parent'], $this->taxonomy );
|
||||
|
||||
if ( ! $parent ) {
|
||||
return new WP_Error( 'rest_term_invalid', __( 'Parent term does not exist.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
}
|
||||
|
||||
$prepared_term = $this->prepare_item_for_database( $request );
|
||||
|
||||
// Only update the term if we have something to update.
|
||||
if ( ! empty( $prepared_term ) ) {
|
||||
if ( ! isset( $prepared_term->{'menu-name'} ) ) {
|
||||
// wp_update_nav_menu_object() requires that the menu-name is always passed.
|
||||
$prepared_term->{'menu-name'} = $term->name;
|
||||
}
|
||||
|
||||
$update = wp_update_nav_menu_object( $term->term_id, wp_slash( (array) $prepared_term ) );
|
||||
|
||||
if ( is_wp_error( $update ) ) {
|
||||
return $update;
|
||||
}
|
||||
}
|
||||
|
||||
$term = get_term( $term->term_id, $this->taxonomy );
|
||||
|
||||
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
|
||||
do_action( "rest_insert_{$this->taxonomy}", $term, $request, false );
|
||||
|
||||
$schema = $this->get_item_schema();
|
||||
if ( ! empty( $schema['properties']['meta'] ) && isset( $request['meta'] ) ) {
|
||||
$meta_update = $this->meta->update_value( $request['meta'], $term->term_id );
|
||||
|
||||
if ( is_wp_error( $meta_update ) ) {
|
||||
return $meta_update;
|
||||
}
|
||||
}
|
||||
|
||||
$locations_update = $this->handle_locations( $term->term_id, $request );
|
||||
|
||||
if ( is_wp_error( $locations_update ) ) {
|
||||
return $locations_update;
|
||||
}
|
||||
|
||||
$this->handle_auto_add( $term->term_id, $request );
|
||||
|
||||
$fields_update = $this->update_additional_fields_for_object( $term, $request );
|
||||
|
||||
if ( is_wp_error( $fields_update ) ) {
|
||||
return $fields_update;
|
||||
}
|
||||
|
||||
$request->set_param( 'context', 'view' );
|
||||
|
||||
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
|
||||
do_action( "rest_after_insert_{$this->taxonomy}", $term, $request, false );
|
||||
|
||||
$response = $this->prepare_item_for_response( $term, $request );
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single term from a taxonomy.
|
||||
*
|
||||
* @since 5.9.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 delete_item( $request ) {
|
||||
$term = $this->get_term( $request['id'] );
|
||||
if ( is_wp_error( $term ) ) {
|
||||
return $term;
|
||||
}
|
||||
|
||||
// We don't support trashing for terms.
|
||||
if ( ! $request['force'] ) {
|
||||
/* translators: %s: force=true */
|
||||
return new WP_Error( 'rest_trash_not_supported', sprintf( __( "Menus do not support trashing. Set '%s' to delete." ), 'force=true' ), array( 'status' => 501 ) );
|
||||
}
|
||||
|
||||
$request->set_param( 'context', 'view' );
|
||||
|
||||
$previous = $this->prepare_item_for_response( $term, $request );
|
||||
|
||||
$result = wp_delete_nav_menu( $term );
|
||||
|
||||
if ( ! $result || is_wp_error( $result ) ) {
|
||||
return new WP_Error( 'rest_cannot_delete', __( 'The menu cannot be deleted.' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
$response = new WP_REST_Response();
|
||||
$response->set_data(
|
||||
array(
|
||||
'deleted' => true,
|
||||
'previous' => $previous->get_data(),
|
||||
)
|
||||
);
|
||||
|
||||
/** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php */
|
||||
do_action( "rest_delete_{$this->taxonomy}", $term, $response, $request );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of a menu's auto_add setting.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param int $menu_id The menu id to query.
|
||||
* @return bool The value of auto_add.
|
||||
*/
|
||||
protected function get_menu_auto_add( $menu_id ) {
|
||||
$nav_menu_option = (array) get_option( 'nav_menu_options', array( 'auto_add' => array() ) );
|
||||
|
||||
return in_array( $menu_id, $nav_menu_option['auto_add'], true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the menu's auto add from a REST request.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param int $menu_id The menu id to update.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return bool True if the auto add setting was successfully updated.
|
||||
*/
|
||||
protected function handle_auto_add( $menu_id, $request ) {
|
||||
if ( ! isset( $request['auto_add'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$nav_menu_option = (array) get_option( 'nav_menu_options', array( 'auto_add' => array() ) );
|
||||
|
||||
if ( ! isset( $nav_menu_option['auto_add'] ) ) {
|
||||
$nav_menu_option['auto_add'] = array();
|
||||
}
|
||||
|
||||
$auto_add = $request['auto_add'];
|
||||
|
||||
$i = array_search( $menu_id, $nav_menu_option['auto_add'], true );
|
||||
|
||||
if ( $auto_add && false === $i ) {
|
||||
$nav_menu_option['auto_add'][] = $menu_id;
|
||||
} elseif ( ! $auto_add && false !== $i ) {
|
||||
array_splice( $nav_menu_option['auto_add'], $i, 1 );
|
||||
}
|
||||
|
||||
$update = update_option( 'nav_menu_options', $nav_menu_option );
|
||||
|
||||
/** This action is documented in wp-includes/nav-menu.php */
|
||||
do_action( 'wp_update_nav_menu', $menu_id );
|
||||
|
||||
return $update;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the names of the locations assigned to the menu.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param int $menu_id The menu id.
|
||||
* @return string[] The locations assigned to the menu.
|
||||
*/
|
||||
protected function get_menu_locations( $menu_id ) {
|
||||
$locations = get_nav_menu_locations();
|
||||
$menu_locations = array();
|
||||
|
||||
foreach ( $locations as $location => $assigned_menu_id ) {
|
||||
if ( $menu_id === $assigned_menu_id ) {
|
||||
$menu_locations[] = $location;
|
||||
}
|
||||
}
|
||||
|
||||
return $menu_locations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the menu's locations from a REST request.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param int $menu_id The menu id to update.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True on success, a WP_Error on an error updating any of the locations.
|
||||
*/
|
||||
protected function handle_locations( $menu_id, $request ) {
|
||||
if ( ! isset( $request['locations'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$menu_locations = get_registered_nav_menus();
|
||||
$menu_locations = array_keys( $menu_locations );
|
||||
$new_locations = array();
|
||||
foreach ( $request['locations'] as $location ) {
|
||||
if ( ! in_array( $location, $menu_locations, true ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_menu_location',
|
||||
__( 'Invalid menu location.' ),
|
||||
array(
|
||||
'status' => 400,
|
||||
'location' => $location,
|
||||
)
|
||||
);
|
||||
}
|
||||
$new_locations[ $location ] = $menu_id;
|
||||
}
|
||||
$assigned_menu = get_nav_menu_locations();
|
||||
foreach ( $assigned_menu as $location => $term_id ) {
|
||||
if ( $term_id === $menu_id ) {
|
||||
unset( $assigned_menu[ $location ] );
|
||||
}
|
||||
}
|
||||
$new_assignments = array_merge( $assigned_menu, $new_locations );
|
||||
set_theme_mod( 'nav_menu_locations', $new_assignments );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the term's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = parent::get_item_schema();
|
||||
unset( $schema['properties']['count'], $schema['properties']['link'], $schema['properties']['taxonomy'] );
|
||||
|
||||
$schema['properties']['locations'] = array(
|
||||
'description' => __( 'The locations assigned to the menu.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'arg_options' => array(
|
||||
'validate_callback' => static function ( $locations, $request, $param ) {
|
||||
$valid = rest_validate_request_arg( $locations, $request, $param );
|
||||
|
||||
if ( true !== $valid ) {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
$locations = rest_sanitize_request_arg( $locations, $request, $param );
|
||||
|
||||
foreach ( $locations as $location ) {
|
||||
if ( ! array_key_exists( $location, get_registered_nav_menus() ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_menu_location',
|
||||
__( 'Invalid menu location.' ),
|
||||
array(
|
||||
'location' => $location,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
$schema['properties']['auto_add'] = array(
|
||||
'description' => __( 'Whether to automatically add top level pages to this menu.' ),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'type' => 'boolean',
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
<?php
|
||||
/**
|
||||
* WP_REST_Navigation_Fallback_Controller class
|
||||
*
|
||||
* REST Controller to create/fetch a fallback Navigation Menu.
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 6.3.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* REST Controller to fetch a fallback Navigation Block Menu. If needed it creates one.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
class WP_REST_Navigation_Fallback_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* The Post Type for the Controller
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $post_type;
|
||||
|
||||
/**
|
||||
* Constructs the controller.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp-block-editor/v1';
|
||||
$this->rest_base = 'navigation-fallback';
|
||||
$this->post_type = 'wp_navigation';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the controllers routes.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
|
||||
// Lists a single nav item based on the given id or slug.
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::READABLE ),
|
||||
),
|
||||
'schema' => array( $this, 'get_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read fallbacks.
|
||||
*
|
||||
* @since 6.3.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_item_permissions_check( $request ) {
|
||||
|
||||
$post_type = get_post_type_object( $this->post_type );
|
||||
|
||||
// Getting fallbacks requires creating and reading `wp_navigation` posts.
|
||||
if ( ! current_user_can( $post_type->cap->create_posts ) || ! current_user_can( 'edit_theme_options' ) || ! current_user_can( 'edit_posts' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_create',
|
||||
__( 'Sorry, you are not allowed to create Navigation Menus as this user.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'edit' === $request['context'] && ! current_user_can( $post_type->cap->edit_posts ) ) {
|
||||
return new WP_Error(
|
||||
'rest_forbidden_context',
|
||||
__( 'Sorry, you are not allowed to edit Navigation Menus as this user.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the most appropriate fallback Navigation Menu.
|
||||
*
|
||||
* @since 6.3.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 ) {
|
||||
$post = WP_Navigation_Fallback::get_fallback();
|
||||
|
||||
if ( empty( $post ) ) {
|
||||
return rest_ensure_response( new WP_Error( 'no_fallback_menu', __( 'No fallback menu found.' ), array( 'status' => 404 ) ) );
|
||||
}
|
||||
|
||||
$response = $this->prepare_item_for_response( $post, $request );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the fallbacks' schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$this->schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'navigation-fallback',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'The unique identifier for the Navigation Menu.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches the post data to the schema we want.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @param WP_Post $item The wp_navigation Post object whose response is being prepared.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response $response The response data.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$data = array();
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
if ( rest_is_field_included( 'id', $fields ) ) {
|
||||
$data['id'] = (int) $item->ID;
|
||||
}
|
||||
|
||||
$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 ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$links = $this->prepare_links( $item );
|
||||
$response->add_links( $links );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the links for the request.
|
||||
*
|
||||
* @since 6.3.0
|
||||
*
|
||||
* @param WP_Post $post the Navigation Menu post object.
|
||||
* @return array Links for the given request.
|
||||
*/
|
||||
private function prepare_links( $post ) {
|
||||
return array(
|
||||
'self' => array(
|
||||
'href' => rest_url( rest_get_route_for_post( $post->ID ) ),
|
||||
'embeddable' => true,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,410 @@
|
||||
<?php
|
||||
/**
|
||||
* Block Pattern Directory REST API: WP_REST_Pattern_Directory_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.8.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller which provides REST endpoint for block patterns.
|
||||
*
|
||||
* This simply proxies the endpoint at http://api.wordpress.org/patterns/1.0/. That isn't necessary for
|
||||
* functionality, but is desired for privacy. It prevents api.wordpress.org from knowing the user's IP address.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Pattern_Directory_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructs the controller.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'pattern-directory';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the necessary REST API routes.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/patterns',
|
||||
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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given request has permission to view the local block pattern directory.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has permission, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_items_permissions_check( $request ) {
|
||||
if ( current_user_can( 'edit_posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
|
||||
if ( current_user_can( $post_type->cap->edit_posts ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_pattern_directory_cannot_view',
|
||||
__( 'Sorry, you are not allowed to browse the local block pattern directory.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search and retrieve block patterns metadata
|
||||
*
|
||||
* @since 5.8.0
|
||||
* @since 6.0.0 Added 'slug' to request.
|
||||
* @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request.
|
||||
*
|
||||
* @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 ) {
|
||||
/*
|
||||
* Include an unmodified `$wp_version`, so the API can craft a response that's tailored to
|
||||
* it. Some plugins modify the version in a misguided attempt to improve security by
|
||||
* obscuring the version, which can cause invalid requests.
|
||||
*/
|
||||
require ABSPATH . WPINC . '/version.php';
|
||||
|
||||
$valid_query_args = array(
|
||||
'offset' => true,
|
||||
'order' => true,
|
||||
'orderby' => true,
|
||||
'page' => true,
|
||||
'per_page' => true,
|
||||
'search' => true,
|
||||
'slug' => true,
|
||||
);
|
||||
$query_args = array_intersect_key( $request->get_params(), $valid_query_args );
|
||||
|
||||
$query_args['locale'] = get_user_locale();
|
||||
$query_args['wp-version'] = $wp_version;
|
||||
$query_args['pattern-categories'] = isset( $request['category'] ) ? $request['category'] : false;
|
||||
$query_args['pattern-keywords'] = isset( $request['keyword'] ) ? $request['keyword'] : false;
|
||||
|
||||
$query_args = array_filter( $query_args );
|
||||
|
||||
$transient_key = $this->get_transient_key( $query_args );
|
||||
|
||||
/*
|
||||
* Use network-wide transient to improve performance. The locale is the only site
|
||||
* configuration that affects the response, and it's included in the transient key.
|
||||
*/
|
||||
$raw_patterns = get_site_transient( $transient_key );
|
||||
|
||||
if ( ! $raw_patterns ) {
|
||||
$api_url = 'http://api.wordpress.org/patterns/1.0/?' . build_query( $query_args );
|
||||
if ( wp_http_supports( array( 'ssl' ) ) ) {
|
||||
$api_url = set_url_scheme( $api_url, 'https' );
|
||||
}
|
||||
|
||||
/*
|
||||
* Default to a short TTL, to mitigate cache stampedes on high-traffic sites.
|
||||
* This assumes that most errors will be short-lived, e.g., packet loss that causes the
|
||||
* first request to fail, but a follow-up one will succeed. The value should be high
|
||||
* enough to avoid stampedes, but low enough to not interfere with users manually
|
||||
* re-trying a failed request.
|
||||
*/
|
||||
$cache_ttl = 5;
|
||||
$wporg_response = wp_remote_get( $api_url );
|
||||
$raw_patterns = json_decode( wp_remote_retrieve_body( $wporg_response ) );
|
||||
|
||||
if ( is_wp_error( $wporg_response ) ) {
|
||||
$raw_patterns = $wporg_response;
|
||||
|
||||
} elseif ( ! is_array( $raw_patterns ) ) {
|
||||
// HTTP request succeeded, but response data is invalid.
|
||||
$raw_patterns = new WP_Error(
|
||||
'pattern_api_failed',
|
||||
sprintf(
|
||||
/* translators: %s: Support forums URL. */
|
||||
__( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ),
|
||||
__( 'https://wordpress.org/support/forums/' )
|
||||
),
|
||||
array(
|
||||
'response' => wp_remote_retrieve_body( $wporg_response ),
|
||||
)
|
||||
);
|
||||
|
||||
} else {
|
||||
// Response has valid data.
|
||||
$cache_ttl = HOUR_IN_SECONDS;
|
||||
}
|
||||
|
||||
set_site_transient( $transient_key, $raw_patterns, $cache_ttl );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $raw_patterns ) ) {
|
||||
$raw_patterns->add_data( array( 'status' => 500 ) );
|
||||
|
||||
return $raw_patterns;
|
||||
}
|
||||
|
||||
$response = array();
|
||||
|
||||
if ( $raw_patterns ) {
|
||||
foreach ( $raw_patterns as $pattern ) {
|
||||
$response[] = $this->prepare_response_for_collection(
|
||||
$this->prepare_item_for_response( $pattern, $request )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a raw block pattern before it gets output in a REST API response.
|
||||
*
|
||||
* @since 5.8.0
|
||||
* @since 5.9.0 Renamed `$raw_pattern` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @param object $item Raw pattern from api.wordpress.org, before any changes.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$raw_pattern = $item;
|
||||
|
||||
$prepared_pattern = array(
|
||||
'id' => absint( $raw_pattern->id ),
|
||||
'title' => sanitize_text_field( $raw_pattern->title->rendered ),
|
||||
'content' => wp_kses_post( $raw_pattern->pattern_content ),
|
||||
'categories' => array_map( 'sanitize_title', $raw_pattern->category_slugs ),
|
||||
'keywords' => array_map( 'sanitize_text_field', explode( ',', $raw_pattern->meta->wpop_keywords ) ),
|
||||
'description' => sanitize_text_field( $raw_pattern->meta->wpop_description ),
|
||||
'viewport_width' => absint( $raw_pattern->meta->wpop_viewport_width ),
|
||||
'block_types' => array_map( 'sanitize_text_field', $raw_pattern->meta->wpop_block_types ),
|
||||
);
|
||||
|
||||
$prepared_pattern = $this->add_additional_fields_to_object( $prepared_pattern, $request );
|
||||
|
||||
$response = new WP_REST_Response( $prepared_pattern );
|
||||
|
||||
/**
|
||||
* Filters the REST API response for a block pattern.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param object $raw_pattern The unprepared block pattern.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_block_pattern', $response, $raw_pattern, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the block pattern's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.8.0
|
||||
* @since 6.2.0 Added `'block_types'` to schema.
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$this->schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'pattern-directory-item',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'The pattern ID.' ),
|
||||
'type' => 'integer',
|
||||
'minimum' => 1,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
|
||||
'title' => array(
|
||||
'description' => __( 'The pattern title, in human readable format.' ),
|
||||
'type' => 'string',
|
||||
'minLength' => 1,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
|
||||
'content' => array(
|
||||
'description' => __( 'The pattern content.' ),
|
||||
'type' => 'string',
|
||||
'minLength' => 1,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
|
||||
'categories' => array(
|
||||
'description' => __( "The pattern's category slugs." ),
|
||||
'type' => 'array',
|
||||
'uniqueItems' => true,
|
||||
'items' => array( 'type' => 'string' ),
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
|
||||
'keywords' => array(
|
||||
'description' => __( "The pattern's keywords." ),
|
||||
'type' => 'array',
|
||||
'uniqueItems' => true,
|
||||
'items' => array( 'type' => 'string' ),
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
|
||||
'description' => array(
|
||||
'description' => __( 'A description of the pattern.' ),
|
||||
'type' => 'string',
|
||||
'minLength' => 1,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
|
||||
'viewport_width' => array(
|
||||
'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
|
||||
'block_types' => array(
|
||||
'description' => __( 'The block types which can use this pattern.' ),
|
||||
'type' => 'array',
|
||||
'uniqueItems' => true,
|
||||
'items' => array( 'type' => 'string' ),
|
||||
'context' => array( 'view', 'embed' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the search parameters for the block pattern's collection.
|
||||
*
|
||||
* @since 5.8.0
|
||||
* @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request.
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$query_params = parent::get_collection_params();
|
||||
|
||||
$query_params['per_page']['default'] = 100;
|
||||
$query_params['search']['minLength'] = 1;
|
||||
$query_params['context']['default'] = 'view';
|
||||
|
||||
$query_params['category'] = array(
|
||||
'description' => __( 'Limit results to those matching a category ID.' ),
|
||||
'type' => 'integer',
|
||||
'minimum' => 1,
|
||||
);
|
||||
|
||||
$query_params['keyword'] = array(
|
||||
'description' => __( 'Limit results to those matching a keyword ID.' ),
|
||||
'type' => 'integer',
|
||||
'minimum' => 1,
|
||||
);
|
||||
|
||||
$query_params['slug'] = array(
|
||||
'description' => __( 'Limit results to those matching a pattern (slug).' ),
|
||||
'type' => '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 post attribute.' ),
|
||||
'type' => 'string',
|
||||
'default' => 'date',
|
||||
'enum' => array(
|
||||
'author',
|
||||
'date',
|
||||
'id',
|
||||
'include',
|
||||
'modified',
|
||||
'parent',
|
||||
'relevance',
|
||||
'slug',
|
||||
'include_slugs',
|
||||
'title',
|
||||
'favorite_count',
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter collection parameters for the block pattern directory controller.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param array $query_params JSON Schema-formatted collection parameters.
|
||||
*/
|
||||
return apply_filters( 'rest_pattern_directory_collection_params', $query_params );
|
||||
}
|
||||
|
||||
/*
|
||||
* Include a hash of the query args, so that different requests are stored in
|
||||
* separate caches.
|
||||
*
|
||||
* MD5 is chosen for its speed, low-collision rate, universal availability, and to stay
|
||||
* under the character limit for `_site_transient_timeout_{...}` keys.
|
||||
*
|
||||
* @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses
|
||||
*
|
||||
* @since 6.0.0
|
||||
*
|
||||
* @param array $query_args Query arguments to generate a transient key from.
|
||||
* @return string Transient key.
|
||||
*/
|
||||
protected function get_transient_key( $query_args ) {
|
||||
|
||||
if ( isset( $query_args['slug'] ) ) {
|
||||
// This is an additional precaution because the "sort" function expects an array.
|
||||
$query_args['slug'] = wp_parse_list( $query_args['slug'] );
|
||||
|
||||
// Empty arrays should not affect the transient key.
|
||||
if ( empty( $query_args['slug'] ) ) {
|
||||
unset( $query_args['slug'] );
|
||||
} else {
|
||||
// Sort the array so that the transient key doesn't depend on the order of slugs.
|
||||
sort( $query_args['slug'] );
|
||||
}
|
||||
}
|
||||
|
||||
return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) );
|
||||
}
|
||||
}
|
1004
wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php
Normal file
1004
wp-includes/rest-api/endpoints/class-wp-rest-plugins-controller.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,373 @@
|
||||
<?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 post statuses.
|
||||
*
|
||||
* @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 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'] ) {
|
||||
$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_REST_Response|WP_Error 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 true|WP_Error 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_REST_Response|WP_Error 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
|
||||
* @since 5.9.0 Renamed `$status` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @param stdClass $item 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( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$status = $item;
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
if ( in_array( 'date_floating', $fields, true ) ) {
|
||||
$data['date_floating'] = $status->date_floating;
|
||||
}
|
||||
|
||||
$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 );
|
||||
|
||||
$rest_url = rest_url( rest_get_route_for_post_type_items( 'post' ) );
|
||||
if ( 'publish' === $status->name ) {
|
||||
$response->add_link( 'archives', $rest_url );
|
||||
} else {
|
||||
$response->add_link( 'archives', add_query_arg( 'status', $status->name, $rest_url ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a post 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 post 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() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->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,
|
||||
),
|
||||
'date_floating' => array(
|
||||
'description' => __( 'Whether posts of this status may have floating published dates.' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->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,430 @@
|
||||
<?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 post types.
|
||||
*
|
||||
* @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' ),
|
||||
'permission_callback' => '__return_true',
|
||||
'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 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'] ) {
|
||||
$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 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_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$data = array();
|
||||
$types = get_post_types( array( 'show_in_rest' => true ), 'objects' );
|
||||
|
||||
foreach ( $types as $type ) {
|
||||
if ( 'edit' === $request['context'] && ! current_user_can( $type->cap->edit_posts ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$post_type = $this->prepare_item_for_response( $type, $request );
|
||||
$data[ $type->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_REST_Response|WP_Error 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
|
||||
* @since 5.9.0 Renamed `$post_type` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @param WP_Post_Type $item 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( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$post_type = $item;
|
||||
|
||||
$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;
|
||||
$namespace = ! empty( $post_type->rest_namespace ) ? $post_type->rest_namespace : 'wp/v2';
|
||||
$supports = get_all_post_type_supports( $post_type->name );
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( rest_is_field_included( 'capabilities', $fields ) ) {
|
||||
$data['capabilities'] = $post_type->cap;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'description', $fields ) ) {
|
||||
$data['description'] = $post_type->description;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'hierarchical', $fields ) ) {
|
||||
$data['hierarchical'] = $post_type->hierarchical;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'has_archive', $fields ) ) {
|
||||
$data['has_archive'] = $post_type->has_archive;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'visibility', $fields ) ) {
|
||||
$data['visibility'] = array(
|
||||
'show_in_nav_menus' => (bool) $post_type->show_in_nav_menus,
|
||||
'show_ui' => (bool) $post_type->show_ui,
|
||||
);
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'viewable', $fields ) ) {
|
||||
$data['viewable'] = is_post_type_viewable( $post_type );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'labels', $fields ) ) {
|
||||
$data['labels'] = $post_type->labels;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'name', $fields ) ) {
|
||||
$data['name'] = $post_type->label;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'slug', $fields ) ) {
|
||||
$data['slug'] = $post_type->name;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'icon', $fields ) ) {
|
||||
$data['icon'] = $post_type->menu_icon;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'supports', $fields ) ) {
|
||||
$data['supports'] = $supports;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'taxonomies', $fields ) ) {
|
||||
$data['taxonomies'] = array_values( $taxonomies );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'rest_base', $fields ) ) {
|
||||
$data['rest_base'] = $base;
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'rest_namespace', $fields ) ) {
|
||||
$data['rest_namespace'] = $namespace;
|
||||
}
|
||||
|
||||
$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 );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $post_type ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a post type returned from the REST 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 WP_Post_Type $post_type 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 6.1.0
|
||||
*
|
||||
* @param WP_Post_Type $post_type The post type.
|
||||
* @return array Links for the given post type.
|
||||
*/
|
||||
protected function prepare_links( $post_type ) {
|
||||
return array(
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
'https://api.w.org/items' => array(
|
||||
'href' => rest_url( rest_get_route_for_post_type_items( $post_type->name ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post type's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @since 4.8.0 The `supports` property was added.
|
||||
* @since 5.9.0 The `visibility` and `rest_namespace` properties were added.
|
||||
* @since 6.1.0 The `icon` property was added.
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->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,
|
||||
),
|
||||
'has_archive' => array(
|
||||
'description' => __( 'If the value is a string, the value will be used as the archive slug. If the value is false the post type has no archive.' ),
|
||||
'type' => array( 'string', 'boolean' ),
|
||||
'context' => array( 'view', '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,
|
||||
),
|
||||
'rest_namespace' => array(
|
||||
'description' => __( 'REST route\'s namespace for the post type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'visibility' => array(
|
||||
'description' => __( 'The visibility settings for the post type.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'show_ui' => array(
|
||||
'description' => __( 'Whether to generate a default UI for managing this post type.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'show_in_nav_menus' => array(
|
||||
'description' => __( 'Whether to make the post type available for selection in navigation menus.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
),
|
||||
'icon' => array(
|
||||
'description' => __( 'The icon for the post type.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->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' ) ),
|
||||
);
|
||||
}
|
||||
}
|
3176
wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
Normal file
3176
wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,869 @@
|
||||
<?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;
|
||||
|
||||
/**
|
||||
* Instance of a revision meta fields object.
|
||||
*
|
||||
* @since 6.4.0
|
||||
* @var WP_REST_Post_Meta_Fields
|
||||
*/
|
||||
protected $meta;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
$post_type_object = get_post_type_object( $parent_post_type );
|
||||
$parent_controller = $post_type_object->get_rest_controller();
|
||||
|
||||
if ( ! $parent_controller ) {
|
||||
$parent_controller = new WP_REST_Posts_Controller( $parent_post_type );
|
||||
}
|
||||
|
||||
$this->parent_controller = $parent_controller;
|
||||
$this->rest_base = 'revisions';
|
||||
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
|
||||
$this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
|
||||
$this->meta = new WP_REST_Post_Meta_Fields( $parent_post_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the 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 revision.' ),
|
||||
'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 revision.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the revision.' ),
|
||||
'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 $parent_post_id Supplied ID.
|
||||
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_parent( $parent_post_id ) {
|
||||
$error = new WP_Error(
|
||||
'rest_post_invalid_parent',
|
||||
__( 'Invalid post parent ID.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
|
||||
if ( (int) $parent_post_id <= 0 ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$parent_post = get_post( (int) $parent_post_id );
|
||||
|
||||
if ( empty( $parent_post ) || empty( $parent_post->ID )
|
||||
|| $this->parent_post_type !== $parent_post->post_type
|
||||
) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
return $parent_post;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get revisions.
|
||||
*
|
||||
* @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 ) {
|
||||
$parent = $this->get_parent( $request['parent'] );
|
||||
if ( is_wp_error( $parent ) ) {
|
||||
return $parent;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( '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 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 ) {
|
||||
$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 = (int) 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_path = rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) );
|
||||
$base = add_query_arg( urlencode_deep( $request_params ), $base_path );
|
||||
|
||||
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 details about the request.
|
||||
* @return true|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
|
||||
* @since 6.5.0 Added a condition to check that parent id matches revision parent id.
|
||||
*
|
||||
* @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 ) {
|
||||
$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;
|
||||
}
|
||||
|
||||
if ( (int) $parent->ID !== (int) $revision->post_parent ) {
|
||||
return new WP_Error(
|
||||
'rest_revision_parent_id_mismatch',
|
||||
/* translators: %d: A post id. */
|
||||
sprintf( __( 'The revision does not belong to the specified parent with id of "%d"' ), $parent->ID ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$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 true|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;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'delete_post', $parent->ID ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_delete',
|
||||
__( 'Sorry, you are not allowed to delete revisions of this post.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'delete_post', $revision->ID ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_delete',
|
||||
__( 'Sorry, you are not allowed to delete this revision.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single revision.
|
||||
*
|
||||
* @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 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 ) {
|
||||
return new WP_Error(
|
||||
'rest_trash_not_supported',
|
||||
/* translators: %s: force=true */
|
||||
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 WP_Post|false|null $result The revision object (if it was deleted or moved to the Trash successfully)
|
||||
* or false or null (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
|
||||
* @since 5.9.0 Renamed `$post` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @global WP_Post $post Global post object.
|
||||
*
|
||||
* @param WP_Post $item Post revision object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$post = $item;
|
||||
|
||||
$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 ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'meta', $fields ) ) {
|
||||
$data['meta'] = $this->meta->get_value( $post->ID, $request );
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
if ( ! empty( $data['parent'] ) ) {
|
||||
$response->add_link( 'parent', rest_url( rest_get_route_for_post( $data['parent'] ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a revision returned from the REST 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() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->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 revision.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'date' => array(
|
||||
'description' => __( "The date the revision was published, in the site's timezone." ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'date_gmt' => array(
|
||||
'description' => __( 'The date the revision was published, as GMT.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'guid' => array(
|
||||
'description' => __( 'GUID for the revision, as it exists in the database.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the revision.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'modified' => array(
|
||||
'description' => __( "The date the revision was last modified, in the site's timezone." ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'modified_gmt' => array(
|
||||
'description' => __( 'The date the revision was last modified, as GMT.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'parent' => array(
|
||||
'description' => __( 'The ID for the parent of the revision.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'slug' => array(
|
||||
'description' => __( 'An alphanumeric identifier for the revision 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'];
|
||||
}
|
||||
|
||||
$schema['properties']['meta'] = $this->meta->get_field_schema();
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->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,408 @@
|
||||
<?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 WP_REST_Search_Handler[]
|
||||
*/
|
||||
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 ) {
|
||||
_doing_it_wrong(
|
||||
__METHOD__,
|
||||
/* translators: %s: PHP class name. */
|
||||
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 search 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 = $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 = (int) 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( urlencode_deep( $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
|
||||
* @since 5.6.0 The `$id` parameter can accept a string.
|
||||
* @since 5.9.0 Renamed `$id` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @param int|string $item 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( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$item_id = $item;
|
||||
|
||||
$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( $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 );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$links = $handler->prepare_item_links( $item_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() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->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' => array( 'integer', 'string' ),
|
||||
'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,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->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' ),
|
||||
);
|
||||
|
||||
$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(),
|
||||
);
|
||||
|
||||
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 string[]|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,342 @@
|
||||
<?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 site's settings.
|
||||
*
|
||||
* @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 specifically 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 via the REST API.
|
||||
*
|
||||
* 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',
|
||||
/* translators: %s: Property name. */
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Allow 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'] = rest_default_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() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->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' ),
|
||||
);
|
||||
}
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->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 allow 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
|
||||
* if no additionalProperties setting is specified.
|
||||
*
|
||||
* This is needed 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
|
||||
* @deprecated 6.1.0 Use {@see rest_default_additional_properties_to_false()} instead.
|
||||
*
|
||||
* @param array $schema The schema array.
|
||||
* @return array
|
||||
*/
|
||||
protected function set_additional_properties_to_false( $schema ) {
|
||||
_deprecated_function( __METHOD__, '6.1.0', 'rest_default_additional_properties_to_false()' );
|
||||
|
||||
return rest_default_additional_properties_to_false( $schema );
|
||||
}
|
||||
}
|
@ -0,0 +1,509 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Sidebars_Controller class
|
||||
*
|
||||
* Original code from {@link https://github.com/martin-pettersson/wp-rest-api-sidebars Martin Pettersson (martin_pettersson@outlook.com)}.
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.8.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to manage a site's sidebars.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Sidebars_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Tracks whether {@see retrieve_widgets()} has been called in the current request.
|
||||
*
|
||||
* @since 5.9.0
|
||||
* @var bool
|
||||
*/
|
||||
protected $widgets_retrieved = false;
|
||||
|
||||
/**
|
||||
* Sidebars controller constructor.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'sidebars';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the controllers routes.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
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' => array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<id>[\w-]+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'The id of a registered sidebar' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'update_item' ),
|
||||
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get sidebars.
|
||||
*
|
||||
* @since 5.8.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 ) {
|
||||
$this->retrieve_widgets();
|
||||
foreach ( wp_get_sidebars_widgets() as $id => $widgets ) {
|
||||
$sidebar = $this->get_sidebar( $id );
|
||||
|
||||
if ( ! $sidebar ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $this->check_read_permission( $sidebar ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->do_permissions_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of sidebars (active or inactive).
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Response object on success.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$this->retrieve_widgets();
|
||||
|
||||
$data = array();
|
||||
$permissions_check = $this->do_permissions_check();
|
||||
|
||||
foreach ( wp_get_sidebars_widgets() as $id => $widgets ) {
|
||||
$sidebar = $this->get_sidebar( $id );
|
||||
|
||||
if ( ! $sidebar ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( is_wp_error( $permissions_check ) && ! $this->check_read_permission( $sidebar ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data[] = $this->prepare_response_for_collection(
|
||||
$this->prepare_item_for_response( $sidebar, $request )
|
||||
);
|
||||
}
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get a single sidebar.
|
||||
*
|
||||
* @since 5.8.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_item_permissions_check( $request ) {
|
||||
$this->retrieve_widgets();
|
||||
|
||||
$sidebar = $this->get_sidebar( $request['id'] );
|
||||
if ( $sidebar && $this->check_read_permission( $sidebar ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->do_permissions_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a sidebar can be read publicly.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param array $sidebar The registered sidebar configuration.
|
||||
* @return bool Whether the side can be read.
|
||||
*/
|
||||
protected function check_read_permission( $sidebar ) {
|
||||
return ! empty( $sidebar['show_in_rest'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves one sidebar from the collection.
|
||||
*
|
||||
* @since 5.8.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 ) {
|
||||
$this->retrieve_widgets();
|
||||
|
||||
$sidebar = $this->get_sidebar( $request['id'] );
|
||||
if ( ! $sidebar ) {
|
||||
return new WP_Error( 'rest_sidebar_not_found', __( 'No sidebar exists with that id.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response( $sidebar, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to update sidebars.
|
||||
*
|
||||
* @since 5.8.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 update_item_permissions_check( $request ) {
|
||||
return $this->do_permissions_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a sidebar.
|
||||
*
|
||||
* @since 5.8.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 update_item( $request ) {
|
||||
if ( isset( $request['widgets'] ) ) {
|
||||
$sidebars = wp_get_sidebars_widgets();
|
||||
|
||||
foreach ( $sidebars as $sidebar_id => $widgets ) {
|
||||
foreach ( $widgets as $i => $widget_id ) {
|
||||
// This automatically removes the passed widget IDs from any other sidebars in use.
|
||||
if ( $sidebar_id !== $request['id'] && in_array( $widget_id, $request['widgets'], true ) ) {
|
||||
unset( $sidebars[ $sidebar_id ][ $i ] );
|
||||
}
|
||||
|
||||
// This automatically removes omitted widget IDs to the inactive sidebar.
|
||||
if ( $sidebar_id === $request['id'] && ! in_array( $widget_id, $request['widgets'], true ) ) {
|
||||
$sidebars['wp_inactive_widgets'][] = $widget_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sidebars[ $request['id'] ] = $request['widgets'];
|
||||
|
||||
wp_set_sidebars_widgets( $sidebars );
|
||||
}
|
||||
|
||||
$request['context'] = 'edit';
|
||||
|
||||
$sidebar = $this->get_sidebar( $request['id'] );
|
||||
|
||||
/**
|
||||
* Fires after a sidebar is updated via the REST API.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param array $sidebar The updated sidebar.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
*/
|
||||
do_action( 'rest_save_sidebar', $sidebar, $request );
|
||||
|
||||
return $this->prepare_item_for_response( $sidebar, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has permissions to make the request.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @return true|WP_Error True if the request has read access, WP_Error object otherwise.
|
||||
*/
|
||||
protected function do_permissions_check() {
|
||||
/*
|
||||
* Verify if the current user has edit_theme_options capability.
|
||||
* This capability is required to access the widgets screen.
|
||||
*/
|
||||
if ( ! current_user_can( 'edit_theme_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_manage_widgets',
|
||||
__( 'Sorry, you are not allowed to manage widgets on this site.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the registered sidebar with the given id.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param string|int $id ID of the sidebar.
|
||||
* @return array|null The discovered sidebar, or null if it is not registered.
|
||||
*/
|
||||
protected function get_sidebar( $id ) {
|
||||
return wp_get_sidebar( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for "lost" widgets once per request.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @see retrieve_widgets()
|
||||
*/
|
||||
protected function retrieve_widgets() {
|
||||
if ( ! $this->widgets_retrieved ) {
|
||||
retrieve_widgets();
|
||||
$this->widgets_retrieved = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single sidebar output for response.
|
||||
*
|
||||
* @since 5.8.0
|
||||
* @since 5.9.0 Renamed `$raw_sidebar` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @global array $wp_registered_sidebars The registered sidebars.
|
||||
* @global array $wp_registered_widgets The registered widgets.
|
||||
*
|
||||
* @param array $item Sidebar instance.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Prepared response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
global $wp_registered_sidebars, $wp_registered_widgets;
|
||||
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$raw_sidebar = $item;
|
||||
|
||||
$id = $raw_sidebar['id'];
|
||||
$sidebar = array( 'id' => $id );
|
||||
|
||||
if ( isset( $wp_registered_sidebars[ $id ] ) ) {
|
||||
$registered_sidebar = $wp_registered_sidebars[ $id ];
|
||||
|
||||
$sidebar['status'] = 'active';
|
||||
$sidebar['name'] = isset( $registered_sidebar['name'] ) ? $registered_sidebar['name'] : '';
|
||||
$sidebar['description'] = isset( $registered_sidebar['description'] ) ? wp_sidebar_description( $id ) : '';
|
||||
$sidebar['class'] = isset( $registered_sidebar['class'] ) ? $registered_sidebar['class'] : '';
|
||||
$sidebar['before_widget'] = isset( $registered_sidebar['before_widget'] ) ? $registered_sidebar['before_widget'] : '';
|
||||
$sidebar['after_widget'] = isset( $registered_sidebar['after_widget'] ) ? $registered_sidebar['after_widget'] : '';
|
||||
$sidebar['before_title'] = isset( $registered_sidebar['before_title'] ) ? $registered_sidebar['before_title'] : '';
|
||||
$sidebar['after_title'] = isset( $registered_sidebar['after_title'] ) ? $registered_sidebar['after_title'] : '';
|
||||
} else {
|
||||
$sidebar['status'] = 'inactive';
|
||||
$sidebar['name'] = $raw_sidebar['name'];
|
||||
$sidebar['description'] = '';
|
||||
$sidebar['class'] = '';
|
||||
}
|
||||
|
||||
if ( wp_is_block_theme() ) {
|
||||
$sidebar['status'] = 'inactive';
|
||||
}
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
if ( rest_is_field_included( 'widgets', $fields ) ) {
|
||||
$sidebars = wp_get_sidebars_widgets();
|
||||
$widgets = array_filter(
|
||||
isset( $sidebars[ $sidebar['id'] ] ) ? $sidebars[ $sidebar['id'] ] : array(),
|
||||
static function ( $widget_id ) use ( $wp_registered_widgets ) {
|
||||
return isset( $wp_registered_widgets[ $widget_id ] );
|
||||
}
|
||||
);
|
||||
|
||||
$sidebar['widgets'] = array_values( $widgets );
|
||||
}
|
||||
|
||||
$schema = $this->get_item_schema();
|
||||
$data = array();
|
||||
foreach ( $schema['properties'] as $property_id => $property ) {
|
||||
if ( isset( $sidebar[ $property_id ] ) && true === rest_validate_value_from_schema( $sidebar[ $property_id ], $property ) ) {
|
||||
$data[ $property_id ] = $sidebar[ $property_id ];
|
||||
} elseif ( isset( $property['default'] ) ) {
|
||||
$data[ $property_id ] = $property['default'];
|
||||
}
|
||||
}
|
||||
|
||||
$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 ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $sidebar ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the REST API response for a sidebar.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param array $raw_sidebar The raw sidebar data.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_sidebar', $response, $raw_sidebar, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the sidebar.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param array $sidebar Sidebar.
|
||||
* @return array Links for the given widget.
|
||||
*/
|
||||
protected function prepare_links( $sidebar ) {
|
||||
return array(
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $sidebar['id'] ) ),
|
||||
),
|
||||
'https://api.w.org/widget' => array(
|
||||
'href' => add_query_arg( 'sidebar', $sidebar['id'], rest_url( '/wp/v2/widgets' ) ),
|
||||
'embeddable' => true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the block type' schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'sidebar',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'ID of sidebar.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Unique name identifying the sidebar.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'Description of sidebar.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'class' => array(
|
||||
'description' => __( 'Extra CSS class to assign to the sidebar in the Widgets interface.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'before_widget' => array(
|
||||
'description' => __( 'HTML content to prepend to each widget\'s HTML output when assigned to this sidebar. Default is an opening list item element.' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'after_widget' => array(
|
||||
'description' => __( 'HTML content to append to each widget\'s HTML output when assigned to this sidebar. Default is a closing list item element.' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'before_title' => array(
|
||||
'description' => __( 'HTML content to prepend to the sidebar title when displayed. Default is an opening h2 element.' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'after_title' => array(
|
||||
'description' => __( 'HTML content to append to the sidebar title when displayed. Default is a closing h2 element.' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'status' => array(
|
||||
'description' => __( 'Status of sidebar.' ),
|
||||
'type' => 'string',
|
||||
'enum' => array( 'active', 'inactive' ),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'widgets' => array(
|
||||
'description' => __( 'Nested widgets.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => array( 'object', 'string' ),
|
||||
),
|
||||
'default' => array(),
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
@ -0,0 +1,407 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Site_Health_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.6.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class for interacting with Site Health tests.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Site_Health_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* An instance of the site health class.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @var WP_Site_Health
|
||||
*/
|
||||
private $site_health;
|
||||
|
||||
/**
|
||||
* Site Health controller constructor.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_Site_Health $site_health An instance of the site health class.
|
||||
*/
|
||||
public function __construct( $site_health ) {
|
||||
$this->namespace = 'wp-site-health/v1';
|
||||
$this->rest_base = 'tests';
|
||||
|
||||
$this->site_health = $site_health;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers API routes.
|
||||
*
|
||||
* @since 5.6.0
|
||||
* @since 6.1.0 Adds page-cache async test.
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
sprintf(
|
||||
'/%s/%s',
|
||||
$this->rest_base,
|
||||
'background-updates'
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'test_background_updates' ),
|
||||
'permission_callback' => function () {
|
||||
return $this->validate_request_permission( 'background_updates' );
|
||||
},
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
sprintf(
|
||||
'/%s/%s',
|
||||
$this->rest_base,
|
||||
'loopback-requests'
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'test_loopback_requests' ),
|
||||
'permission_callback' => function () {
|
||||
return $this->validate_request_permission( 'loopback_requests' );
|
||||
},
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
sprintf(
|
||||
'/%s/%s',
|
||||
$this->rest_base,
|
||||
'https-status'
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'test_https_status' ),
|
||||
'permission_callback' => function () {
|
||||
return $this->validate_request_permission( 'https_status' );
|
||||
},
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
sprintf(
|
||||
'/%s/%s',
|
||||
$this->rest_base,
|
||||
'dotorg-communication'
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'test_dotorg_communication' ),
|
||||
'permission_callback' => function () {
|
||||
return $this->validate_request_permission( 'dotorg_communication' );
|
||||
},
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
sprintf(
|
||||
'/%s/%s',
|
||||
$this->rest_base,
|
||||
'authorization-header'
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'test_authorization_header' ),
|
||||
'permission_callback' => function () {
|
||||
return $this->validate_request_permission( 'authorization_header' );
|
||||
},
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
sprintf(
|
||||
'/%s',
|
||||
'directory-sizes'
|
||||
),
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_directory_sizes' ),
|
||||
'permission_callback' => function () {
|
||||
return $this->validate_request_permission( 'directory_sizes' ) && ! is_multisite();
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
sprintf(
|
||||
'/%s/%s',
|
||||
$this->rest_base,
|
||||
'page-cache'
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'test_page_cache' ),
|
||||
'permission_callback' => function () {
|
||||
return $this->validate_request_permission( 'page_cache' );
|
||||
},
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if the current user can request this REST endpoint.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param string $check The endpoint check being ran.
|
||||
* @return bool
|
||||
*/
|
||||
protected function validate_request_permission( $check ) {
|
||||
$default_capability = 'view_site_health_checks';
|
||||
|
||||
/**
|
||||
* Filters the capability needed to run a given Site Health check.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param string $default_capability The default capability required for this check.
|
||||
* @param string $check The Site Health check being performed.
|
||||
*/
|
||||
$capability = apply_filters( "site_health_test_rest_capability_{$check}", $default_capability, $check );
|
||||
|
||||
return current_user_can( $capability );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if background updates work as expected.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_background_updates() {
|
||||
$this->load_admin_textdomain();
|
||||
return $this->site_health->get_test_background_updates();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the site can reach the WordPress.org API.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_dotorg_communication() {
|
||||
$this->load_admin_textdomain();
|
||||
return $this->site_health->get_test_dotorg_communication();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that loopbacks can be performed.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_loopback_requests() {
|
||||
$this->load_admin_textdomain();
|
||||
return $this->site_health->get_test_loopback_requests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the site's frontend can be accessed over HTTPS.
|
||||
*
|
||||
* @since 5.7.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_https_status() {
|
||||
$this->load_admin_textdomain();
|
||||
return $this->site_health->get_test_https_status();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the authorization header is valid.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function test_authorization_header() {
|
||||
$this->load_admin_textdomain();
|
||||
return $this->site_health->get_test_authorization_header();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that full page cache is active.
|
||||
*
|
||||
* @since 6.1.0
|
||||
*
|
||||
* @return array The test result.
|
||||
*/
|
||||
public function test_page_cache() {
|
||||
$this->load_admin_textdomain();
|
||||
return $this->site_health->get_test_page_cache();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current directory sizes for this install.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function get_directory_sizes() {
|
||||
if ( ! class_exists( 'WP_Debug_Data' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-debug-data.php';
|
||||
}
|
||||
|
||||
$this->load_admin_textdomain();
|
||||
|
||||
$sizes_data = WP_Debug_Data::get_sizes();
|
||||
$all_sizes = array( 'raw' => 0 );
|
||||
|
||||
foreach ( $sizes_data as $name => $value ) {
|
||||
$name = sanitize_text_field( $name );
|
||||
$data = array();
|
||||
|
||||
if ( isset( $value['size'] ) ) {
|
||||
if ( is_string( $value['size'] ) ) {
|
||||
$data['size'] = sanitize_text_field( $value['size'] );
|
||||
} else {
|
||||
$data['size'] = (int) $value['size'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( isset( $value['debug'] ) ) {
|
||||
if ( is_string( $value['debug'] ) ) {
|
||||
$data['debug'] = sanitize_text_field( $value['debug'] );
|
||||
} else {
|
||||
$data['debug'] = (int) $value['debug'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $value['raw'] ) ) {
|
||||
$data['raw'] = (int) $value['raw'];
|
||||
}
|
||||
|
||||
$all_sizes[ $name ] = $data;
|
||||
}
|
||||
|
||||
if ( isset( $all_sizes['total_size']['debug'] ) && 'not available' === $all_sizes['total_size']['debug'] ) {
|
||||
return new WP_Error( 'not_available', __( 'Directory sizes could not be returned.' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
return $all_sizes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the admin textdomain for Site Health tests.
|
||||
*
|
||||
* The {@see WP_Site_Health} class is defined in WP-Admin, while the REST API operates in a front-end context.
|
||||
* This means that the translations for Site Health won't be loaded by default in {@see load_default_textdomain()}.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*/
|
||||
protected function load_admin_textdomain() {
|
||||
// Accounts for inner REST API requests in the admin.
|
||||
if ( ! is_admin() ) {
|
||||
$locale = determine_locale();
|
||||
load_textdomain( 'default', WP_LANG_DIR . "/admin-$locale.mo", $locale );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the schema for each site health test.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @return array The test schema.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->schema;
|
||||
}
|
||||
|
||||
$this->schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'wp-site-health-test',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'test' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'The name of the test being run.' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'label' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'A label describing the test.' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'status' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'The status of the test.' ),
|
||||
'enum' => array( 'good', 'recommended', 'critical' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'badge' => array(
|
||||
'type' => 'object',
|
||||
'description' => __( 'The category this test is grouped in.' ),
|
||||
'properties' => array(
|
||||
'label' => array(
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'color' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'blue', 'orange', 'red', 'green', 'purple', 'gray' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'A more descriptive explanation of what the test looks for, and why it is important for the user.' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'actions' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'HTML containing an action to direct the user to where they can resolve the issue.' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->schema;
|
||||
}
|
||||
}
|
@ -0,0 +1,452 @@
|
||||
<?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 taxonomies.
|
||||
*
|
||||
* @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
|
||||
* @since 5.9.0 Renamed `$taxonomy` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @param WP_Taxonomy $item Taxonomy data.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$taxonomy = $item;
|
||||
|
||||
$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'] = array_values( $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( 'rest_namespace', $fields, true ) ) {
|
||||
$data['rest_namespace'] = $taxonomy->rest_namespace;
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $taxonomy ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 WP_Taxonomy $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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 6.1.0
|
||||
*
|
||||
* @param WP_Taxonomy $taxonomy The taxonomy.
|
||||
* @return array Links for the given taxonomy.
|
||||
*/
|
||||
protected function prepare_links( $taxonomy ) {
|
||||
return array(
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
'https://api.w.org/items' => array(
|
||||
'href' => rest_url( rest_get_route_for_taxonomy_items( $taxonomy->name ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the taxonomy's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @since 5.0.0 The `visibility` property was added.
|
||||
* @since 5.9.0 The `rest_namespace` property was added.
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->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,
|
||||
),
|
||||
'rest_namespace' => array(
|
||||
'description' => __( 'REST namespace 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',
|
||||
),
|
||||
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->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;
|
||||
}
|
||||
}
|
@ -0,0 +1,276 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Template_Autosaves_Controller class.
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 6.4.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access template autosaves via the REST API.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @see WP_REST_Autosaves_Controller
|
||||
*/
|
||||
class WP_REST_Template_Autosaves_Controller extends WP_REST_Autosaves_Controller {
|
||||
/**
|
||||
* Parent post type.
|
||||
*
|
||||
* @since 6.4.0
|
||||
* @var string
|
||||
*/
|
||||
private $parent_post_type;
|
||||
|
||||
/**
|
||||
* Parent post controller.
|
||||
*
|
||||
* @since 6.4.0
|
||||
* @var WP_REST_Controller
|
||||
*/
|
||||
private $parent_controller;
|
||||
|
||||
/**
|
||||
* Revision controller.
|
||||
*
|
||||
* @since 6.4.0
|
||||
* @var WP_REST_Revisions_Controller
|
||||
*/
|
||||
private $revisions_controller;
|
||||
|
||||
/**
|
||||
* The base of the parent controller's route.
|
||||
*
|
||||
* @since 6.4.0
|
||||
* @var string
|
||||
*/
|
||||
private $parent_base;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param string $parent_post_type Post type of the parent.
|
||||
*/
|
||||
public function __construct( $parent_post_type ) {
|
||||
parent::__construct( $parent_post_type );
|
||||
$this->parent_post_type = $parent_post_type;
|
||||
$post_type_object = get_post_type_object( $parent_post_type );
|
||||
$parent_controller = $post_type_object->get_rest_controller();
|
||||
|
||||
if ( ! $parent_controller ) {
|
||||
$parent_controller = new WP_REST_Templates_Controller( $parent_post_type );
|
||||
}
|
||||
|
||||
$this->parent_controller = $parent_controller;
|
||||
|
||||
$revisions_controller = $post_type_object->get_revisions_rest_controller();
|
||||
if ( ! $revisions_controller ) {
|
||||
$revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
|
||||
}
|
||||
$this->revisions_controller = $revisions_controller;
|
||||
$this->rest_base = 'autosaves';
|
||||
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
|
||||
$this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for autosaves.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
sprintf(
|
||||
'/%s/(?P<id>%s%s)/%s',
|
||||
$this->parent_base,
|
||||
/*
|
||||
* Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
|
||||
* Excludes invalid directory name characters: `/:<>*?"|`.
|
||||
*/
|
||||
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
|
||||
// Matches the template name.
|
||||
'[\/\w%-]+',
|
||||
$this->rest_base
|
||||
),
|
||||
array(
|
||||
'args' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'The id of a template' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ),
|
||||
),
|
||||
),
|
||||
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->namespace,
|
||||
sprintf(
|
||||
'/%s/(?P<parent>%s%s)/%s/%s',
|
||||
$this->parent_base,
|
||||
/*
|
||||
* Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
|
||||
* Excludes invalid directory name characters: `/:<>*?"|`.
|
||||
*/
|
||||
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
|
||||
// Matches the template name.
|
||||
'[\/\w%-]+',
|
||||
$this->rest_base,
|
||||
'(?P<id>[\d]+)'
|
||||
),
|
||||
array(
|
||||
'args' => array(
|
||||
'parent' => array(
|
||||
'description' => __( 'The id of a template' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ),
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'The ID for the autosave.' ),
|
||||
'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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the item for the REST response.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WP_Post $item Post revision object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$template = _build_block_template_result_from_post( $item );
|
||||
$response = $this->parent_controller->prepare_item_for_response( $template, $request );
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
if ( in_array( 'parent', $fields, true ) ) {
|
||||
$data['parent'] = (int) $item->post_parent;
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = new WP_REST_Response( $data );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$links = $this->prepare_links( $template );
|
||||
$response->add_links( $links );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the autosave, if the ID is valid.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_Post|WP_Error Autosave post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
$parent = $this->get_parent( $request['parent'] );
|
||||
if ( is_wp_error( $parent ) ) {
|
||||
return $parent;
|
||||
}
|
||||
|
||||
$autosave = wp_get_post_autosave( $parent->ID );
|
||||
|
||||
if ( ! $autosave ) {
|
||||
return new WP_Error(
|
||||
'rest_post_no_autosave',
|
||||
__( 'There is no autosave revision for this template.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$response = $this->prepare_item_for_response( $autosave, $request );
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the parent post.
|
||||
*
|
||||
* @since 6.4.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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WP_Block_Template $template Template.
|
||||
* @return array Links for the given post.
|
||||
*/
|
||||
protected function prepare_links( $template ) {
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '/%s/%s/%s/%s/%d', $this->namespace, $this->parent_base, $template->id, $this->rest_base, $template->wp_id ) ),
|
||||
),
|
||||
'parent' => array(
|
||||
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->parent_base, $template->id ) ),
|
||||
),
|
||||
);
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the autosave's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$this->schema = $this->revisions_controller->get_item_schema();
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
@ -0,0 +1,297 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Template_Revisions_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 6.4.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access template revisions via the REST API.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Template_Revisions_Controller extends WP_REST_Revisions_Controller {
|
||||
/**
|
||||
* Parent post type.
|
||||
*
|
||||
* @since 6.4.0
|
||||
* @var string
|
||||
*/
|
||||
private $parent_post_type;
|
||||
|
||||
/**
|
||||
* Parent controller.
|
||||
*
|
||||
* @since 6.4.0
|
||||
* @var WP_REST_Controller
|
||||
*/
|
||||
private $parent_controller;
|
||||
|
||||
/**
|
||||
* The base of the parent controller's route.
|
||||
*
|
||||
* @since 6.4.0
|
||||
* @var string
|
||||
*/
|
||||
private $parent_base;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param string $parent_post_type Post type of the parent.
|
||||
*/
|
||||
public function __construct( $parent_post_type ) {
|
||||
parent::__construct( $parent_post_type );
|
||||
$this->parent_post_type = $parent_post_type;
|
||||
$post_type_object = get_post_type_object( $parent_post_type );
|
||||
$parent_controller = $post_type_object->get_rest_controller();
|
||||
|
||||
if ( ! $parent_controller ) {
|
||||
$parent_controller = new WP_REST_Templates_Controller( $parent_post_type );
|
||||
}
|
||||
|
||||
$this->parent_controller = $parent_controller;
|
||||
$this->rest_base = 'revisions';
|
||||
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
|
||||
$this->namespace = ! empty( $post_type_object->rest_namespace ) ? $post_type_object->rest_namespace : 'wp/v2';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for revisions based on post types supporting revisions.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @see register_rest_route()
|
||||
*/
|
||||
public function register_routes() {
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
sprintf(
|
||||
'/%s/(?P<parent>%s%s)/%s',
|
||||
$this->parent_base,
|
||||
/*
|
||||
* Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
|
||||
* Excludes invalid directory name characters: `/:<>*?"|`.
|
||||
*/
|
||||
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
|
||||
// Matches the template name.
|
||||
'[\/\w%-]+',
|
||||
$this->rest_base
|
||||
),
|
||||
array(
|
||||
'args' => array(
|
||||
'parent' => array(
|
||||
'description' => __( 'The id of a template' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ),
|
||||
),
|
||||
),
|
||||
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,
|
||||
sprintf(
|
||||
'/%s/(?P<parent>%s%s)/%s/%s',
|
||||
$this->parent_base,
|
||||
/*
|
||||
* Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
|
||||
* Excludes invalid directory name characters: `/:<>*?"|`.
|
||||
*/
|
||||
'([^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?)',
|
||||
// Matches the template name.
|
||||
'[\/\w%-]+',
|
||||
$this->rest_base,
|
||||
'(?P<id>[\d]+)'
|
||||
),
|
||||
array(
|
||||
'args' => array(
|
||||
'parent' => array(
|
||||
'description' => __( 'The id of a template' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( $this->parent_controller, '_sanitize_template_id' ),
|
||||
),
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the revision.' ),
|
||||
'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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the parent post, if the ID is valid.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param int $parent_post_id Supplied ID.
|
||||
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_parent( $parent_post_id ) {
|
||||
$template = get_block_template( $parent_post_id, $this->parent_post_type );
|
||||
|
||||
if ( ! $template ) {
|
||||
return new WP_Error(
|
||||
'rest_post_invalid_parent',
|
||||
__( 'Invalid template parent ID.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
return get_post( $template->wp_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the item for the REST response.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WP_Post $item Post revision object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
$template = _build_block_template_result_from_post( $item );
|
||||
$response = $this->parent_controller->prepare_item_for_response( $template, $request );
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = $response->get_data();
|
||||
|
||||
if ( in_array( 'parent', $fields, true ) ) {
|
||||
$data['parent'] = (int) $item->post_parent;
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = new WP_REST_Response( $data );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$links = $this->prepare_links( $template );
|
||||
$response->add_links( $links );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to delete a revision.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|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;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'delete_post', $parent->ID ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_delete',
|
||||
__( 'Sorry, you are not allowed to delete revisions of this post.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
$revision = $this->get_revision( $request['id'] );
|
||||
if ( is_wp_error( $revision ) ) {
|
||||
return $revision;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'edit_theme_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_delete',
|
||||
__( 'Sorry, you are not allowed to delete this revision.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @param WP_Block_Template $template Template.
|
||||
* @return array Links for the given post.
|
||||
*/
|
||||
protected function prepare_links( $template ) {
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '/%s/%s/%s/%s/%d', $this->namespace, $this->parent_base, $template->id, $this->rest_base, $template->wp_id ) ),
|
||||
),
|
||||
'parent' => array(
|
||||
'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->parent_base, $template->id ) ),
|
||||
),
|
||||
);
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = $this->parent_controller->get_item_schema();
|
||||
|
||||
$schema['properties']['parent'] = array(
|
||||
'description' => __( 'The ID for the parent of the revision.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
1211
wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php
Normal file
1211
wp-includes/rest-api/endpoints/class-wp-rest-terms-controller.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,667 @@
|
||||
<?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 {
|
||||
|
||||
/**
|
||||
* Matches theme's directory: `/themes/<subdirectory>/<theme>/` or `/themes/<theme>/`.
|
||||
* Excludes invalid directory name characters: `/:<>*?"|`.
|
||||
*/
|
||||
const PATTERN = '[^\/:<>\*\?"\|]+(?:\/[^\/:<>\*\?"\|]+)?';
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'themes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for themes.
|
||||
*
|
||||
* @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' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
sprintf( '/%s/(?P<stylesheet>%s)', $this->rest_base, self::PATTERN ),
|
||||
array(
|
||||
'args' => array(
|
||||
'stylesheet' => array(
|
||||
'description' => __( "The theme's stylesheet. This uniquely identifies the theme." ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => array( $this, '_sanitize_stylesheet_callback' ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the stylesheet to decode endpoint.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $stylesheet The stylesheet name.
|
||||
* @return string Sanitized stylesheet.
|
||||
*/
|
||||
public function _sanitize_stylesheet_callback( $stylesheet ) {
|
||||
return urldecode( $stylesheet );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$registered = $this->get_collection_params();
|
||||
if ( isset( $registered['status'], $request['status'] ) && is_array( $request['status'] ) && array( 'active' ) === $request['status'] ) {
|
||||
return $this->check_read_active_theme_permission();
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_view_themes',
|
||||
__( 'Sorry, you are not allowed to view themes.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read the theme.
|
||||
*
|
||||
* @since 5.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 WP_Error object.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
if ( current_user_can( 'switch_themes' ) || current_user_can( 'manage_network_themes' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$wp_theme = wp_get_theme( $request['stylesheet'] );
|
||||
$current_theme = wp_get_theme();
|
||||
|
||||
if ( $this->is_same_theme( $wp_theme, $current_theme ) ) {
|
||||
return $this->check_read_active_theme_permission();
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_view_themes',
|
||||
__( 'Sorry, you are not allowed to view themes.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a theme can be read.
|
||||
*
|
||||
* @since 5.7.0
|
||||
*
|
||||
* @return true|WP_Error True if the theme can be read, WP_Error object otherwise.
|
||||
*/
|
||||
protected function check_read_active_theme_permission() {
|
||||
if ( current_user_can( 'edit_posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
|
||||
if ( current_user_can( $post_type->cap->edit_posts ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_view_active_theme',
|
||||
__( 'Sorry, you are not allowed to view the active theme.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single theme.
|
||||
*
|
||||
* @since 5.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 ) {
|
||||
$wp_theme = wp_get_theme( $request['stylesheet'] );
|
||||
if ( ! $wp_theme->exists() ) {
|
||||
return new WP_Error(
|
||||
'rest_theme_not_found',
|
||||
__( 'Theme not found.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
$data = $this->prepare_item_for_response( $wp_theme, $request );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
$themes = array();
|
||||
|
||||
$active_themes = wp_get_themes();
|
||||
$current_theme = wp_get_theme();
|
||||
$status = $request['status'];
|
||||
|
||||
foreach ( $active_themes as $theme_name => $theme ) {
|
||||
$theme_status = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive';
|
||||
if ( is_array( $status ) && ! in_array( $theme_status, $status, true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$prepared = $this->prepare_item_for_response( $theme, $request );
|
||||
$themes[] = $this->prepare_response_for_collection( $prepared );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $themes );
|
||||
|
||||
$response->header( 'X-WP-Total', count( $themes ) );
|
||||
$response->header( 'X-WP-TotalPages', 1 );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single theme output for response.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @since 5.9.0 Renamed `$theme` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @param WP_Theme $item Theme object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$theme = $item;
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( rest_is_field_included( 'stylesheet', $fields ) ) {
|
||||
$data['stylesheet'] = $theme->get_stylesheet();
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'template', $fields ) ) {
|
||||
/**
|
||||
* Use the get_template() method, not the 'Template' header, for finding the template.
|
||||
* The 'Template' header is only good for what was written in the style.css, while
|
||||
* get_template() takes into account where WordPress actually located the theme and
|
||||
* whether it is actually valid.
|
||||
*/
|
||||
$data['template'] = $theme->get_template();
|
||||
}
|
||||
|
||||
$plain_field_mappings = array(
|
||||
'requires_php' => 'RequiresPHP',
|
||||
'requires_wp' => 'RequiresWP',
|
||||
'textdomain' => 'TextDomain',
|
||||
'version' => 'Version',
|
||||
);
|
||||
|
||||
foreach ( $plain_field_mappings as $field => $header ) {
|
||||
if ( rest_is_field_included( $field, $fields ) ) {
|
||||
$data[ $field ] = $theme->get( $header );
|
||||
}
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'screenshot', $fields ) ) {
|
||||
// Using $theme->get_screenshot() with no args to get absolute URL.
|
||||
$data['screenshot'] = $theme->get_screenshot() ? $theme->get_screenshot() : '';
|
||||
}
|
||||
|
||||
$rich_field_mappings = array(
|
||||
'author' => 'Author',
|
||||
'author_uri' => 'AuthorURI',
|
||||
'description' => 'Description',
|
||||
'name' => 'Name',
|
||||
'tags' => 'Tags',
|
||||
'theme_uri' => 'ThemeURI',
|
||||
);
|
||||
|
||||
foreach ( $rich_field_mappings as $field => $header ) {
|
||||
if ( rest_is_field_included( "{$field}.raw", $fields ) ) {
|
||||
$data[ $field ]['raw'] = $theme->display( $header, false, true );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( "{$field}.rendered", $fields ) ) {
|
||||
$data[ $field ]['rendered'] = $theme->display( $header );
|
||||
}
|
||||
}
|
||||
|
||||
$current_theme = wp_get_theme();
|
||||
if ( rest_is_field_included( 'status', $fields ) ) {
|
||||
$data['status'] = ( $this->is_same_theme( $theme, $current_theme ) ) ? 'active' : 'inactive';
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'theme_supports', $fields ) && $this->is_same_theme( $theme, $current_theme ) ) {
|
||||
foreach ( get_registered_theme_features() as $feature => $config ) {
|
||||
if ( ! is_array( $config['show_in_rest'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $config['show_in_rest']['name'];
|
||||
|
||||
if ( ! rest_is_field_included( "theme_supports.{$name}", $fields ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! current_theme_supports( $feature ) ) {
|
||||
$data['theme_supports'][ $name ] = $config['show_in_rest']['schema']['default'];
|
||||
continue;
|
||||
}
|
||||
|
||||
$support = get_theme_support( $feature );
|
||||
|
||||
if ( isset( $config['show_in_rest']['prepare_callback'] ) ) {
|
||||
$prepare = $config['show_in_rest']['prepare_callback'];
|
||||
} else {
|
||||
$prepare = array( $this, 'prepare_theme_support' );
|
||||
}
|
||||
|
||||
$prepared = $prepare( $support, $config, $feature, $request );
|
||||
|
||||
if ( is_wp_error( $prepared ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data['theme_supports'][ $name ] = $prepared;
|
||||
}
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'is_block_theme', $fields ) ) {
|
||||
$data['is_block_theme'] = $theme->is_block_theme();
|
||||
}
|
||||
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $theme ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 5.7.0
|
||||
*
|
||||
* @param WP_Theme $theme Theme data.
|
||||
* @return array Links for the given block type.
|
||||
*/
|
||||
protected function prepare_links( $theme ) {
|
||||
$links = array(
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $theme->get_stylesheet() ) ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
);
|
||||
|
||||
if ( $this->is_same_theme( $theme, wp_get_theme() ) ) {
|
||||
// This creates a record for the active theme if not existent.
|
||||
$id = WP_Theme_JSON_Resolver::get_user_global_styles_post_id();
|
||||
} else {
|
||||
$user_cpt = WP_Theme_JSON_Resolver::get_user_data_from_wp_global_styles( $theme );
|
||||
$id = isset( $user_cpt['ID'] ) ? $user_cpt['ID'] : null;
|
||||
}
|
||||
|
||||
if ( $id ) {
|
||||
$links['https://api.w.org/user-global-styles'] = array(
|
||||
'href' => rest_url( 'wp/v2/global-styles/' . $id ),
|
||||
);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to compare two themes.
|
||||
*
|
||||
* @since 5.7.0
|
||||
*
|
||||
* @param WP_Theme $theme_a First theme to compare.
|
||||
* @param WP_Theme $theme_b Second theme to compare.
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_same_theme( $theme_a, $theme_b ) {
|
||||
return $theme_a->get_stylesheet() === $theme_b->get_stylesheet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the theme support value for inclusion in the REST API response.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param mixed $support The raw value from get_theme_support().
|
||||
* @param array $args The feature's registration args.
|
||||
* @param string $feature The feature name.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
* @return mixed The prepared support value.
|
||||
*/
|
||||
protected function prepare_theme_support( $support, $args, $feature, $request ) {
|
||||
$schema = $args['show_in_rest']['schema'];
|
||||
|
||||
if ( 'boolean' === $schema['type'] ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( is_array( $support ) && ! $args['variadic'] ) {
|
||||
$support = $support[0];
|
||||
}
|
||||
|
||||
return rest_sanitize_value_from_schema( $support, $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the theme's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'theme',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'stylesheet' => array(
|
||||
'description' => __( 'The theme\'s stylesheet. This uniquely identifies the theme.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'template' => array(
|
||||
'description' => __( 'The theme\'s template. If this is a child theme, this refers to the parent theme, otherwise this is the same as the theme\'s stylesheet.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'author' => array(
|
||||
'description' => __( 'The theme author.' ),
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'description' => __( 'The theme author\'s name, as found in the theme header.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'HTML for the theme author, transformed for display.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'author_uri' => array(
|
||||
'description' => __( 'The website of the theme author.' ),
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'description' => __( 'The website of the theme author, as found in the theme header.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'The website of the theme author, transformed for display.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
),
|
||||
),
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'A description of the theme.' ),
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'description' => __( 'The theme description, as found in the theme header.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'The theme description, transformed for display.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'is_block_theme' => array(
|
||||
'description' => __( 'Whether the theme is a block-based theme.' ),
|
||||
'type' => 'boolean',
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'The name of the theme.' ),
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'description' => __( 'The theme name, as found in the theme header.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'The theme name, transformed for display.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'requires_php' => array(
|
||||
'description' => __( 'The minimum PHP version required for the theme to work.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'requires_wp' => array(
|
||||
'description' => __( 'The minimum WordPress version required for the theme to work.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'screenshot' => array(
|
||||
'description' => __( 'The theme\'s screenshot URL.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'readonly' => true,
|
||||
),
|
||||
'tags' => array(
|
||||
'description' => __( 'Tags indicating styles and features of the theme.' ),
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'description' => __( 'The theme tags, as found in the theme header.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'The theme tags, transformed for display.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'textdomain' => array(
|
||||
'description' => __( 'The theme\'s text domain.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'theme_supports' => array(
|
||||
'description' => __( 'Features supported by this theme.' ),
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'properties' => array(),
|
||||
),
|
||||
'theme_uri' => array(
|
||||
'description' => __( 'The URI of the theme\'s webpage.' ),
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'description' => __( 'The URI of the theme\'s webpage, as found in the theme header.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'The URI of the theme\'s webpage, transformed for display.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
),
|
||||
),
|
||||
),
|
||||
'version' => array(
|
||||
'description' => __( 'The theme\'s current version.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
),
|
||||
'status' => array(
|
||||
'description' => __( 'A named status for the theme.' ),
|
||||
'type' => 'string',
|
||||
'enum' => array( 'inactive', 'active' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
foreach ( get_registered_theme_features() as $feature => $config ) {
|
||||
if ( ! is_array( $config['show_in_rest'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = $config['show_in_rest']['name'];
|
||||
|
||||
$schema['properties']['theme_supports']['properties'][ $name ] = $config['show_in_rest']['schema'];
|
||||
}
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the search params for the themes collection.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$query_params = array(
|
||||
'status' => array(
|
||||
'description' => __( 'Limit result set to themes assigned one or more statuses.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'enum' => array( 'active', 'inactive' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters REST API 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
|
||||
* @deprecated 5.7.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 ) {
|
||||
_deprecated_function( __METHOD__, '5.7.0' );
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
@ -0,0 +1,668 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_URL_Details_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.9.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller which provides REST endpoint for retrieving information
|
||||
* from a remote site's HTML response.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_URL_Details_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructs the controller.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp-block-editor/v1';
|
||||
$this->rest_base = 'url-details';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the necessary REST API routes.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'parse_url_details' ),
|
||||
'args' => array(
|
||||
'url' => array(
|
||||
'required' => true,
|
||||
'description' => __( 'The URL to process.' ),
|
||||
'validate_callback' => 'wp_http_validate_url',
|
||||
'sanitize_callback' => 'sanitize_url',
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
),
|
||||
),
|
||||
'permission_callback' => array( $this, 'permissions_check' ),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$this->schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'url-details',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'title' => array(
|
||||
'description' => sprintf(
|
||||
/* translators: %s: HTML title tag. */
|
||||
__( 'The contents of the %s element from the URL.' ),
|
||||
'<title>'
|
||||
),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'icon' => array(
|
||||
'description' => sprintf(
|
||||
/* translators: %s: HTML link tag. */
|
||||
__( 'The favicon image link of the %s element from the URL.' ),
|
||||
'<link rel="icon">'
|
||||
),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => sprintf(
|
||||
/* translators: %s: HTML meta tag. */
|
||||
__( 'The content of the %s element from the URL.' ),
|
||||
'<meta name="description">'
|
||||
),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'image' => array(
|
||||
'description' => sprintf(
|
||||
/* translators: 1: HTML meta tag, 2: HTML meta tag. */
|
||||
__( 'The Open Graph image link of the %1$s or %2$s element from the URL.' ),
|
||||
'<meta property="og:image">',
|
||||
'<meta property="og:image:url">'
|
||||
),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the contents of the title tag from the HTML response.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error The parsed details as a response object. WP_Error if there are errors.
|
||||
*/
|
||||
public function parse_url_details( $request ) {
|
||||
$url = untrailingslashit( $request['url'] );
|
||||
|
||||
if ( empty( $url ) ) {
|
||||
return new WP_Error( 'rest_invalid_url', __( 'Invalid URL' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
// Transient per URL.
|
||||
$cache_key = $this->build_cache_key_for_url( $url );
|
||||
|
||||
// Attempt to retrieve cached response.
|
||||
$cached_response = $this->get_cache( $cache_key );
|
||||
|
||||
if ( ! empty( $cached_response ) ) {
|
||||
$remote_url_response = $cached_response;
|
||||
} else {
|
||||
$remote_url_response = $this->get_remote_url( $url );
|
||||
|
||||
// Exit if we don't have a valid body or it's empty.
|
||||
if ( is_wp_error( $remote_url_response ) || empty( $remote_url_response ) ) {
|
||||
return $remote_url_response;
|
||||
}
|
||||
|
||||
// Cache the valid response.
|
||||
$this->set_cache( $cache_key, $remote_url_response );
|
||||
}
|
||||
|
||||
$html_head = $this->get_document_head( $remote_url_response );
|
||||
$meta_elements = $this->get_meta_with_content_elements( $html_head );
|
||||
|
||||
$data = $this->add_additional_fields_to_object(
|
||||
array(
|
||||
'title' => $this->get_title( $html_head ),
|
||||
'icon' => $this->get_icon( $html_head, $url ),
|
||||
'description' => $this->get_description( $meta_elements ),
|
||||
'image' => $this->get_image( $meta_elements, $url ),
|
||||
),
|
||||
$request
|
||||
);
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
/**
|
||||
* Filters the URL data for the response.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param string $url The requested URL.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @param string $remote_url_response HTTP response body from the remote URL.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_url_details', $response, $url, $request, $remote_url_response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given request has permission to read remote URLs.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @return WP_Error|bool True if the request has permission, else WP_Error.
|
||||
*/
|
||||
public function permissions_check() {
|
||||
if ( current_user_can( 'edit_posts' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) {
|
||||
if ( current_user_can( $post_type->cap->edit_posts ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_view_url_details',
|
||||
__( 'Sorry, you are not allowed to process remote URLs.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the document title from a remote URL.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $url The website URL whose HTML to access.
|
||||
* @return string|WP_Error The HTTP response from the remote URL on success.
|
||||
* WP_Error if no response or no content.
|
||||
*/
|
||||
private function get_remote_url( $url ) {
|
||||
|
||||
/*
|
||||
* Provide a modified UA string to workaround web properties which block WordPress "Pingbacks".
|
||||
* Why? The UA string used for pingback requests contains `WordPress/` which is very similar
|
||||
* to that used as the default UA string by the WP HTTP API. Therefore requests from this
|
||||
* REST endpoint are being unintentionally blocked as they are misidentified as pingback requests.
|
||||
* By slightly modifying the UA string, but still retaining the "WordPress" identification (via "WP")
|
||||
* we are able to work around this issue.
|
||||
* Example UA string: `WP-URLDetails/5.9-alpha-51389 (+http://localhost:8888)`.
|
||||
*/
|
||||
$modified_user_agent = 'WP-URLDetails/' . get_bloginfo( 'version' ) . ' (+' . get_bloginfo( 'url' ) . ')';
|
||||
|
||||
$args = array(
|
||||
'limit_response_size' => 150 * KB_IN_BYTES,
|
||||
'user-agent' => $modified_user_agent,
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters the HTTP request args for URL data retrieval.
|
||||
*
|
||||
* Can be used to adjust response size limit and other WP_Http::request() args.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param array $args Arguments used for the HTTP request.
|
||||
* @param string $url The attempted URL.
|
||||
*/
|
||||
$args = apply_filters( 'rest_url_details_http_request_args', $args, $url );
|
||||
|
||||
$response = wp_safe_remote_get( $url, $args );
|
||||
|
||||
if ( WP_Http::OK !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
// Not saving the error response to cache since the error might be temporary.
|
||||
return new WP_Error(
|
||||
'no_response',
|
||||
__( 'URL not found. Response returned a non-200 status code for this URL.' ),
|
||||
array( 'status' => WP_Http::NOT_FOUND )
|
||||
);
|
||||
}
|
||||
|
||||
$remote_body = wp_remote_retrieve_body( $response );
|
||||
|
||||
if ( empty( $remote_body ) ) {
|
||||
return new WP_Error(
|
||||
'no_content',
|
||||
__( 'Unable to retrieve body from response at this URL.' ),
|
||||
array( 'status' => WP_Http::NOT_FOUND )
|
||||
);
|
||||
}
|
||||
|
||||
return $remote_body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the title tag contents from the provided HTML.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $html The HTML from the remote website at URL.
|
||||
* @return string The title tag contents on success. Empty string if not found.
|
||||
*/
|
||||
private function get_title( $html ) {
|
||||
$pattern = '#<title[^>]*>(.*?)<\s*/\s*title>#is';
|
||||
preg_match( $pattern, $html, $match_title );
|
||||
|
||||
if ( empty( $match_title[1] ) || ! is_string( $match_title[1] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$title = trim( $match_title[1] );
|
||||
|
||||
return $this->prepare_metadata_for_output( $title );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the site icon from the provided HTML.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $html The HTML from the remote website at URL.
|
||||
* @param string $url The target website URL.
|
||||
* @return string The icon URI on success. Empty string if not found.
|
||||
*/
|
||||
private function get_icon( $html, $url ) {
|
||||
// Grab the icon's link element.
|
||||
$pattern = '#<link\s[^>]*rel=(?:[\"\']??)\s*(?:icon|shortcut icon|icon shortcut)\s*(?:[\"\']??)[^>]*\/?>#isU';
|
||||
preg_match( $pattern, $html, $element );
|
||||
if ( empty( $element[0] ) || ! is_string( $element[0] ) ) {
|
||||
return '';
|
||||
}
|
||||
$element = trim( $element[0] );
|
||||
|
||||
// Get the icon's href value.
|
||||
$pattern = '#href=([\"\']??)([^\" >]*?)\\1[^>]*#isU';
|
||||
preg_match( $pattern, $element, $icon );
|
||||
if ( empty( $icon[2] ) || ! is_string( $icon[2] ) ) {
|
||||
return '';
|
||||
}
|
||||
$icon = trim( $icon[2] );
|
||||
|
||||
// If the icon is a data URL, return it.
|
||||
$parsed_icon = parse_url( $icon );
|
||||
if ( isset( $parsed_icon['scheme'] ) && 'data' === $parsed_icon['scheme'] ) {
|
||||
return $icon;
|
||||
}
|
||||
|
||||
// Attempt to convert relative URLs to absolute.
|
||||
if ( ! is_string( $url ) || '' === $url ) {
|
||||
return $icon;
|
||||
}
|
||||
$parsed_url = parse_url( $url );
|
||||
if ( isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) ) {
|
||||
$root_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/';
|
||||
$icon = WP_Http::make_absolute_url( $icon, $root_url );
|
||||
}
|
||||
|
||||
return $icon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the meta description from the provided HTML.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param array $meta_elements {
|
||||
* A multi-dimensional indexed array on success, else empty array.
|
||||
*
|
||||
* @type string[] $0 Meta elements with a content attribute.
|
||||
* @type string[] $1 Content attribute's opening quotation mark.
|
||||
* @type string[] $2 Content attribute's value for each meta element.
|
||||
* }
|
||||
* @return string The meta description contents on success. Empty string if not found.
|
||||
*/
|
||||
private function get_description( $meta_elements ) {
|
||||
// Bail out if there are no meta elements.
|
||||
if ( empty( $meta_elements[0] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$description = $this->get_metadata_from_meta_element(
|
||||
$meta_elements,
|
||||
'name',
|
||||
'(?:description|og:description)'
|
||||
);
|
||||
|
||||
// Bail out if description not found.
|
||||
if ( '' === $description ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $this->prepare_metadata_for_output( $description );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the Open Graph (OG) Image from the provided HTML.
|
||||
*
|
||||
* See: https://ogp.me/.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param array $meta_elements {
|
||||
* A multi-dimensional indexed array on success, else empty array.
|
||||
*
|
||||
* @type string[] $0 Meta elements with a content attribute.
|
||||
* @type string[] $1 Content attribute's opening quotation mark.
|
||||
* @type string[] $2 Content attribute's value for each meta element.
|
||||
* }
|
||||
* @param string $url The target website URL.
|
||||
* @return string The OG image on success. Empty string if not found.
|
||||
*/
|
||||
private function get_image( $meta_elements, $url ) {
|
||||
$image = $this->get_metadata_from_meta_element(
|
||||
$meta_elements,
|
||||
'property',
|
||||
'(?:og:image|og:image:url)'
|
||||
);
|
||||
|
||||
// Bail out if image not found.
|
||||
if ( '' === $image ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Attempt to convert relative URLs to absolute.
|
||||
$parsed_url = parse_url( $url );
|
||||
if ( isset( $parsed_url['scheme'] ) && isset( $parsed_url['host'] ) ) {
|
||||
$root_url = $parsed_url['scheme'] . '://' . $parsed_url['host'] . '/';
|
||||
$image = WP_Http::make_absolute_url( $image, $root_url );
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the metadata by:
|
||||
* - stripping all HTML tags and tag entities.
|
||||
* - converting non-tag entities into characters.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $metadata The metadata content to prepare.
|
||||
* @return string The prepared metadata.
|
||||
*/
|
||||
private function prepare_metadata_for_output( $metadata ) {
|
||||
$metadata = html_entity_decode( $metadata, ENT_QUOTES, get_bloginfo( 'charset' ) );
|
||||
$metadata = wp_strip_all_tags( $metadata );
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to build cache key for a given URL.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $url The URL for which to build a cache key.
|
||||
* @return string The cache key.
|
||||
*/
|
||||
private function build_cache_key_for_url( $url ) {
|
||||
return 'g_url_details_response_' . md5( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to retrieve a value from the cache at a given key.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $key The cache key.
|
||||
* @return mixed The value from the cache.
|
||||
*/
|
||||
private function get_cache( $key ) {
|
||||
return get_site_transient( $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility function to cache a given data set at a given cache key.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $key The cache key under which to store the value.
|
||||
* @param string $data The data to be stored at the given cache key.
|
||||
* @return bool True when transient set. False if not set.
|
||||
*/
|
||||
private function set_cache( $key, $data = '' ) {
|
||||
$ttl = HOUR_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* Filters the cache expiration.
|
||||
*
|
||||
* Can be used to adjust the time until expiration in seconds for the cache
|
||||
* of the data retrieved for the given URL.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param int $ttl The time until cache expiration in seconds.
|
||||
*/
|
||||
$cache_expiration = apply_filters( 'rest_url_details_cache_expiration', $ttl );
|
||||
|
||||
return set_site_transient( $key, $data, $cache_expiration );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the head element section.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $html The string of HTML to parse.
|
||||
* @return string The `<head>..</head>` section on success. Given `$html` if not found.
|
||||
*/
|
||||
private function get_document_head( $html ) {
|
||||
$head_html = $html;
|
||||
|
||||
// Find the opening `<head>` tag.
|
||||
$head_start = strpos( $html, '<head' );
|
||||
if ( false === $head_start ) {
|
||||
// Didn't find it. Return the original HTML.
|
||||
return $html;
|
||||
}
|
||||
|
||||
// Find the closing `</head>` tag.
|
||||
$head_end = strpos( $head_html, '</head>' );
|
||||
if ( false === $head_end ) {
|
||||
// Didn't find it. Find the opening `<body>` tag.
|
||||
$head_end = strpos( $head_html, '<body' );
|
||||
|
||||
// Didn't find it. Return the original HTML.
|
||||
if ( false === $head_end ) {
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the HTML from opening tag to the closing tag. Then add the closing tag.
|
||||
$head_html = substr( $head_html, $head_start, $head_end );
|
||||
$head_html .= '</head>';
|
||||
|
||||
return $head_html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the meta tag elements that have a 'content' attribute.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $html The string of HTML to be parsed.
|
||||
* @return array {
|
||||
* A multi-dimensional indexed array on success, else empty array.
|
||||
*
|
||||
* @type string[] $0 Meta elements with a content attribute.
|
||||
* @type string[] $1 Content attribute's opening quotation mark.
|
||||
* @type string[] $2 Content attribute's value for each meta element.
|
||||
* }
|
||||
*/
|
||||
private function get_meta_with_content_elements( $html ) {
|
||||
/*
|
||||
* Parse all meta elements with a content attribute.
|
||||
*
|
||||
* Why first search for the content attribute rather than directly searching for name=description element?
|
||||
* tl;dr The content attribute's value will be truncated when it contains a > symbol.
|
||||
*
|
||||
* The content attribute's value (i.e. the description to get) can have HTML in it and be well-formed as
|
||||
* it's a string to the browser. Imagine what happens when attempting to match for the name=description
|
||||
* first. Hmm, if a > or /> symbol is in the content attribute's value, then it terminates the match
|
||||
* as the element's closing symbol. But wait, it's in the content attribute and is not the end of the
|
||||
* element. This is a limitation of using regex. It can't determine "wait a minute this is inside of quotation".
|
||||
* If this happens, what gets matched is not the entire element or all of the content.
|
||||
*
|
||||
* Why not search for the name=description and then content="(.*)"?
|
||||
* The attribute order could be opposite. Plus, additional attributes may exist including being between
|
||||
* the name and content attributes.
|
||||
*
|
||||
* Why not lookahead?
|
||||
* Lookahead is not constrained to stay within the element. The first <meta it finds may not include
|
||||
* the name or content, but rather could be from a different element downstream.
|
||||
*/
|
||||
$pattern = '#<meta\s' .
|
||||
|
||||
/*
|
||||
* Allows for additional attributes before the content attribute.
|
||||
* Searches for anything other than > symbol.
|
||||
*/
|
||||
'[^>]*' .
|
||||
|
||||
/*
|
||||
* Find the content attribute. When found, capture its value (.*).
|
||||
*
|
||||
* Allows for (a) single or double quotes and (b) whitespace in the value.
|
||||
*
|
||||
* Why capture the opening quotation mark, i.e. (["\']), and then backreference,
|
||||
* i.e \1, for the closing quotation mark?
|
||||
* To ensure the closing quotation mark matches the opening one. Why? Attribute values
|
||||
* can contain quotation marks, such as an apostrophe in the content.
|
||||
*/
|
||||
'content=(["\']??)(.*)\1' .
|
||||
|
||||
/*
|
||||
* Allows for additional attributes after the content attribute.
|
||||
* Searches for anything other than > symbol.
|
||||
*/
|
||||
'[^>]*' .
|
||||
|
||||
/*
|
||||
* \/?> searches for the closing > symbol, which can be in either /> or > format.
|
||||
* # ends the pattern.
|
||||
*/
|
||||
'\/?>#' .
|
||||
|
||||
/*
|
||||
* These are the options:
|
||||
* - i : case insensitive
|
||||
* - s : allows newline characters for the . match (needed for multiline elements)
|
||||
* - U means non-greedy matching
|
||||
*/
|
||||
'isU';
|
||||
|
||||
preg_match_all( $pattern, $html, $elements );
|
||||
|
||||
return $elements;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the metadata from a target meta element.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param array $meta_elements {
|
||||
* A multi-dimensional indexed array on success, else empty array.
|
||||
*
|
||||
* @type string[] $0 Meta elements with a content attribute.
|
||||
* @type string[] $1 Content attribute's opening quotation mark.
|
||||
* @type string[] $2 Content attribute's value for each meta element.
|
||||
* }
|
||||
* @param string $attr Attribute that identifies the element with the target metadata.
|
||||
* @param string $attr_value The attribute's value that identifies the element with the target metadata.
|
||||
* @return string The metadata on success. Empty string if not found.
|
||||
*/
|
||||
private function get_metadata_from_meta_element( $meta_elements, $attr, $attr_value ) {
|
||||
// Bail out if there are no meta elements.
|
||||
if ( empty( $meta_elements[0] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$metadata = '';
|
||||
$pattern = '#' .
|
||||
/*
|
||||
* Target this attribute and value to find the metadata element.
|
||||
*
|
||||
* Allows for (a) no, single, double quotes and (b) whitespace in the value.
|
||||
*
|
||||
* Why capture the opening quotation mark, i.e. (["\']), and then backreference,
|
||||
* i.e \1, for the closing quotation mark?
|
||||
* To ensure the closing quotation mark matches the opening one. Why? Attribute values
|
||||
* can contain quotation marks, such as an apostrophe in the content.
|
||||
*/
|
||||
$attr . '=([\"\']??)\s*' . $attr_value . '\s*\1' .
|
||||
|
||||
/*
|
||||
* These are the options:
|
||||
* - i : case insensitive
|
||||
* - s : allows newline characters for the . match (needed for multiline elements)
|
||||
* - U means non-greedy matching
|
||||
*/
|
||||
'#isU';
|
||||
|
||||
// Find the metadata element.
|
||||
foreach ( $meta_elements[0] as $index => $element ) {
|
||||
preg_match( $pattern, $element, $match );
|
||||
|
||||
// This is not the metadata element. Skip it.
|
||||
if ( empty( $match ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* Found the metadata element.
|
||||
* Get the metadata from its matching content array.
|
||||
*/
|
||||
if ( isset( $meta_elements[2][ $index ] ) && is_string( $meta_elements[2][ $index ] ) ) {
|
||||
$metadata = trim( $meta_elements[2][ $index ] );
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
}
|
1614
wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
Normal file
1614
wp-includes/rest-api/endpoints/class-wp-rest-users-controller.php
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,674 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Widget_Types_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.8.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class to access widget types via the REST API.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Widget_Types_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'widget-types';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the widget type routes.
|
||||
*
|
||||
* @since 5.8.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<id>[a-zA-Z0-9_-]+)',
|
||||
array(
|
||||
'args' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'The widget type id.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<id>[a-zA-Z0-9_-]+)/encode',
|
||||
array(
|
||||
'args' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'The widget type id.' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'instance' => array(
|
||||
'description' => __( 'Current instance settings of the widget.' ),
|
||||
'type' => 'object',
|
||||
),
|
||||
'form_data' => array(
|
||||
'description' => __( 'Serialized widget form data to encode into instance settings.' ),
|
||||
'type' => 'string',
|
||||
'sanitize_callback' => static function ( $form_data ) {
|
||||
$array = array();
|
||||
wp_parse_str( $form_data, $array );
|
||||
return $array;
|
||||
},
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
'callback' => array( $this, 'encode_form_data' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<id>[a-zA-Z0-9_-]+)/render',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
'callback' => array( $this, 'render' ),
|
||||
'args' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'The widget type id.' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
'instance' => array(
|
||||
'description' => __( 'Current instance settings of the widget.' ),
|
||||
'type' => 'object',
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a given request has permission to read widget types.
|
||||
*
|
||||
* @since 5.8.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 ) {
|
||||
return $this->check_read_permission();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the list of all widget types.
|
||||
*
|
||||
* @since 5.8.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 ) {
|
||||
$data = array();
|
||||
foreach ( $this->get_widgets() as $widget ) {
|
||||
$widget_type = $this->prepare_item_for_response( $widget, $request );
|
||||
$data[] = $this->prepare_response_for_collection( $widget_type );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read a widget type.
|
||||
*
|
||||
* @since 5.8.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, WP_Error object otherwise.
|
||||
*/
|
||||
public function get_item_permissions_check( $request ) {
|
||||
$check = $this->check_read_permission();
|
||||
if ( is_wp_error( $check ) ) {
|
||||
return $check;
|
||||
}
|
||||
$widget_id = $request['id'];
|
||||
$widget_type = $this->get_widget( $widget_id );
|
||||
if ( is_wp_error( $widget_type ) ) {
|
||||
return $widget_type;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the user can read widget types.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @return true|WP_Error True if the widget type is visible, WP_Error otherwise.
|
||||
*/
|
||||
protected function check_read_permission() {
|
||||
if ( ! current_user_can( 'edit_theme_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_manage_widgets',
|
||||
__( 'Sorry, you are not allowed to manage widgets on this site.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the details about the requested widget.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param string $id The widget type id.
|
||||
* @return array|WP_Error The array of widget data if the name is valid, WP_Error otherwise.
|
||||
*/
|
||||
public function get_widget( $id ) {
|
||||
foreach ( $this->get_widgets() as $widget ) {
|
||||
if ( $id === $widget['id'] ) {
|
||||
return $widget;
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_Error( 'rest_widget_type_invalid', __( 'Invalid widget type.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize array of widgets.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @global WP_Widget_Factory $wp_widget_factory
|
||||
* @global array $wp_registered_widgets The list of registered widgets.
|
||||
*
|
||||
* @return array Array of widgets.
|
||||
*/
|
||||
protected function get_widgets() {
|
||||
global $wp_widget_factory, $wp_registered_widgets;
|
||||
|
||||
$widgets = array();
|
||||
|
||||
foreach ( $wp_registered_widgets as $widget ) {
|
||||
$parsed_id = wp_parse_widget_id( $widget['id'] );
|
||||
$widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] );
|
||||
|
||||
$widget['id'] = $parsed_id['id_base'];
|
||||
$widget['is_multi'] = (bool) $widget_object;
|
||||
|
||||
if ( isset( $widget['name'] ) ) {
|
||||
$widget['name'] = html_entity_decode( $widget['name'], ENT_QUOTES, get_bloginfo( 'charset' ) );
|
||||
}
|
||||
|
||||
if ( isset( $widget['description'] ) ) {
|
||||
$widget['description'] = html_entity_decode( $widget['description'], ENT_QUOTES, get_bloginfo( 'charset' ) );
|
||||
}
|
||||
|
||||
unset( $widget['callback'] );
|
||||
|
||||
$classname = '';
|
||||
foreach ( (array) $widget['classname'] as $cn ) {
|
||||
if ( is_string( $cn ) ) {
|
||||
$classname .= '_' . $cn;
|
||||
} elseif ( is_object( $cn ) ) {
|
||||
$classname .= '_' . get_class( $cn );
|
||||
}
|
||||
}
|
||||
$widget['classname'] = ltrim( $classname, '_' );
|
||||
|
||||
$widgets[ $widget['id'] ] = $widget;
|
||||
}
|
||||
|
||||
ksort( $widgets );
|
||||
|
||||
return $widgets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a single widget type from the collection.
|
||||
*
|
||||
* @since 5.8.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 ) {
|
||||
$widget_id = $request['id'];
|
||||
$widget_type = $this->get_widget( $widget_id );
|
||||
if ( is_wp_error( $widget_type ) ) {
|
||||
return $widget_type;
|
||||
}
|
||||
$data = $this->prepare_item_for_response( $widget_type, $request );
|
||||
|
||||
return rest_ensure_response( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a widget type object for serialization.
|
||||
*
|
||||
* @since 5.8.0
|
||||
* @since 5.9.0 Renamed `$widget_type` to `$item` to match parent class for PHP 8 named parameter support.
|
||||
*
|
||||
* @param array $item Widget type data.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Widget type data.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
// Restores the more descriptive, specific name for use within this method.
|
||||
$widget_type = $item;
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array(
|
||||
'id' => $widget_type['id'],
|
||||
);
|
||||
|
||||
$schema = $this->get_item_schema();
|
||||
$extra_fields = array(
|
||||
'name',
|
||||
'description',
|
||||
'is_multi',
|
||||
'classname',
|
||||
'widget_class',
|
||||
'option_name',
|
||||
'customize_selective_refresh',
|
||||
);
|
||||
|
||||
foreach ( $extra_fields as $extra_field ) {
|
||||
if ( ! rest_is_field_included( $extra_field, $fields ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( isset( $widget_type[ $extra_field ] ) ) {
|
||||
$field = $widget_type[ $extra_field ];
|
||||
} elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) {
|
||||
$field = $schema['properties'][ $extra_field ]['default'];
|
||||
} else {
|
||||
$field = '';
|
||||
}
|
||||
|
||||
$data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] );
|
||||
}
|
||||
|
||||
$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 ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $widget_type ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the REST API response for a widget type.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param array $widget_type The array of widget data.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_widget_type', $response, $widget_type, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the widget type.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param array $widget_type Widget type data.
|
||||
* @return array Links for the given widget type.
|
||||
*/
|
||||
protected function prepare_links( $widget_type ) {
|
||||
return array(
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $widget_type['id'] ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the widget type's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'widget-type',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Unique slug identifying the widget type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'Human-readable name identifying the widget type.' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'Description of the widget.' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'is_multi' => array(
|
||||
'description' => __( 'Whether the widget supports multiple instances' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'classname' => array(
|
||||
'description' => __( 'Class name' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* An RPC-style endpoint which can be used by clients to turn user input in
|
||||
* a widget admin form into an encoded instance object.
|
||||
*
|
||||
* Accepts:
|
||||
*
|
||||
* - id: A widget type ID.
|
||||
* - instance: A widget's encoded instance object. Optional.
|
||||
* - form_data: Form data from submitting a widget's admin form. Optional.
|
||||
*
|
||||
* Returns:
|
||||
* - instance: The encoded instance object after updating the widget with
|
||||
* the given form data.
|
||||
* - form: The widget's admin form after updating the widget with the
|
||||
* given form data.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @global WP_Widget_Factory $wp_widget_factory
|
||||
*
|
||||
* @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 encode_form_data( $request ) {
|
||||
global $wp_widget_factory;
|
||||
|
||||
$id = $request['id'];
|
||||
$widget_object = $wp_widget_factory->get_widget_object( $id );
|
||||
|
||||
if ( ! $widget_object ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_widget',
|
||||
__( 'Cannot preview a widget that does not extend WP_Widget.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the widget's number so that the id attributes in the HTML that we
|
||||
* return are predictable.
|
||||
*/
|
||||
if ( isset( $request['number'] ) && is_numeric( $request['number'] ) ) {
|
||||
$widget_object->_set( (int) $request['number'] );
|
||||
} else {
|
||||
$widget_object->_set( -1 );
|
||||
}
|
||||
|
||||
if ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) {
|
||||
$serialized_instance = base64_decode( $request['instance']['encoded'] );
|
||||
if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_widget',
|
||||
__( 'The provided instance is malformed.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
$instance = unserialize( $serialized_instance );
|
||||
} else {
|
||||
$instance = array();
|
||||
}
|
||||
|
||||
if (
|
||||
isset( $request['form_data'][ "widget-$id" ] ) &&
|
||||
is_array( $request['form_data'][ "widget-$id" ] )
|
||||
) {
|
||||
$new_instance = array_values( $request['form_data'][ "widget-$id" ] )[0];
|
||||
$old_instance = $instance;
|
||||
|
||||
$instance = $widget_object->update( $new_instance, $old_instance );
|
||||
|
||||
/** This filter is documented in wp-includes/class-wp-widget.php */
|
||||
$instance = apply_filters(
|
||||
'widget_update_callback',
|
||||
$instance,
|
||||
$new_instance,
|
||||
$old_instance,
|
||||
$widget_object
|
||||
);
|
||||
}
|
||||
|
||||
$serialized_instance = serialize( $instance );
|
||||
$widget_key = $wp_widget_factory->get_widget_key( $id );
|
||||
|
||||
$response = array(
|
||||
'form' => trim(
|
||||
$this->get_widget_form(
|
||||
$widget_object,
|
||||
$instance
|
||||
)
|
||||
),
|
||||
'preview' => trim(
|
||||
$this->get_widget_preview(
|
||||
$widget_key,
|
||||
$instance
|
||||
)
|
||||
),
|
||||
'instance' => array(
|
||||
'encoded' => base64_encode( $serialized_instance ),
|
||||
'hash' => wp_hash( $serialized_instance ),
|
||||
),
|
||||
);
|
||||
|
||||
if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
|
||||
// Use new stdClass so that JSON result is {} and not [].
|
||||
$response['instance']['raw'] = empty( $instance ) ? new stdClass() : $instance;
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the output of WP_Widget::widget() when called with the provided
|
||||
* instance. Used by encode_form_data() to preview a widget.
|
||||
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param string $widget The widget's PHP class name (see class-wp-widget.php).
|
||||
* @param array $instance Widget instance settings.
|
||||
* @return string
|
||||
*/
|
||||
private function get_widget_preview( $widget, $instance ) {
|
||||
ob_start();
|
||||
the_widget( $widget, $instance );
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the output of WP_Widget::form() when called with the provided
|
||||
* instance. Used by encode_form_data() to preview a widget's form.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_Widget $widget_object Widget object to call widget() on.
|
||||
* @param array $instance Widget instance settings.
|
||||
* @return string
|
||||
*/
|
||||
private function get_widget_form( $widget_object, $instance ) {
|
||||
ob_start();
|
||||
|
||||
/** This filter is documented in wp-includes/class-wp-widget.php */
|
||||
$instance = apply_filters(
|
||||
'widget_form_callback',
|
||||
$instance,
|
||||
$widget_object
|
||||
);
|
||||
|
||||
if ( false !== $instance ) {
|
||||
$return = $widget_object->form( $instance );
|
||||
|
||||
/** This filter is documented in wp-includes/class-wp-widget.php */
|
||||
do_action_ref_array(
|
||||
'in_widget_form',
|
||||
array( &$widget_object, &$return, $instance )
|
||||
);
|
||||
}
|
||||
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a single Legacy Widget and wraps it in a JSON-encodable array.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
*
|
||||
* @return array An array with rendered Legacy Widget HTML.
|
||||
*/
|
||||
public function render( $request ) {
|
||||
return array(
|
||||
'preview' => $this->render_legacy_widget_preview_iframe(
|
||||
$request['id'],
|
||||
isset( $request['instance'] ) ? $request['instance'] : null
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a page containing a preview of the requested Legacy Widget block.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $id_base The id base of the requested widget.
|
||||
* @param array $instance The widget instance attributes.
|
||||
*
|
||||
* @return string Rendered Legacy Widget block preview.
|
||||
*/
|
||||
private function render_legacy_widget_preview_iframe( $id_base, $instance ) {
|
||||
if ( ! defined( 'IFRAME_REQUEST' ) ) {
|
||||
define( 'IFRAME_REQUEST', true );
|
||||
}
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<!doctype html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<meta charset="<?php bloginfo( 'charset' ); ?>" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link rel="profile" href="https://gmpg.org/xfn/11" />
|
||||
<?php wp_head(); ?>
|
||||
<style>
|
||||
/* Reset theme styles */
|
||||
html, body, #page, #content {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body <?php body_class(); ?>>
|
||||
<div id="page" class="site">
|
||||
<div id="content" class="site-content">
|
||||
<?php
|
||||
$registry = WP_Block_Type_Registry::get_instance();
|
||||
$block = $registry->get_registered( 'core/legacy-widget' );
|
||||
echo $block->render(
|
||||
array(
|
||||
'idBase' => $id_base,
|
||||
'instance' => $instance,
|
||||
)
|
||||
);
|
||||
?>
|
||||
</div><!-- #content -->
|
||||
</div><!-- #page -->
|
||||
<?php wp_footer(); ?>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for collections.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
return array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,876 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Widgets_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.8.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class to access widgets via the REST API.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Widgets_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Tracks whether {@see retrieve_widgets()} has been called in the current request.
|
||||
*
|
||||
* @since 5.9.0
|
||||
* @var bool
|
||||
*/
|
||||
protected $widgets_retrieved = false;
|
||||
|
||||
/**
|
||||
* Whether the controller supports batching.
|
||||
*
|
||||
* @since 5.9.0
|
||||
* @var array
|
||||
*/
|
||||
protected $allow_batch = array( 'v1' => true );
|
||||
|
||||
/**
|
||||
* Widgets controller constructor.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'widgets';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the widget routes for the controller.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
$this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'create_item' ),
|
||||
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema(),
|
||||
),
|
||||
'allow_batch' => $this->allow_batch,
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
$this->rest_base . '/(?P<id>[\w\-]+)',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::EDITABLE,
|
||||
'callback' => array( $this, 'update_item' ),
|
||||
'permission_callback' => array( $this, 'update_item_permissions_check' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::DELETABLE,
|
||||
'callback' => array( $this, 'delete_item' ),
|
||||
'permission_callback' => array( $this, 'delete_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'force' => array(
|
||||
'description' => __( 'Whether to force removal of the widget, or move it to the inactive sidebar.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
),
|
||||
),
|
||||
'allow_batch' => $this->allow_batch,
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get widgets.
|
||||
*
|
||||
* @since 5.8.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 ) {
|
||||
$this->retrieve_widgets();
|
||||
if ( isset( $request['sidebar'] ) && $this->check_read_sidebar_permission( $request['sidebar'] ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) {
|
||||
if ( $this->check_read_sidebar_permission( $sidebar_id ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a collection of widgets.
|
||||
*
|
||||
* @since 5.8.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 ) {
|
||||
$this->retrieve_widgets();
|
||||
|
||||
$prepared = array();
|
||||
$permissions_check = $this->permissions_check( $request );
|
||||
|
||||
foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) {
|
||||
if ( isset( $request['sidebar'] ) && $sidebar_id !== $request['sidebar'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( is_wp_error( $permissions_check ) && ! $this->check_read_sidebar_permission( $sidebar_id ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $widget_ids as $widget_id ) {
|
||||
$response = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request );
|
||||
|
||||
if ( ! is_wp_error( $response ) ) {
|
||||
$prepared[] = $this->prepare_response_for_collection( $response );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $prepared );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get a widget.
|
||||
*
|
||||
* @since 5.8.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_item_permissions_check( $request ) {
|
||||
$this->retrieve_widgets();
|
||||
|
||||
$widget_id = $request['id'];
|
||||
$sidebar_id = wp_find_widgets_sidebar( $widget_id );
|
||||
|
||||
if ( $sidebar_id && $this->check_read_sidebar_permission( $sidebar_id ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a sidebar can be read publicly.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @param string $sidebar_id The sidebar ID.
|
||||
* @return bool Whether the sidebar can be read.
|
||||
*/
|
||||
protected function check_read_sidebar_permission( $sidebar_id ) {
|
||||
$sidebar = wp_get_sidebar( $sidebar_id );
|
||||
|
||||
return ! empty( $sidebar['show_in_rest'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an individual widget.
|
||||
*
|
||||
* @since 5.8.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 ) {
|
||||
$this->retrieve_widgets();
|
||||
|
||||
$widget_id = $request['id'];
|
||||
$sidebar_id = wp_find_widgets_sidebar( $widget_id );
|
||||
|
||||
if ( is_null( $sidebar_id ) ) {
|
||||
return new WP_Error(
|
||||
'rest_widget_not_found',
|
||||
__( 'No widget was found with that id.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to create widgets.
|
||||
*
|
||||
* @since 5.8.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 create_item_permissions_check( $request ) {
|
||||
return $this->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a widget.
|
||||
*
|
||||
* @since 5.8.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 ) {
|
||||
$sidebar_id = $request['sidebar'];
|
||||
|
||||
$widget_id = $this->save_widget( $request, $sidebar_id );
|
||||
|
||||
if ( is_wp_error( $widget_id ) ) {
|
||||
return $widget_id;
|
||||
}
|
||||
|
||||
wp_assign_widget_to_sidebar( $widget_id, $sidebar_id );
|
||||
|
||||
$request['context'] = 'edit';
|
||||
|
||||
$response = $this->prepare_item_for_response( compact( 'sidebar_id', 'widget_id' ), $request );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->set_status( 201 );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to update widgets.
|
||||
*
|
||||
* @since 5.8.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 update_item_permissions_check( $request ) {
|
||||
return $this->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates an existing widget.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @global WP_Widget_Factory $wp_widget_factory
|
||||
*
|
||||
* @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 update_item( $request ) {
|
||||
global $wp_widget_factory;
|
||||
|
||||
/*
|
||||
* retrieve_widgets() contains logic to move "hidden" or "lost" widgets to the
|
||||
* wp_inactive_widgets sidebar based on the contents of the $sidebars_widgets global.
|
||||
*
|
||||
* When batch requests are processed, this global is not properly updated by previous
|
||||
* calls, resulting in widgets incorrectly being moved to the wp_inactive_widgets
|
||||
* sidebar.
|
||||
*
|
||||
* See https://core.trac.wordpress.org/ticket/53657.
|
||||
*/
|
||||
wp_get_sidebars_widgets();
|
||||
$this->retrieve_widgets();
|
||||
|
||||
$widget_id = $request['id'];
|
||||
$sidebar_id = wp_find_widgets_sidebar( $widget_id );
|
||||
|
||||
// Allow sidebar to be unset or missing when widget is not a WP_Widget.
|
||||
$parsed_id = wp_parse_widget_id( $widget_id );
|
||||
$widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] );
|
||||
if ( is_null( $sidebar_id ) && $widget_object ) {
|
||||
return new WP_Error(
|
||||
'rest_widget_not_found',
|
||||
__( 'No widget was found with that id.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
$request->has_param( 'instance' ) ||
|
||||
$request->has_param( 'form_data' )
|
||||
) {
|
||||
$maybe_error = $this->save_widget( $request, $sidebar_id );
|
||||
if ( is_wp_error( $maybe_error ) ) {
|
||||
return $maybe_error;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $request->has_param( 'sidebar' ) ) {
|
||||
if ( $sidebar_id !== $request['sidebar'] ) {
|
||||
$sidebar_id = $request['sidebar'];
|
||||
wp_assign_widget_to_sidebar( $widget_id, $sidebar_id );
|
||||
}
|
||||
}
|
||||
|
||||
$request['context'] = 'edit';
|
||||
|
||||
return $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to delete widgets.
|
||||
*
|
||||
* @since 5.8.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 delete_item_permissions_check( $request ) {
|
||||
return $this->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a widget.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @global WP_Widget_Factory $wp_widget_factory
|
||||
* @global array $wp_registered_widget_updates The registered widget update functions.
|
||||
*
|
||||
* @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 delete_item( $request ) {
|
||||
global $wp_widget_factory, $wp_registered_widget_updates;
|
||||
|
||||
/*
|
||||
* retrieve_widgets() contains logic to move "hidden" or "lost" widgets to the
|
||||
* wp_inactive_widgets sidebar based on the contents of the $sidebars_widgets global.
|
||||
*
|
||||
* When batch requests are processed, this global is not properly updated by previous
|
||||
* calls, resulting in widgets incorrectly being moved to the wp_inactive_widgets
|
||||
* sidebar.
|
||||
*
|
||||
* See https://core.trac.wordpress.org/ticket/53657.
|
||||
*/
|
||||
wp_get_sidebars_widgets();
|
||||
$this->retrieve_widgets();
|
||||
|
||||
$widget_id = $request['id'];
|
||||
$sidebar_id = wp_find_widgets_sidebar( $widget_id );
|
||||
|
||||
if ( is_null( $sidebar_id ) ) {
|
||||
return new WP_Error(
|
||||
'rest_widget_not_found',
|
||||
__( 'No widget was found with that id.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$request['context'] = 'edit';
|
||||
|
||||
if ( $request['force'] ) {
|
||||
$response = $this->prepare_item_for_response( compact( 'widget_id', 'sidebar_id' ), $request );
|
||||
|
||||
$parsed_id = wp_parse_widget_id( $widget_id );
|
||||
$id_base = $parsed_id['id_base'];
|
||||
|
||||
$original_post = $_POST;
|
||||
$original_request = $_REQUEST;
|
||||
|
||||
$_POST = array(
|
||||
'sidebar' => $sidebar_id,
|
||||
"widget-$id_base" => array(),
|
||||
'the-widget-id' => $widget_id,
|
||||
'delete_widget' => '1',
|
||||
);
|
||||
$_REQUEST = $_POST;
|
||||
|
||||
/** This action is documented in wp-admin/widgets-form.php */
|
||||
do_action( 'delete_widget', $widget_id, $sidebar_id, $id_base );
|
||||
|
||||
$callback = $wp_registered_widget_updates[ $id_base ]['callback'];
|
||||
$params = $wp_registered_widget_updates[ $id_base ]['params'];
|
||||
|
||||
if ( is_callable( $callback ) ) {
|
||||
ob_start();
|
||||
call_user_func_array( $callback, $params );
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
$_POST = $original_post;
|
||||
$_REQUEST = $original_request;
|
||||
|
||||
$widget_object = $wp_widget_factory->get_widget_object( $id_base );
|
||||
|
||||
if ( $widget_object ) {
|
||||
/*
|
||||
* WP_Widget sets `updated = true` after an update to prevent more than one widget
|
||||
* from being saved per request. This isn't what we want in the REST API, though,
|
||||
* as we support batch requests.
|
||||
*/
|
||||
$widget_object->updated = false;
|
||||
}
|
||||
|
||||
wp_assign_widget_to_sidebar( $widget_id, '' );
|
||||
|
||||
$response->set_data(
|
||||
array(
|
||||
'deleted' => true,
|
||||
'previous' => $response->get_data(),
|
||||
)
|
||||
);
|
||||
} else {
|
||||
wp_assign_widget_to_sidebar( $widget_id, 'wp_inactive_widgets' );
|
||||
|
||||
$response = $this->prepare_item_for_response(
|
||||
array(
|
||||
'sidebar_id' => 'wp_inactive_widgets',
|
||||
'widget_id' => $widget_id,
|
||||
),
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after a widget is deleted via the REST API.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param string $widget_id ID of the widget marked for deletion.
|
||||
* @param string $sidebar_id ID of the sidebar the widget was deleted from.
|
||||
* @param WP_REST_Response|WP_Error $response The response data, or WP_Error object on failure.
|
||||
* @param WP_REST_Request $request The request sent to the API.
|
||||
*/
|
||||
do_action( 'rest_delete_widget', $widget_id, $sidebar_id, $response, $request );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a permissions check for managing widgets.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
protected function permissions_check( $request ) {
|
||||
if ( ! current_user_can( 'edit_theme_options' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_manage_widgets',
|
||||
__( 'Sorry, you are not allowed to manage widgets on this site.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for "lost" widgets once per request.
|
||||
*
|
||||
* @since 5.9.0
|
||||
*
|
||||
* @see retrieve_widgets()
|
||||
*/
|
||||
protected function retrieve_widgets() {
|
||||
if ( ! $this->widgets_retrieved ) {
|
||||
retrieve_widgets();
|
||||
$this->widgets_retrieved = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the widget in the request object.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @global WP_Widget_Factory $wp_widget_factory
|
||||
* @global array $wp_registered_widget_updates The registered widget update functions.
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @param string $sidebar_id ID of the sidebar the widget belongs to.
|
||||
* @return string|WP_Error The saved widget ID.
|
||||
*/
|
||||
protected function save_widget( $request, $sidebar_id ) {
|
||||
global $wp_widget_factory, $wp_registered_widget_updates;
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/widgets.php'; // For next_widget_id_number().
|
||||
|
||||
if ( isset( $request['id'] ) ) {
|
||||
// Saving an existing widget.
|
||||
$id = $request['id'];
|
||||
$parsed_id = wp_parse_widget_id( $id );
|
||||
$id_base = $parsed_id['id_base'];
|
||||
$number = isset( $parsed_id['number'] ) ? $parsed_id['number'] : null;
|
||||
$widget_object = $wp_widget_factory->get_widget_object( $id_base );
|
||||
$creating = false;
|
||||
} elseif ( $request['id_base'] ) {
|
||||
// Saving a new widget.
|
||||
$id_base = $request['id_base'];
|
||||
$widget_object = $wp_widget_factory->get_widget_object( $id_base );
|
||||
$number = $widget_object ? next_widget_id_number( $id_base ) : null;
|
||||
$id = $widget_object ? $id_base . '-' . $number : $id_base;
|
||||
$creating = true;
|
||||
} else {
|
||||
return new WP_Error(
|
||||
'rest_invalid_widget',
|
||||
__( 'Widget type (id_base) is required.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! isset( $wp_registered_widget_updates[ $id_base ] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_widget',
|
||||
__( 'The provided widget type (id_base) cannot be updated.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $request['instance'] ) ) {
|
||||
if ( ! $widget_object ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_widget',
|
||||
__( 'Cannot set instance on a widget that does not extend WP_Widget.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
if ( isset( $request['instance']['raw'] ) ) {
|
||||
if ( empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_widget',
|
||||
__( 'Widget type does not support raw instances.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
$instance = $request['instance']['raw'];
|
||||
} elseif ( isset( $request['instance']['encoded'], $request['instance']['hash'] ) ) {
|
||||
$serialized_instance = base64_decode( $request['instance']['encoded'] );
|
||||
if ( ! hash_equals( wp_hash( $serialized_instance ), $request['instance']['hash'] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_widget',
|
||||
__( 'The provided instance is malformed.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
$instance = unserialize( $serialized_instance );
|
||||
} else {
|
||||
return new WP_Error(
|
||||
'rest_invalid_widget',
|
||||
__( 'The provided instance is invalid. Must contain raw OR encoded and hash.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$form_data = array(
|
||||
"widget-$id_base" => array(
|
||||
$number => $instance,
|
||||
),
|
||||
'sidebar' => $sidebar_id,
|
||||
);
|
||||
} elseif ( isset( $request['form_data'] ) ) {
|
||||
$form_data = $request['form_data'];
|
||||
} else {
|
||||
$form_data = array();
|
||||
}
|
||||
|
||||
$original_post = $_POST;
|
||||
$original_request = $_REQUEST;
|
||||
|
||||
foreach ( $form_data as $key => $value ) {
|
||||
$slashed_value = wp_slash( $value );
|
||||
$_POST[ $key ] = $slashed_value;
|
||||
$_REQUEST[ $key ] = $slashed_value;
|
||||
}
|
||||
|
||||
$callback = $wp_registered_widget_updates[ $id_base ]['callback'];
|
||||
$params = $wp_registered_widget_updates[ $id_base ]['params'];
|
||||
|
||||
if ( is_callable( $callback ) ) {
|
||||
ob_start();
|
||||
call_user_func_array( $callback, $params );
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
$_POST = $original_post;
|
||||
$_REQUEST = $original_request;
|
||||
|
||||
if ( $widget_object ) {
|
||||
// Register any multi-widget that the update callback just created.
|
||||
$widget_object->_set( $number );
|
||||
$widget_object->_register_one( $number );
|
||||
|
||||
/*
|
||||
* WP_Widget sets `updated = true` after an update to prevent more than one widget
|
||||
* from being saved per request. This isn't what we want in the REST API, though,
|
||||
* as we support batch requests.
|
||||
*/
|
||||
$widget_object->updated = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires after a widget is created or updated via the REST API.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param string $id ID of the widget being saved.
|
||||
* @param string $sidebar_id ID of the sidebar containing the widget being saved.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @param bool $creating True when creating a widget, false when updating.
|
||||
*/
|
||||
do_action( 'rest_after_save_widget', $id, $sidebar_id, $request, $creating );
|
||||
|
||||
return $id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the widget for the REST response.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @global WP_Widget_Factory $wp_widget_factory
|
||||
* @global array $wp_registered_widgets The registered widgets.
|
||||
*
|
||||
* @param array $item An array containing a widget_id and sidebar_id.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function prepare_item_for_response( $item, $request ) {
|
||||
global $wp_widget_factory, $wp_registered_widgets;
|
||||
|
||||
$widget_id = $item['widget_id'];
|
||||
$sidebar_id = $item['sidebar_id'];
|
||||
|
||||
if ( ! isset( $wp_registered_widgets[ $widget_id ] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_widget',
|
||||
__( 'The requested widget is invalid.' ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
$widget = $wp_registered_widgets[ $widget_id ];
|
||||
$parsed_id = wp_parse_widget_id( $widget_id );
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
$prepared = array(
|
||||
'id' => $widget_id,
|
||||
'id_base' => $parsed_id['id_base'],
|
||||
'sidebar' => $sidebar_id,
|
||||
'rendered' => '',
|
||||
'rendered_form' => null,
|
||||
'instance' => null,
|
||||
);
|
||||
|
||||
if (
|
||||
rest_is_field_included( 'rendered', $fields ) &&
|
||||
'wp_inactive_widgets' !== $sidebar_id
|
||||
) {
|
||||
$prepared['rendered'] = trim( wp_render_widget( $widget_id, $sidebar_id ) );
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'rendered_form', $fields ) ) {
|
||||
$rendered_form = wp_render_widget_control( $widget_id );
|
||||
if ( ! is_null( $rendered_form ) ) {
|
||||
$prepared['rendered_form'] = trim( $rendered_form );
|
||||
}
|
||||
}
|
||||
|
||||
if ( rest_is_field_included( 'instance', $fields ) ) {
|
||||
$widget_object = $wp_widget_factory->get_widget_object( $parsed_id['id_base'] );
|
||||
if ( $widget_object && isset( $parsed_id['number'] ) ) {
|
||||
$all_instances = $widget_object->get_settings();
|
||||
$instance = $all_instances[ $parsed_id['number'] ];
|
||||
$serialized_instance = serialize( $instance );
|
||||
$prepared['instance']['encoded'] = base64_encode( $serialized_instance );
|
||||
$prepared['instance']['hash'] = wp_hash( $serialized_instance );
|
||||
|
||||
if ( ! empty( $widget_object->widget_options['show_instance_in_rest'] ) ) {
|
||||
// Use new stdClass so that JSON result is {} and not [].
|
||||
$prepared['instance']['raw'] = empty( $instance ) ? new stdClass() : $instance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$prepared = $this->add_additional_fields_to_object( $prepared, $request );
|
||||
$prepared = $this->filter_response_by_context( $prepared, $context );
|
||||
|
||||
$response = rest_ensure_response( $prepared );
|
||||
|
||||
if ( rest_is_field_included( '_links', $fields ) || rest_is_field_included( '_embedded', $fields ) ) {
|
||||
$response->add_links( $this->prepare_links( $prepared ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the REST API response for a widget.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Response|WP_Error $response The response object, or WP_Error object on failure.
|
||||
* @param array $widget The registered widget data.
|
||||
* @param WP_REST_Request $request Request used to generate the response.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_widget', $response, $widget, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the widget.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param array $prepared Widget.
|
||||
* @return array Links for the given widget.
|
||||
*/
|
||||
protected function prepare_links( $prepared ) {
|
||||
$id_base = ! empty( $prepared['id_base'] ) ? $prepared['id_base'] : $prepared['id'];
|
||||
|
||||
return array(
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $prepared['id'] ) ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
'about' => array(
|
||||
'href' => rest_url( sprintf( 'wp/v2/widget-types/%s', $id_base ) ),
|
||||
'embeddable' => true,
|
||||
),
|
||||
'https://api.w.org/sidebar' => array(
|
||||
'href' => rest_url( sprintf( 'wp/v2/sidebars/%s/', $prepared['sidebar'] ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the list of collection params.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @return array[]
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
return array(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
'sidebar' => array(
|
||||
'description' => __( 'The sidebar to return widgets for.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the widget's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
if ( $this->schema ) {
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
$this->schema = array(
|
||||
'$schema' => 'http://json-schema.org/draft-04/schema#',
|
||||
'title' => 'widget',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'Unique identifier for the widget.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'id_base' => array(
|
||||
'description' => __( 'The type of the widget. Corresponds to ID in widget-types endpoint.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'sidebar' => array(
|
||||
'description' => __( 'The sidebar the widget belongs to.' ),
|
||||
'type' => 'string',
|
||||
'default' => 'wp_inactive_widgets',
|
||||
'required' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'HTML representation of the widget.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'rendered_form' => array(
|
||||
'description' => __( 'HTML representation of the widget admin form.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'instance' => array(
|
||||
'description' => __( 'Instance settings of the widget, if supported.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'default' => null,
|
||||
'properties' => array(
|
||||
'encoded' => array(
|
||||
'description' => __( 'Base64 encoded representation of the instance settings.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
'hash' => array(
|
||||
'description' => __( 'Cryptographic hash of the instance settings.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
'raw' => array(
|
||||
'description' => __( 'Unencoded instance settings, if supported.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'form_data' => array(
|
||||
'description' => __( 'URL-encoded form data from the widget admin form. Used to update a widget that does not support instance. Write only.' ),
|
||||
'type' => 'string',
|
||||
'context' => array(),
|
||||
'arg_options' => array(
|
||||
'sanitize_callback' => static function ( $form_data ) {
|
||||
$array = array();
|
||||
wp_parse_str( $form_data, $array );
|
||||
return $array;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
@ -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 comment type for comment meta.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The meta type.
|
||||
*/
|
||||
protected function get_meta_type() {
|
||||
return 'comment';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the comment 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';
|
||||
}
|
||||
}
|
634
wp-includes/rest-api/fields/class-wp-rest-meta-fields.php
Normal file
634
wp-includes/rest-api/fields/class-wp-rest-meta-fields.php
Normal file
@ -0,0 +1,634 @@
|
||||
<?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
|
||||
*/
|
||||
#[AllowDynamicProperties]
|
||||
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
|
||||
* @deprecated 5.6.0
|
||||
*
|
||||
* @see register_rest_field()
|
||||
*/
|
||||
public function register_field() {
|
||||
_deprecated_function( __METHOD__, '5.6.0' );
|
||||
|
||||
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 array Array containing the meta values keyed by name.
|
||||
*/
|
||||
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();
|
||||
|
||||
if ( is_array( $all_values ) ) {
|
||||
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 null|WP_Error Null on success, WP_Error object on failure.
|
||||
*/
|
||||
public function update_value( $meta, $object_id ) {
|
||||
$fields = $this->get_registered_fields();
|
||||
$error = new WP_Error();
|
||||
|
||||
foreach ( $fields as $meta_key => $args ) {
|
||||
$name = $args['name'];
|
||||
if ( ! array_key_exists( $name, $meta ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $meta[ $name ];
|
||||
|
||||
/*
|
||||
* A null value means reset the field, which is essentially deleting it
|
||||
* from the database and then relying on the default value.
|
||||
*
|
||||
* Non-single meta can also be removed by passing an empty array.
|
||||
*/
|
||||
if ( is_null( $value ) || ( array() === $value && ! $args['single'] ) ) {
|
||||
$args = $this->get_registered_fields()[ $meta_key ];
|
||||
|
||||
if ( $args['single'] ) {
|
||||
$current = get_metadata( $this->get_meta_type(), $object_id, $meta_key, true );
|
||||
|
||||
if ( is_wp_error( rest_validate_value_from_schema( $current, $args['schema'] ) ) ) {
|
||||
$error->add(
|
||||
'rest_invalid_stored_value',
|
||||
/* translators: %s: Custom field key. */
|
||||
sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$result = $this->delete_meta_value( $object_id, $meta_key, $name );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$error->merge_from( $result );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $args['single'] && is_array( $value ) && count( array_filter( $value, 'is_null' ) ) ) {
|
||||
$error->add(
|
||||
'rest_invalid_stored_value',
|
||||
/* translators: %s: Custom field key. */
|
||||
sprintf( __( 'The %s property has an invalid stored value, and cannot be updated to null.' ), $name ),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_valid = rest_validate_value_from_schema( $value, $args['schema'], 'meta.' . $name );
|
||||
if ( is_wp_error( $is_valid ) ) {
|
||||
$is_valid->add_data( array( 'status' => 400 ) );
|
||||
$error->merge_from( $is_valid );
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = rest_sanitize_value_from_schema( $value, $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 ) ) {
|
||||
$error->merge_from( $result );
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $error->has_errors() ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
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 true|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 ( null === get_metadata_raw( $meta_type, $object_id, wp_slash( $meta_key ) ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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 true|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_values = get_metadata( $meta_type, $object_id, $meta_key, false );
|
||||
$subtype = get_object_subtype( $meta_type, $object_id );
|
||||
|
||||
if ( ! is_array( $current_values ) ) {
|
||||
$current_values = array();
|
||||
}
|
||||
|
||||
$to_remove = $current_values;
|
||||
$to_add = $values;
|
||||
|
||||
foreach ( $to_add as $add_key => $value ) {
|
||||
$remove_keys = array_keys(
|
||||
array_filter(
|
||||
$current_values,
|
||||
function ( $stored_value ) use ( $meta_key, $subtype, $value ) {
|
||||
return $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $value );
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
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. Otherwise,
|
||||
* `delete_metadata` will return false for subsequent calls of the same value.
|
||||
* Use serialization to produce a predictable string that can be used by array_unique.
|
||||
*/
|
||||
$to_remove = array_map( 'maybe_unserialize', array_unique( array_map( 'maybe_serialize', $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',
|
||||
/* translators: %s: Custom field key. */
|
||||
sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ),
|
||||
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',
|
||||
/* translators: %s: Custom field key. */
|
||||
sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ),
|
||||
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 true|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();
|
||||
|
||||
// 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 ( is_array( $old_value ) && 1 === count( $old_value )
|
||||
&& $this->is_meta_value_same_as_stored_value( $meta_key, $subtype, $old_value[0], $value )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! update_metadata( $meta_type, $object_id, wp_slash( $meta_key ), wp_slash( $value ) ) ) {
|
||||
return new WP_Error(
|
||||
'rest_meta_database_error',
|
||||
/* translators: %s: Custom field key. */
|
||||
sprintf( __( 'Could not update the meta value of %s in database.' ), $meta_key ),
|
||||
array(
|
||||
'key' => $name,
|
||||
'status' => WP_Http::INTERNAL_SERVER_ERROR,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user provided value is equivalent to a stored value for the given meta key.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $meta_key The meta key being checked.
|
||||
* @param string $subtype The object subtype.
|
||||
* @param mixed $stored_value The currently stored value retrieved from get_metadata().
|
||||
* @param mixed $user_value The value provided by the user.
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_meta_value_same_as_stored_value( $meta_key, $subtype, $stored_value, $user_value ) {
|
||||
$args = $this->get_registered_fields()[ $meta_key ];
|
||||
$sanitized = sanitize_meta( $meta_key, $user_value, $this->get_meta_type(), $subtype );
|
||||
|
||||
if ( in_array( $args['type'], array( 'string', 'number', 'integer', 'boolean' ), true ) ) {
|
||||
// The return value of get_metadata will always be a string for scalar types.
|
||||
$sanitized = (string) $sanitized;
|
||||
}
|
||||
|
||||
return $sanitized === $stored_value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ( null === $rest_args['schema']['default'] ) {
|
||||
$rest_args['schema']['default'] = static::get_empty_value_for_type( $type );
|
||||
}
|
||||
|
||||
$rest_args['schema'] = rest_default_additional_properties_to_false( $rest_args['schema'] );
|
||||
|
||||
if ( ! in_array( $type, array( 'string', 'boolean', 'integer', 'number', 'array', 'object' ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $rest_args['single'] ) ) {
|
||||
$rest_args['schema'] = array(
|
||||
'type' => 'array',
|
||||
'items' => $rest_args['schema'],
|
||||
);
|
||||
}
|
||||
|
||||
$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 ) {
|
||||
if ( $args['single'] ) {
|
||||
$schema = $args['schema'];
|
||||
} else {
|
||||
$schema = $args['schema']['items'];
|
||||
}
|
||||
|
||||
if ( '' === $value && in_array( $schema['type'], array( 'boolean', 'integer', 'number' ), true ) ) {
|
||||
$value = static::get_empty_value_for_type( $schema['type'] );
|
||||
}
|
||||
|
||||
if ( is_wp_error( rest_validate_value_from_schema( $value, $schema ) ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return rest_sanitize_value_from_schema( $value, $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 array|false The meta array, if valid, false otherwise.
|
||||
*/
|
||||
public function check_meta_is_array( $value, $request, $param ) {
|
||||
if ( ! is_array( $value ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively add additionalProperties = false to all objects in a schema if no additionalProperties setting
|
||||
* is specified.
|
||||
*
|
||||
* This is needed to restrict properties of objects in meta values to only
|
||||
* registered items, as the REST API will allow additional properties by
|
||||
* default.
|
||||
*
|
||||
* @since 5.3.0
|
||||
* @deprecated 5.6.0 Use rest_default_additional_properties_to_false() instead.
|
||||
*
|
||||
* @param array $schema The schema array.
|
||||
* @return array
|
||||
*/
|
||||
protected function default_additional_properties_to_false( $schema ) {
|
||||
_deprecated_function( __METHOD__, '5.6.0', 'rest_default_additional_properties_to_false()' );
|
||||
|
||||
return rest_default_additional_properties_to_false( $schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the empty value for a schema type.
|
||||
*
|
||||
* @since 5.3.0
|
||||
*
|
||||
* @param string $type The schema type.
|
||||
* @return mixed
|
||||
*/
|
||||
protected static function get_empty_value_for_type( $type ) {
|
||||
switch ( $type ) {
|
||||
case 'string':
|
||||
return '';
|
||||
case 'boolean':
|
||||
return false;
|
||||
case 'integer':
|
||||
return 0;
|
||||
case 'number':
|
||||
return 0.0;
|
||||
case 'array':
|
||||
case 'object':
|
||||
return array();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 post meta type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The meta type.
|
||||
*/
|
||||
protected function get_meta_type() {
|
||||
return 'post';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post 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 term meta type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The meta type.
|
||||
*/
|
||||
protected function get_meta_type() {
|
||||
return 'term';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the term 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 user meta type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string The user meta type.
|
||||
*/
|
||||
protected function get_meta_type() {
|
||||
return 'user';
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the user 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,138 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Post_Format_Search_Handler class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.6.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class representing a search handler for post formats in the REST API.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @see WP_REST_Search_Handler
|
||||
*/
|
||||
class WP_REST_Post_Format_Search_Handler extends WP_REST_Search_Handler {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->type = 'post-format';
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches the post formats for a given search request.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full REST request.
|
||||
* @return array {
|
||||
* Associative array containing found IDs and total count for the matching search results.
|
||||
*
|
||||
* @type string[] $ids Array containing slugs for the matching post formats.
|
||||
* @type int $total Total count for the matching search results.
|
||||
* }
|
||||
*/
|
||||
public function search_items( WP_REST_Request $request ) {
|
||||
$format_strings = get_post_format_strings();
|
||||
$format_slugs = array_keys( $format_strings );
|
||||
|
||||
$query_args = array();
|
||||
|
||||
if ( ! empty( $request['search'] ) ) {
|
||||
$query_args['search'] = $request['search'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the query arguments for a REST API post format search request.
|
||||
*
|
||||
* Enables adding extra arguments or setting defaults for a post format search request.
|
||||
*
|
||||
* @since 5.6.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_format_search_query', $query_args, $request );
|
||||
|
||||
$found_ids = array();
|
||||
foreach ( $format_slugs as $index => $format_slug ) {
|
||||
if ( ! empty( $query_args['search'] ) ) {
|
||||
$format_string = get_post_format_string( $format_slug );
|
||||
$format_slug_match = stripos( $format_slug, $query_args['search'] ) !== false;
|
||||
$format_string_match = stripos( $format_string, $query_args['search'] ) !== false;
|
||||
if ( ! $format_slug_match && ! $format_string_match ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$format_link = get_post_format_link( $format_slug );
|
||||
if ( $format_link ) {
|
||||
$found_ids[] = $format_slug;
|
||||
}
|
||||
}
|
||||
|
||||
$page = (int) $request['page'];
|
||||
$per_page = (int) $request['per_page'];
|
||||
|
||||
return array(
|
||||
self::RESULT_IDS => array_slice( $found_ids, ( $page - 1 ) * $per_page, $per_page ),
|
||||
self::RESULT_TOTAL => count( $found_ids ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the search result for a given post format.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param string $id Item ID, the post format slug.
|
||||
* @param array $fields Fields to include for the item.
|
||||
* @return array {
|
||||
* Associative array containing fields for the post format based on the `$fields` parameter.
|
||||
*
|
||||
* @type string $id Optional. Post format slug.
|
||||
* @type string $title Optional. Post format name.
|
||||
* @type string $url Optional. Post format permalink URL.
|
||||
* @type string $type Optional. String 'post-format'.
|
||||
*}
|
||||
*/
|
||||
public function prepare_item( $id, array $fields ) {
|
||||
$data = array();
|
||||
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_ID ] = $id;
|
||||
}
|
||||
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_TITLE ] = get_post_format_string( $id );
|
||||
}
|
||||
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_URL ] = get_post_format_link( $id );
|
||||
}
|
||||
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_TYPE ] = $this->type;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the search result.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param string $id Item ID, the post format slug.
|
||||
* @return array Links for the given item.
|
||||
*/
|
||||
public function prepare_item_links( $id ) {
|
||||
return array();
|
||||
}
|
||||
}
|
@ -0,0 +1,215 @@
|
||||
<?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
|
||||
*
|
||||
* @see WP_REST_Search_Handler
|
||||
*/
|
||||
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 posts for a given search request.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full REST request.
|
||||
* @return array {
|
||||
* Associative array containing found IDs and total count for the matching search results.
|
||||
*
|
||||
* @type int[] $ids Array containing the matching post IDs.
|
||||
* @type int $total 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,
|
||||
);
|
||||
|
||||
if ( ! empty( $request['search'] ) ) {
|
||||
$query_args['s'] = $request['search'];
|
||||
}
|
||||
|
||||
if ( ! empty( $request['exclude'] ) ) {
|
||||
$query_args['post__not_in'] = $request['exclude'];
|
||||
}
|
||||
|
||||
if ( ! empty( $request['include'] ) ) {
|
||||
$query_args['post__in'] = $request['include'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the query arguments for a REST API post 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();
|
||||
$posts = $query->query( $query_args );
|
||||
// Querying the whole post object will warm the object cache, avoiding an extra query per result.
|
||||
$found_ids = wp_list_pluck( $posts, 'ID' );
|
||||
$total = $query->found_posts;
|
||||
|
||||
return array(
|
||||
self::RESULT_IDS => $found_ids,
|
||||
self::RESULT_TOTAL => $total,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the search result for a given post ID.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param int $id Post ID.
|
||||
* @param array $fields Fields to include for the post.
|
||||
* @return array {
|
||||
* Associative array containing fields for the post based on the `$fields` parameter.
|
||||
*
|
||||
* @type int $id Optional. Post ID.
|
||||
* @type string $title Optional. Post title.
|
||||
* @type string $url Optional. Post permalink URL.
|
||||
* @type string $type Optional. Post type.
|
||||
* }
|
||||
*/
|
||||
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 = rest_get_route_for_post( $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
|
||||
* @deprecated 5.5.0 Use rest_get_route_for_post()
|
||||
* @see rest_get_route_for_post()
|
||||
*
|
||||
* @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 ) {
|
||||
_deprecated_function( __METHOD__, '5.5.0', 'rest_get_route_for_post()' );
|
||||
|
||||
return rest_get_route_for_post( $post );
|
||||
}
|
||||
}
|
100
wp-includes/rest-api/search/class-wp-rest-search-handler.php
Normal file
100
wp-includes/rest-api/search/class-wp-rest-search-handler.php
Normal file
@ -0,0 +1,100 @@
|
||||
<?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
|
||||
*/
|
||||
#[AllowDynamicProperties]
|
||||
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 string[]
|
||||
*/
|
||||
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 string[] 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
|
||||
* @since 5.6.0 The `$id` parameter can accept a string.
|
||||
*
|
||||
* @param int|string $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
|
||||
* @since 5.6.0 The `$id` parameter can accept a string.
|
||||
*
|
||||
* @param int|string $id Item ID.
|
||||
* @return array Links for the given item.
|
||||
*/
|
||||
abstract public function prepare_item_links( $id );
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Term_Search_Handler class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.6.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class representing a search handler for terms in the REST API.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @see WP_REST_Search_Handler
|
||||
*/
|
||||
class WP_REST_Term_Search_Handler extends WP_REST_Search_Handler {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->type = 'term';
|
||||
|
||||
$this->subtypes = array_values(
|
||||
get_taxonomies(
|
||||
array(
|
||||
'public' => true,
|
||||
'show_in_rest' => true,
|
||||
),
|
||||
'names'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Searches terms for a given search request.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full REST request.
|
||||
* @return array {
|
||||
* Associative array containing found IDs and total count for the matching search results.
|
||||
*
|
||||
* @type int[] $ids Found term IDs.
|
||||
* @type string|int|WP_Error $total Numeric string containing the number of terms in that
|
||||
* taxonomy, 0 if there are no results, or WP_Error if
|
||||
* the requested taxonomy does not exist.
|
||||
* }
|
||||
*/
|
||||
public function search_items( WP_REST_Request $request ) {
|
||||
$taxonomies = $request[ WP_REST_Search_Controller::PROP_SUBTYPE ];
|
||||
if ( in_array( WP_REST_Search_Controller::TYPE_ANY, $taxonomies, true ) ) {
|
||||
$taxonomies = $this->subtypes;
|
||||
}
|
||||
|
||||
$page = (int) $request['page'];
|
||||
$per_page = (int) $request['per_page'];
|
||||
|
||||
$query_args = array(
|
||||
'taxonomy' => $taxonomies,
|
||||
'hide_empty' => false,
|
||||
'offset' => ( $page - 1 ) * $per_page,
|
||||
'number' => $per_page,
|
||||
);
|
||||
|
||||
if ( ! empty( $request['search'] ) ) {
|
||||
$query_args['search'] = $request['search'];
|
||||
}
|
||||
|
||||
if ( ! empty( $request['exclude'] ) ) {
|
||||
$query_args['exclude'] = $request['exclude'];
|
||||
}
|
||||
|
||||
if ( ! empty( $request['include'] ) ) {
|
||||
$query_args['include'] = $request['include'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the query arguments for a REST API term search request.
|
||||
*
|
||||
* Enables adding extra arguments or setting defaults for a term search request.
|
||||
*
|
||||
* @since 5.6.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_term_search_query', $query_args, $request );
|
||||
|
||||
$query = new WP_Term_Query();
|
||||
$found_terms = $query->query( $query_args );
|
||||
$found_ids = wp_list_pluck( $found_terms, 'term_id' );
|
||||
|
||||
unset( $query_args['offset'], $query_args['number'] );
|
||||
|
||||
$total = wp_count_terms( $query_args );
|
||||
|
||||
// wp_count_terms() can return a falsey value when the term has no children.
|
||||
if ( ! $total ) {
|
||||
$total = 0;
|
||||
}
|
||||
|
||||
return array(
|
||||
self::RESULT_IDS => $found_ids,
|
||||
self::RESULT_TOTAL => $total,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the search result for a given term ID.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param int $id Term ID.
|
||||
* @param array $fields Fields to include for the term.
|
||||
* @return array {
|
||||
* Associative array containing fields for the term based on the `$fields` parameter.
|
||||
*
|
||||
* @type int $id Optional. Term ID.
|
||||
* @type string $title Optional. Term name.
|
||||
* @type string $url Optional. Term permalink URL.
|
||||
* @type string $type Optional. Term taxonomy name.
|
||||
* }
|
||||
*/
|
||||
public function prepare_item( $id, array $fields ) {
|
||||
$term = get_term( $id );
|
||||
|
||||
$data = array();
|
||||
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_ID, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_ID ] = (int) $id;
|
||||
}
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_TITLE, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_TITLE ] = $term->name;
|
||||
}
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_URL, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_URL ] = get_term_link( $id );
|
||||
}
|
||||
if ( in_array( WP_REST_Search_Controller::PROP_TYPE, $fields, true ) ) {
|
||||
$data[ WP_REST_Search_Controller::PROP_TYPE ] = $term->taxonomy;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the search result of a given ID.
|
||||
*
|
||||
* @since 5.6.0
|
||||
*
|
||||
* @param int $id Item ID.
|
||||
* @return array[] Array of link arrays for the given item.
|
||||
*/
|
||||
public function prepare_item_links( $id ) {
|
||||
$term = get_term( $id );
|
||||
|
||||
$links = array();
|
||||
|
||||
$item_route = rest_get_route_for_term( $term );
|
||||
if ( $item_route ) {
|
||||
$links['self'] = array(
|
||||
'href' => rest_url( $item_route ),
|
||||
'embeddable' => true,
|
||||
);
|
||||
}
|
||||
|
||||
$links['about'] = array(
|
||||
'href' => rest_url( sprintf( 'wp/v2/taxonomies/%s', $term->taxonomy ) ),
|
||||
);
|
||||
|
||||
return $links;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user