initial commit
This commit is contained in:
@@ -0,0 +1,836 @@
|
||||
<?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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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 one 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 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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
$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 );
|
||||
$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() && ! 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.
|
||||
*
|
||||
* @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,453 @@
|
||||
<?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_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;
|
||||
$this->revisions_controller = new WP_REST_Revisions_Controller( $parent_post_type );
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'autosaves';
|
||||
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers 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( 'DOING_AUTOSAVE' ) ) {
|
||||
define( 'DOING_AUTOSAVE', true );
|
||||
}
|
||||
|
||||
$post = get_post( $request['id'] );
|
||||
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
$prepared_post = $this->parent_controller->prepare_item_for_database( $request );
|
||||
$prepared_post->ID = $post->ID;
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if ( ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status ) && $post->post_author == $user_id ) {
|
||||
// Draft posts for the same author: autosaving updates the post and does not create a revision.
|
||||
// Convert the post object to an array and add slashes, wp_update_post() expects escaped array.
|
||||
$autosave_id = wp_update_post( wp_slash( (array) $prepared_post ), true );
|
||||
} else {
|
||||
// Non-draft posts: create or update the post autosave.
|
||||
$autosave_id = $this->create_post_autosave( (array) $prepared_post );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $autosave_id ) ) {
|
||||
return $autosave_id;
|
||||
}
|
||||
|
||||
$autosave = get_post( $autosave_id );
|
||||
$request->set_param( 'context', 'edit' );
|
||||
|
||||
$response = $this->prepare_item_for_response( $autosave, $request );
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the autosave, if the ID is valid.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full 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 ( false !== strpos( $revision->post_name, "{$parent_id}-autosave" ) ) {
|
||||
$data = $this->prepare_item_for_response( $revision, $request );
|
||||
$response[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
}
|
||||
|
||||
return rest_ensure_response( $response );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieves the autosave's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
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
|
||||
*
|
||||
* @param array $post_data Associative array containing the post data.
|
||||
* @return mixed The autosave revision ID or WP_Error.
|
||||
*/
|
||||
public function create_post_autosave( $post_data ) {
|
||||
|
||||
$post_id = (int) $post_data['ID'];
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( is_wp_error( $post ) ) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
// Store one autosave per author. If there is already an autosave, overwrite it.
|
||||
$old_autosave = wp_get_post_autosave( $post_id, $user_id );
|
||||
|
||||
if ( $old_autosave ) {
|
||||
$new_autosave = _wp_post_revision_data( $post_data, true );
|
||||
$new_autosave['ID'] = $old_autosave->ID;
|
||||
$new_autosave['post_author'] = $user_id;
|
||||
|
||||
// If the new autosave has the same content as the post, delete the autosave.
|
||||
$autosave_is_different = false;
|
||||
|
||||
foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
|
||||
if ( normalize_whitespace( $new_autosave[ $field ] ) !== normalize_whitespace( $post->$field ) ) {
|
||||
$autosave_is_different = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $autosave_is_different ) {
|
||||
wp_delete_post_revision( $old_autosave->ID );
|
||||
return new WP_Error(
|
||||
'rest_autosave_no_changes',
|
||||
__( 'There is nothing to save. The autosave and the post content are the same.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
/** This filter is documented in wp-admin/post.php */
|
||||
do_action( 'wp_creating_autosave', $new_autosave );
|
||||
|
||||
// wp_update_post() expects escaped array.
|
||||
return wp_update_post( wp_slash( $new_autosave ) );
|
||||
}
|
||||
|
||||
// Create the new autosave as a special post revision.
|
||||
return _wp_put_post_revision( $post_data, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the revision for the REST response.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_Post $post Post revision object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $post, $request ) {
|
||||
|
||||
$response = $this->revisions_controller->prepare_item_for_response( $post, $request );
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
if ( in_array( 'preview_link', $fields, true ) ) {
|
||||
$parent_id = wp_is_post_autosave( $post );
|
||||
$preview_post_id = false === $parent_id ? $post->ID : $parent_id;
|
||||
$preview_query_args = array();
|
||||
|
||||
if ( false !== $parent_id ) {
|
||||
$preview_query_args['preview_id'] = $parent_id;
|
||||
$preview_query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $parent_id );
|
||||
}
|
||||
|
||||
$response->data['preview_link'] = get_preview_post_link( $preview_post_id, $preview_query_args );
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$response->data = $this->add_additional_fields_to_object( $response->data, $request );
|
||||
$response->data = $this->filter_response_by_context( $response->data, $context );
|
||||
|
||||
/**
|
||||
* Filters a revision returned from the 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,319 @@
|
||||
<?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 repsonse.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param array $plugin 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( $plugin, $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 );
|
||||
$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' => 'integer',
|
||||
'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' => 'string',
|
||||
'context' => array( 'view' ),
|
||||
),
|
||||
'author_block_rating' => array(
|
||||
'description' => __( 'The average rating of blocks published by the same author.' ),
|
||||
'type' => 'integer',
|
||||
'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, in fuzzy human readable format.' ),
|
||||
'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,220 @@
|
||||
<?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
|
||||
*
|
||||
* @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 ( 0 < $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( ! $post || ! current_user_can( 'edit_post', $post->ID ) ) {
|
||||
return new WP_Error(
|
||||
'block_cannot_read',
|
||||
__( 'Sorry, you are not allowed to read blocks of this post.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if ( ! current_user_can( 'edit_posts' ) ) {
|
||||
return new WP_Error(
|
||||
'block_cannot_read',
|
||||
__( 'Sorry, you are not allowed to read blocks as this user.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns block output from block's registered render_callback.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
global $post;
|
||||
|
||||
$post_id = isset( $request['post_id'] ) ? (int) $request['post_id'] : 0;
|
||||
|
||||
if ( 0 < $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
|
||||
// Set up postdata since this will be needed if post_id was set.
|
||||
setup_postdata( $post );
|
||||
}
|
||||
|
||||
$registry = WP_Block_Type_Registry::get_instance();
|
||||
$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,665 @@
|
||||
<?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 {
|
||||
|
||||
/**
|
||||
* 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
|
||||
*
|
||||
* @param WP_Block_Type $block_type 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( $block_type, $request ) {
|
||||
|
||||
$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();
|
||||
$extra_fields = array(
|
||||
'api_version',
|
||||
'name',
|
||||
'title',
|
||||
'description',
|
||||
'icon',
|
||||
'category',
|
||||
'keywords',
|
||||
'parent',
|
||||
'provides_context',
|
||||
'uses_context',
|
||||
'supports',
|
||||
'styles',
|
||||
'textdomain',
|
||||
'example',
|
||||
'editor_script',
|
||||
'script',
|
||||
'editor_style',
|
||||
'style',
|
||||
'variations',
|
||||
);
|
||||
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;
|
||||
} 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 );
|
||||
|
||||
$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
|
||||
*
|
||||
* @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',
|
||||
),
|
||||
'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,
|
||||
);
|
||||
|
||||
$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',
|
||||
'default' => '',
|
||||
'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,
|
||||
),
|
||||
'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' => array(
|
||||
'description' => __( 'Editor script handle.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'script' => array(
|
||||
'description' => __( 'Public facing script handle.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'editor_style' => array(
|
||||
'description' => __( 'Editor style handle.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'style' => array(
|
||||
'description' => __( 'Public facing style handle.' ),
|
||||
'type' => array( 'string', 'null' ),
|
||||
'default' => null,
|
||||
'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',
|
||||
),
|
||||
'default' => null,
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'keywords' => $keywords_definition,
|
||||
'example' => $example_definition,
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
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,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Reusable blocks REST API: WP_REST_Blocks_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Controller which provides a REST endpoint for the editor to read, create,
|
||||
* edit and delete reusable blocks. Blocks are stored as posts with the wp_block
|
||||
* post type.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see WP_REST_Posts_Controller
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Blocks_Controller extends WP_REST_Posts_Controller {
|
||||
|
||||
/**
|
||||
* Checks if a block can be read.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_Post $post Post object that backs the block.
|
||||
* @return bool Whether the block 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
|
||||
*
|
||||
* @param array $data Response data to fiter.
|
||||
* @param string $context Context defined in the schema.
|
||||
* @return array Filtered response.
|
||||
*/
|
||||
public function filter_response_by_context( $data, $context ) {
|
||||
$data = parent::filter_response_by_context( $data, $context );
|
||||
|
||||
/*
|
||||
* Remove `title.rendered` and `content.rendered` from the response. It
|
||||
* doesn't make sense for a reusable block to have rendered content on its
|
||||
* own, since rendering a block requires it to be inside a post or a page.
|
||||
*/
|
||||
unset( $data['title']['rendered'] );
|
||||
unset( $data['content']['rendered'] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the block's schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
// Do not cache this schema because all properties are derived from parent controller.
|
||||
$schema = parent::get_item_schema();
|
||||
|
||||
/*
|
||||
* Allow all contexts to access `title.raw` and `content.raw`. Clients always
|
||||
* need the raw markup of a reusable block to do anything useful, e.g. parse
|
||||
* it or display it in an editor.
|
||||
*/
|
||||
$schema['properties']['title']['properties']['raw']['context'] = array( 'view', 'edit' );
|
||||
$schema['properties']['content']['properties']['raw']['context'] = array( 'view', 'edit' );
|
||||
|
||||
/*
|
||||
* Remove `title.rendered` and `content.rendered` from the schema. It doesn’t
|
||||
* make sense for a reusable block to have rendered content on its own, since
|
||||
* rendering a block requires it to be inside a post or a page.
|
||||
*/
|
||||
unset( $schema['properties']['title']['properties']['rendered'] );
|
||||
unset( $schema['properties']['content']['properties']['rendered'] );
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,650 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core base controller for managing and interacting with REST API items.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*/
|
||||
abstract class WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* The namespace of this controller's route.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var string
|
||||
*/
|
||||
protected $namespace;
|
||||
|
||||
/**
|
||||
* The base of this controller's route.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var string
|
||||
*/
|
||||
protected $rest_base;
|
||||
|
||||
/**
|
||||
* 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 $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 ) {
|
||||
|
||||
$schema = $this->get_item_schema();
|
||||
|
||||
return rest_filter_response_by_context( $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 $prepared 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( $prepared, $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;
|
||||
}
|
||||
|
||||
$prepared[ $field_name ] = call_user_func( $field_options['get_callback'], $prepared, $field_name, $request, $this->get_object_type() );
|
||||
}
|
||||
|
||||
return $prepared;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the values of additional fields added to a data object.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param object $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( $object, $request ) {
|
||||
$additional_fields = $this->get_additional_fields();
|
||||
|
||||
foreach ( $additional_fields as $field_name => $field_options ) {
|
||||
if ( ! $field_options['update_callback'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't run the update callbacks if the data wasn't passed in the request.
|
||||
if ( ! isset( $request[ $field_name ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$result = call_user_func( $field_options['update_callback'], $request[ $field_name ], $object, $field_name, $request, $this->get_object_type() );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the schema from additional fields to a schema array.
|
||||
*
|
||||
* The type of object is inferred from the passed schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param array $schema Schema array.
|
||||
* @return array Modified Schema array.
|
||||
*/
|
||||
protected function add_additional_fields_schema( $schema ) {
|
||||
if ( empty( $schema['title'] ) ) {
|
||||
return $schema;
|
||||
}
|
||||
|
||||
// Can't use $this->get_object_type otherwise we cause an inf loop.
|
||||
$object_type = $schema['title'];
|
||||
|
||||
$additional_fields = $this->get_additional_fields( $object_type );
|
||||
|
||||
foreach ( $additional_fields as $field_name => $field_options ) {
|
||||
if ( ! $field_options['schema'] ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$schema['properties'][ $field_name ] = $field_options['schema'];
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all of the registered additional fields for a given object-type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param string $object_type Optional. The object type.
|
||||
* @return array Registered additional fields (if any), empty array if none or if the object type could
|
||||
* not be inferred.
|
||||
*/
|
||||
protected function get_additional_fields( $object_type = null ) {
|
||||
|
||||
if ( ! $object_type ) {
|
||||
$object_type = $this->get_object_type();
|
||||
}
|
||||
|
||||
if ( ! $object_type ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
global $wp_rest_additional_fields;
|
||||
|
||||
if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return $wp_rest_additional_fields[ $object_type ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the object type this controller is responsible for managing.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return string Object type for the controller.
|
||||
*/
|
||||
protected function get_object_type() {
|
||||
$schema = $this->get_item_schema();
|
||||
|
||||
if ( ! $schema || ! isset( $schema['title'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $schema['title'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of fields to be included on the response.
|
||||
*
|
||||
* Included fields are based on item schema and `_fields=` request argument.
|
||||
*
|
||||
* @since 4.9.6
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return 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 );
|
||||
|
||||
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,
|
||||
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,344 @@
|
||||
<?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 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
|
||||
*
|
||||
* @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';
|
||||
|
||||
$query_args = array(
|
||||
'locale' => get_user_locale(),
|
||||
'wp-version' => $wp_version,
|
||||
);
|
||||
|
||||
$category_id = $request['category'];
|
||||
$keyword_id = $request['keyword'];
|
||||
$search_term = $request['search'];
|
||||
|
||||
if ( $category_id ) {
|
||||
$query_args['pattern-categories'] = $category_id;
|
||||
}
|
||||
|
||||
if ( $keyword_id ) {
|
||||
$query_args['pattern-keywords'] = $keyword_id;
|
||||
}
|
||||
|
||||
if ( $search_term ) {
|
||||
$query_args['search'] = $search_term;
|
||||
}
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
$transient_key = 'wp_remote_block_patterns_' . md5( implode( '-', $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 = add_query_arg(
|
||||
array_map( 'rawurlencode', $query_args ),
|
||||
'http://api.wordpress.org/patterns/1.0/'
|
||||
);
|
||||
|
||||
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 pattern before it's output in an API response.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param object $raw_pattern A 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( $raw_pattern, $request ) {
|
||||
$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 ),
|
||||
);
|
||||
|
||||
$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 pattern.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param object $raw_pattern The unprepared pattern.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_block_pattern', $response, $raw_pattern, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the pattern'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' => 'pattern-directory-item',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'The pattern ID.' ),
|
||||
'type' => 'integer',
|
||||
'minimum' => 1,
|
||||
'context' => array( 'view', 'embed' ),
|
||||
),
|
||||
|
||||
'title' => array(
|
||||
'description' => __( 'The pattern title, in human readable format.' ),
|
||||
'type' => 'string',
|
||||
'minLength' => 1,
|
||||
'context' => array( 'view', 'embed' ),
|
||||
),
|
||||
|
||||
'content' => array(
|
||||
'description' => __( 'The pattern content.' ),
|
||||
'type' => 'string',
|
||||
'minLength' => 1,
|
||||
'context' => array( 'view', 'embed' ),
|
||||
),
|
||||
|
||||
'categories' => array(
|
||||
'description' => __( "The pattern's category slugs." ),
|
||||
'type' => 'array',
|
||||
'uniqueItems' => true,
|
||||
'items' => array( 'type' => 'string' ),
|
||||
'context' => array( 'view', 'embed' ),
|
||||
),
|
||||
|
||||
'keywords' => array(
|
||||
'description' => __( "The pattern's keywords." ),
|
||||
'type' => 'array',
|
||||
'uniqueItems' => true,
|
||||
'items' => array( 'type' => 'string' ),
|
||||
'context' => array( 'view', 'embed' ),
|
||||
),
|
||||
|
||||
'description' => array(
|
||||
'description' => __( 'A description of the pattern.' ),
|
||||
'type' => 'string',
|
||||
'minLength' => 1,
|
||||
'context' => array( 'view', 'embed' ),
|
||||
),
|
||||
|
||||
'viewport_width' => array(
|
||||
'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'view', 'embed' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the search params for the patterns collection.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$query_params = parent::get_collection_params();
|
||||
|
||||
// Pagination is not supported.
|
||||
unset( $query_params['page'] );
|
||||
unset( $query_params['per_page'] );
|
||||
|
||||
$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,
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter collection parameters for the 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 );
|
||||
}
|
||||
}
|
@@ -0,0 +1,975 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Plugins_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.5.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class to access plugins via the REST API.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Plugins_Controller extends WP_REST_Controller {
|
||||
|
||||
const PATTERN = '[^.\/]+(?:\/[^.\/]+)?';
|
||||
|
||||
/**
|
||||
* Plugins controller constructor.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'plugins';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for the plugins controller.
|
||||
*
|
||||
* @since 5.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(),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'create_item' ),
|
||||
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
||||
'args' => array(
|
||||
'slug' => array(
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
'description' => __( 'WordPress.org plugin directory slug.' ),
|
||||
'pattern' => '[\w\-]+',
|
||||
),
|
||||
'status' => array(
|
||||
'description' => __( 'The plugin activation status.' ),
|
||||
'type' => 'string',
|
||||
'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
|
||||
'default' => 'inactive',
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base . '/(?P<plugin>' . self::PATTERN . ')',
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_item' ),
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
),
|
||||
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(
|
||||
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
|
||||
'plugin' => array(
|
||||
'type' => 'string',
|
||||
'pattern' => self::PATTERN,
|
||||
'validate_callback' => array( $this, 'validate_plugin_param' ),
|
||||
'sanitize_callback' => array( $this, 'sanitize_plugin_param' ),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get plugins.
|
||||
*
|
||||
* @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 ) {
|
||||
if ( ! current_user_can( 'activate_plugins' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_view_plugins',
|
||||
__( 'Sorry, you are not allowed to manage plugins for this site.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a collection of plugins.
|
||||
*
|
||||
* @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.php';
|
||||
|
||||
$plugins = array();
|
||||
|
||||
foreach ( get_plugins() as $file => $data ) {
|
||||
if ( is_wp_error( $this->check_read_permission( $file ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$data['_file'] = $file;
|
||||
|
||||
if ( ! $this->does_plugin_match_request( $request, $data ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$plugins[] = $this->prepare_response_for_collection( $this->prepare_item_for_response( $data, $request ) );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $plugins );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get a specific plugin.
|
||||
*
|
||||
* @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 ) {
|
||||
if ( ! current_user_can( 'activate_plugins' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_view_plugin',
|
||||
__( 'Sorry, you are not allowed to manage plugins for this site.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
$can_read = $this->check_read_permission( $request['plugin'] );
|
||||
|
||||
if ( is_wp_error( $can_read ) ) {
|
||||
return $can_read;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves one plugin from the site.
|
||||
*
|
||||
* @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 ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$data = $this->get_plugin_data( $request['plugin'] );
|
||||
|
||||
if ( is_wp_error( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response( $data, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given plugin can be viewed by the current user.
|
||||
*
|
||||
* On multisite, this hides non-active network only plugins if the user does not have permission
|
||||
* to manage network plugins.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $plugin The plugin file to check.
|
||||
* @return true|WP_Error True if can read, a WP_Error instance otherwise.
|
||||
*/
|
||||
protected function check_read_permission( $plugin ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
if ( ! $this->is_plugin_installed( $plugin ) ) {
|
||||
return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
if ( ! is_multisite() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( ! is_network_only_plugin( $plugin ) || is_plugin_active( $plugin ) || current_user_can( 'manage_network_plugins' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
'rest_cannot_view_plugin',
|
||||
__( 'Sorry, you are not allowed to manage this plugin.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to upload plugins.
|
||||
*
|
||||
* @since 5.5.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 ) {
|
||||
if ( ! current_user_can( 'install_plugins' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_install_plugin',
|
||||
__( 'Sorry, you are not allowed to install plugins on this site.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'inactive' !== $request['status'] && ! current_user_can( 'activate_plugins' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_activate_plugin',
|
||||
__( 'Sorry, you are not allowed to activate plugins.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads a plugin and optionally activates it.
|
||||
*
|
||||
* @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 create_item( $request ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||
|
||||
$slug = $request['slug'];
|
||||
|
||||
// Verify filesystem is accessible first.
|
||||
$filesystem_available = $this->is_filesystem_available();
|
||||
if ( is_wp_error( $filesystem_available ) ) {
|
||||
return $filesystem_available;
|
||||
}
|
||||
|
||||
$api = plugins_api(
|
||||
'plugin_information',
|
||||
array(
|
||||
'slug' => $slug,
|
||||
'fields' => array(
|
||||
'sections' => false,
|
||||
'language_packs' => true,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $api ) ) {
|
||||
if ( false !== strpos( $api->get_error_message(), 'Plugin not found.' ) ) {
|
||||
$api->add_data( array( 'status' => 404 ) );
|
||||
} else {
|
||||
$api->add_data( array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
return $api;
|
||||
}
|
||||
|
||||
$skin = new WP_Ajax_Upgrader_Skin();
|
||||
$upgrader = new Plugin_Upgrader( $skin );
|
||||
|
||||
$result = $upgrader->install( $api->download_link );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$result->add_data( array( 'status' => 500 ) );
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
// This should be the same as $result above.
|
||||
if ( is_wp_error( $skin->result ) ) {
|
||||
$skin->result->add_data( array( 'status' => 500 ) );
|
||||
|
||||
return $skin->result;
|
||||
}
|
||||
|
||||
if ( $skin->get_errors()->has_errors() ) {
|
||||
$error = $skin->get_errors();
|
||||
$error->add_data( array( 'status' => 500 ) );
|
||||
|
||||
return $error;
|
||||
}
|
||||
|
||||
if ( is_null( $result ) ) {
|
||||
global $wp_filesystem;
|
||||
// Pass through the error from WP_Filesystem if one was raised.
|
||||
if ( $wp_filesystem instanceof WP_Filesystem_Base && is_wp_error( $wp_filesystem->errors ) && $wp_filesystem->errors->has_errors() ) {
|
||||
return new WP_Error( 'unable_to_connect_to_filesystem', $wp_filesystem->errors->get_error_message(), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
return new WP_Error( 'unable_to_connect_to_filesystem', __( 'Unable to connect to the filesystem. Please confirm your credentials.' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
$file = $upgrader->plugin_info();
|
||||
|
||||
if ( ! $file ) {
|
||||
return new WP_Error( 'unable_to_determine_installed_plugin', __( 'Unable to determine what plugin was installed.' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
if ( 'inactive' !== $request['status'] ) {
|
||||
$can_change_status = $this->plugin_status_permission_check( $file, $request['status'], 'inactive' );
|
||||
|
||||
if ( is_wp_error( $can_change_status ) ) {
|
||||
return $can_change_status;
|
||||
}
|
||||
|
||||
$changed_status = $this->handle_plugin_status( $file, $request['status'], 'inactive' );
|
||||
|
||||
if ( is_wp_error( $changed_status ) ) {
|
||||
return $changed_status;
|
||||
}
|
||||
}
|
||||
|
||||
// Install translations.
|
||||
$installed_locales = array_values( get_available_languages() );
|
||||
/** This filter is documented in wp-includes/update.php */
|
||||
$installed_locales = apply_filters( 'plugins_update_check_locales', $installed_locales );
|
||||
|
||||
$language_packs = array_map(
|
||||
function( $item ) {
|
||||
return (object) $item;
|
||||
},
|
||||
$api->language_packs
|
||||
);
|
||||
|
||||
$language_packs = array_filter(
|
||||
$language_packs,
|
||||
function( $pack ) use ( $installed_locales ) {
|
||||
return in_array( $pack->language, $installed_locales, true );
|
||||
}
|
||||
);
|
||||
|
||||
if ( $language_packs ) {
|
||||
$lp_upgrader = new Language_Pack_Upgrader( $skin );
|
||||
|
||||
// Install all applicable language packs for the plugin.
|
||||
$lp_upgrader->bulk_upgrade( $language_packs );
|
||||
}
|
||||
|
||||
$path = WP_PLUGIN_DIR . '/' . $file;
|
||||
$data = get_plugin_data( $path, false, false );
|
||||
$data['_file'] = $file;
|
||||
|
||||
$response = $this->prepare_item_for_response( $data, $request );
|
||||
$response->set_status( 201 );
|
||||
$response->header( 'Location', rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $file, 0, - 4 ) ) ) );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to update a specific plugin.
|
||||
*
|
||||
* @since 5.5.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 ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
if ( ! current_user_can( 'activate_plugins' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_manage_plugins',
|
||||
__( 'Sorry, you are not allowed to manage plugins for this site.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
$can_read = $this->check_read_permission( $request['plugin'] );
|
||||
|
||||
if ( is_wp_error( $can_read ) ) {
|
||||
return $can_read;
|
||||
}
|
||||
|
||||
$status = $this->get_plugin_status( $request['plugin'] );
|
||||
|
||||
if ( $request['status'] && $status !== $request['status'] ) {
|
||||
$can_change_status = $this->plugin_status_permission_check( $request['plugin'], $request['status'], $status );
|
||||
|
||||
if ( is_wp_error( $can_change_status ) ) {
|
||||
return $can_change_status;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates one plugin.
|
||||
*
|
||||
* @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 update_item( $request ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$data = $this->get_plugin_data( $request['plugin'] );
|
||||
|
||||
if ( is_wp_error( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$status = $this->get_plugin_status( $request['plugin'] );
|
||||
|
||||
if ( $request['status'] && $status !== $request['status'] ) {
|
||||
$handled = $this->handle_plugin_status( $request['plugin'], $request['status'], $status );
|
||||
|
||||
if ( is_wp_error( $handled ) ) {
|
||||
return $handled;
|
||||
}
|
||||
}
|
||||
|
||||
$this->update_additional_fields_for_object( $data, $request );
|
||||
|
||||
$request['context'] = 'edit';
|
||||
|
||||
return $this->prepare_item_for_response( $data, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to delete a specific plugin.
|
||||
*
|
||||
* @since 5.5.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 ) {
|
||||
if ( ! current_user_can( 'activate_plugins' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_manage_plugins',
|
||||
__( 'Sorry, you are not allowed to manage plugins for this site.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'delete_plugins' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_manage_plugins',
|
||||
__( 'Sorry, you are not allowed to delete plugins for this site.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
$can_read = $this->check_read_permission( $request['plugin'] );
|
||||
|
||||
if ( is_wp_error( $can_read ) ) {
|
||||
return $can_read;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes one plugin from the site.
|
||||
*
|
||||
* @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 delete_item( $request ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$data = $this->get_plugin_data( $request['plugin'] );
|
||||
|
||||
if ( is_wp_error( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if ( is_plugin_active( $request['plugin'] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_delete_active_plugin',
|
||||
__( 'Cannot delete an active plugin. Please deactivate it first.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$filesystem_available = $this->is_filesystem_available();
|
||||
if ( is_wp_error( $filesystem_available ) ) {
|
||||
return $filesystem_available;
|
||||
}
|
||||
|
||||
$prepared = $this->prepare_item_for_response( $data, $request );
|
||||
$deleted = delete_plugins( array( $request['plugin'] ) );
|
||||
|
||||
if ( is_wp_error( $deleted ) ) {
|
||||
$deleted->add_data( array( 'status' => 500 ) );
|
||||
|
||||
return $deleted;
|
||||
}
|
||||
|
||||
return new WP_REST_Response(
|
||||
array(
|
||||
'deleted' => true,
|
||||
'previous' => $prepared->get_data(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the plugin for the REST response.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param mixed $item Unmarked up and untranslated plugin data from {@see get_plugin_data()}.
|
||||
* @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 ) {
|
||||
$item = _get_plugin_data_markup_translate( $item['_file'], $item, false );
|
||||
$marked = _get_plugin_data_markup_translate( $item['_file'], $item, true );
|
||||
|
||||
$data = array(
|
||||
'plugin' => substr( $item['_file'], 0, - 4 ),
|
||||
'status' => $this->get_plugin_status( $item['_file'] ),
|
||||
'name' => $item['Name'],
|
||||
'plugin_uri' => $item['PluginURI'],
|
||||
'author' => $item['Author'],
|
||||
'author_uri' => $item['AuthorURI'],
|
||||
'description' => array(
|
||||
'raw' => $item['Description'],
|
||||
'rendered' => $marked['Description'],
|
||||
),
|
||||
'version' => $item['Version'],
|
||||
'network_only' => $item['Network'],
|
||||
'requires_wp' => $item['RequiresWP'],
|
||||
'requires_php' => $item['RequiresPHP'],
|
||||
'textdomain' => $item['TextDomain'],
|
||||
);
|
||||
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
|
||||
$response = new WP_REST_Response( $data );
|
||||
$response->add_links( $this->prepare_links( $item ) );
|
||||
|
||||
/**
|
||||
* Filters plugin data for a REST API response.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param array $item The plugin item from {@see get_plugin_data()}.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*/
|
||||
return apply_filters( 'rest_prepare_plugin', $response, $item, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares links for the request.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param array $item The plugin item.
|
||||
* @return array[]
|
||||
*/
|
||||
protected function prepare_links( $item ) {
|
||||
return array(
|
||||
'self' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, substr( $item['_file'], 0, - 4 ) ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the plugin header data for a plugin.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $plugin The plugin file to get data for.
|
||||
* @return array|WP_Error The plugin data, or a WP_Error if the plugin is not installed.
|
||||
*/
|
||||
protected function get_plugin_data( $plugin ) {
|
||||
$plugins = get_plugins();
|
||||
|
||||
if ( ! isset( $plugins[ $plugin ] ) ) {
|
||||
return new WP_Error( 'rest_plugin_not_found', __( 'Plugin not found.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$data = $plugins[ $plugin ];
|
||||
$data['_file'] = $plugin;
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get's the activation status for a plugin.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $plugin The plugin file to check.
|
||||
* @return string Either 'network-active', 'active' or 'inactive'.
|
||||
*/
|
||||
protected function get_plugin_status( $plugin ) {
|
||||
if ( is_plugin_active_for_network( $plugin ) ) {
|
||||
return 'network-active';
|
||||
}
|
||||
|
||||
if ( is_plugin_active( $plugin ) ) {
|
||||
return 'active';
|
||||
}
|
||||
|
||||
return 'inactive';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle updating a plugin's status.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $plugin The plugin file to update.
|
||||
* @param string $new_status The plugin's new status.
|
||||
* @param string $current_status The plugin's current status.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
protected function plugin_status_permission_check( $plugin, $new_status, $current_status ) {
|
||||
if ( is_multisite() && ( 'network-active' === $current_status || 'network-active' === $new_status ) && ! current_user_can( 'manage_network_plugins' ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_manage_network_plugins',
|
||||
__( 'Sorry, you are not allowed to manage network plugins.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
if ( ( 'active' === $new_status || 'network-active' === $new_status ) && ! current_user_can( 'activate_plugin', $plugin ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_activate_plugin',
|
||||
__( 'Sorry, you are not allowed to activate this plugin.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
if ( 'inactive' === $new_status && ! current_user_can( 'deactivate_plugin', $plugin ) ) {
|
||||
return new WP_Error(
|
||||
'rest_cannot_deactivate_plugin',
|
||||
__( 'Sorry, you are not allowed to deactivate this plugin.' ),
|
||||
array( 'status' => rest_authorization_required_code() )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle updating a plugin's status.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $plugin The plugin file to update.
|
||||
* @param string $new_status The plugin's new status.
|
||||
* @param string $current_status The plugin's current status.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
protected function handle_plugin_status( $plugin, $new_status, $current_status ) {
|
||||
if ( 'inactive' === $new_status ) {
|
||||
deactivate_plugins( $plugin, false, 'network-active' === $current_status );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( 'active' === $new_status && 'network-active' === $current_status ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$network_activate = 'network-active' === $new_status;
|
||||
|
||||
if ( is_multisite() && ! $network_activate && is_network_only_plugin( $plugin ) ) {
|
||||
return new WP_Error(
|
||||
'rest_network_only_plugin',
|
||||
__( 'Network only plugin must be network activated.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$activated = activate_plugin( $plugin, '', $network_activate );
|
||||
|
||||
if ( is_wp_error( $activated ) ) {
|
||||
$activated->add_data( array( 'status' => 500 ) );
|
||||
|
||||
return $activated;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that the "plugin" parameter is a valid path.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $file The plugin file parameter.
|
||||
* @return bool
|
||||
*/
|
||||
public function validate_plugin_param( $file ) {
|
||||
if ( ! is_string( $file ) || ! preg_match( '/' . self::PATTERN . '/u', $file ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$validated = validate_file( plugin_basename( $file ) );
|
||||
|
||||
return 0 === $validated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the "plugin" parameter to be a proper plugin file with ".php" appended.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $file The plugin file parameter.
|
||||
* @return string
|
||||
*/
|
||||
public function sanitize_plugin_param( $file ) {
|
||||
return plugin_basename( sanitize_text_field( $file . '.php' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the plugin matches the requested parameters.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request to require the plugin matches against.
|
||||
* @param array $item The plugin item.
|
||||
* @return bool
|
||||
*/
|
||||
protected function does_plugin_match_request( $request, $item ) {
|
||||
$search = $request['search'];
|
||||
|
||||
if ( $search ) {
|
||||
$matched_search = false;
|
||||
|
||||
foreach ( $item as $field ) {
|
||||
if ( is_string( $field ) && false !== strpos( strip_tags( $field ), $search ) ) {
|
||||
$matched_search = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $matched_search ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$status = $request['status'];
|
||||
|
||||
if ( $status && ! in_array( $this->get_plugin_status( $item['_file'] ), $status, true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the plugin is installed.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @param string $plugin The plugin file.
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_plugin_installed( $plugin ) {
|
||||
return file_exists( WP_PLUGIN_DIR . '/' . $plugin );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the endpoints are available.
|
||||
*
|
||||
* Only the 'Direct' filesystem transport, and SSH/FTP when credentials are stored are supported at present.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @return true|WP_Error True if filesystem is available, WP_Error otherwise.
|
||||
*/
|
||||
protected function is_filesystem_available() {
|
||||
$filesystem_method = get_filesystem_method();
|
||||
|
||||
if ( 'direct' === $filesystem_method ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ob_start();
|
||||
$filesystem_credentials_are_stored = request_filesystem_credentials( self_admin_url() );
|
||||
ob_end_clean();
|
||||
|
||||
if ( $filesystem_credentials_are_stored ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error( 'fs_unavailable', __( 'The filesystem is currently unavailable for managing plugins.' ), array( 'status' => 500 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the plugin'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' => 'plugin',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'plugin' => array(
|
||||
'description' => __( 'The plugin file.' ),
|
||||
'type' => 'string',
|
||||
'pattern' => self::PATTERN,
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'status' => array(
|
||||
'description' => __( 'The plugin activation status.' ),
|
||||
'type' => 'string',
|
||||
'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'The plugin name.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'plugin_uri' => array(
|
||||
'description' => __( 'The plugin\'s website address.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'author' => array(
|
||||
'description' => __( 'The plugin author.' ),
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'author_uri' => array(
|
||||
'description' => __( 'Plugin author\'s website address.' ),
|
||||
'type' => 'string',
|
||||
'format' => 'uri',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'The plugin description.' ),
|
||||
'type' => 'object',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'properties' => array(
|
||||
'raw' => array(
|
||||
'description' => __( 'The raw plugin description.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
'rendered' => array(
|
||||
'description' => __( 'The plugin description formatted for display.' ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
),
|
||||
'version' => array(
|
||||
'description' => __( 'The plugin version number.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
'network_only' => array(
|
||||
'description' => __( 'Whether the plugin can only be activated network-wide.' ),
|
||||
'type' => 'boolean',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'requires_wp' => array(
|
||||
'description' => __( 'Minimum required version of WordPress.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'requires_php' => array(
|
||||
'description' => __( 'Minimum required version of PHP.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'textdomain' => array(
|
||||
'description' => __( 'The plugin\'s text domain.' ),
|
||||
'type' => 'string',
|
||||
'readonly' => true,
|
||||
'context' => array( 'view', 'edit' ),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the collections.
|
||||
*
|
||||
* @since 5.5.0
|
||||
*
|
||||
* @return array Query parameters for the collection.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
$query_params = parent::get_collection_params();
|
||||
|
||||
$query_params['context']['default'] = 'view';
|
||||
|
||||
$query_params['status'] = array(
|
||||
'description' => __( 'Limits results to plugins with the given status.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
'enum' => is_multisite() ? array( 'inactive', 'active', 'network-active' ) : array( 'inactive', 'active' ),
|
||||
),
|
||||
);
|
||||
|
||||
unset( $query_params['page'], $query_params['per_page'] );
|
||||
|
||||
return $query_params;
|
||||
}
|
||||
}
|
@@ -0,0 +1,370 @@
|
||||
<?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
|
||||
*
|
||||
* @param stdClass $status Post status data.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Post status data.
|
||||
*/
|
||||
public function prepare_item_for_response( $status, $request ) {
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( in_array( 'name', $fields, true ) ) {
|
||||
$data['name'] = $status->label;
|
||||
}
|
||||
|
||||
if ( in_array( 'private', $fields, true ) ) {
|
||||
$data['private'] = (bool) $status->private;
|
||||
}
|
||||
|
||||
if ( in_array( 'protected', $fields, true ) ) {
|
||||
$data['protected'] = (bool) $status->protected;
|
||||
}
|
||||
|
||||
if ( in_array( 'public', $fields, true ) ) {
|
||||
$data['public'] = (bool) $status->public;
|
||||
}
|
||||
|
||||
if ( in_array( 'queryable', $fields, true ) ) {
|
||||
$data['queryable'] = (bool) $status->publicly_queryable;
|
||||
}
|
||||
|
||||
if ( in_array( 'show_in_list', $fields, true ) ) {
|
||||
$data['show_in_list'] = (bool) $status->show_in_admin_all_list;
|
||||
}
|
||||
|
||||
if ( in_array( 'slug', $fields, true ) ) {
|
||||
$data['slug'] = $status->name;
|
||||
}
|
||||
|
||||
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 );
|
||||
|
||||
if ( 'publish' === $status->name ) {
|
||||
$response->add_link( 'archives', rest_url( 'wp/v2/posts' ) );
|
||||
} else {
|
||||
$response->add_link( 'archives', add_query_arg( 'status', $status->name, rest_url( 'wp/v2/posts' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a 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,358 @@
|
||||
<?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
|
||||
*
|
||||
* @param WP_Post_Type $post_type Post type object.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $post_type, $request ) {
|
||||
$taxonomies = wp_list_filter( get_object_taxonomies( $post_type->name, 'objects' ), array( 'show_in_rest' => true ) );
|
||||
$taxonomies = wp_list_pluck( $taxonomies, 'name' );
|
||||
$base = ! empty( $post_type->rest_base ) ? $post_type->rest_base : $post_type->name;
|
||||
$supports = get_all_post_type_supports( $post_type->name );
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( in_array( 'capabilities', $fields, true ) ) {
|
||||
$data['capabilities'] = $post_type->cap;
|
||||
}
|
||||
|
||||
if ( in_array( 'description', $fields, true ) ) {
|
||||
$data['description'] = $post_type->description;
|
||||
}
|
||||
|
||||
if ( in_array( 'hierarchical', $fields, true ) ) {
|
||||
$data['hierarchical'] = $post_type->hierarchical;
|
||||
}
|
||||
|
||||
if ( in_array( 'viewable', $fields, true ) ) {
|
||||
$data['viewable'] = is_post_type_viewable( $post_type );
|
||||
}
|
||||
|
||||
if ( in_array( 'labels', $fields, true ) ) {
|
||||
$data['labels'] = $post_type->labels;
|
||||
}
|
||||
|
||||
if ( in_array( 'name', $fields, true ) ) {
|
||||
$data['name'] = $post_type->label;
|
||||
}
|
||||
|
||||
if ( in_array( 'slug', $fields, true ) ) {
|
||||
$data['slug'] = $post_type->name;
|
||||
}
|
||||
|
||||
if ( in_array( 'supports', $fields, true ) ) {
|
||||
$data['supports'] = $supports;
|
||||
}
|
||||
|
||||
if ( in_array( 'taxonomies', $fields, true ) ) {
|
||||
$data['taxonomies'] = array_values( $taxonomies );
|
||||
}
|
||||
|
||||
if ( in_array( 'rest_base', $fields, true ) ) {
|
||||
$data['rest_base'] = $base;
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
$response->add_links(
|
||||
array(
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
'https://api.w.org/items' => array(
|
||||
'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters a post type returned from the 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the post type'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' => 'type',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'capabilities' => array(
|
||||
'description' => __( 'All capabilities used by the post type.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'A human-readable description of the post type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'hierarchical' => array(
|
||||
'description' => __( 'Whether or not the post type should have children.' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'viewable' => array(
|
||||
'description' => __( 'Whether or not the post type can be viewed.' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'labels' => array(
|
||||
'description' => __( 'Human-readable labels for the post type for various contexts.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'The title for the post type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'slug' => array(
|
||||
'description' => __( 'An alphanumeric identifier for the post type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'supports' => array(
|
||||
'description' => __( 'All features, supported by the post type.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'taxonomies' => array(
|
||||
'description' => __( 'Taxonomies associated with post type.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'rest_base' => array(
|
||||
'description' => __( 'REST base route for the post type.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$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' ) ),
|
||||
);
|
||||
}
|
||||
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,834 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Revisions_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 4.7.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to access revisions via the REST API.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Revisions_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Parent post type.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $parent_post_type;
|
||||
|
||||
/**
|
||||
* Parent controller.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var WP_REST_Controller
|
||||
*/
|
||||
private $parent_controller;
|
||||
|
||||
/**
|
||||
* The base of the parent controller's route.
|
||||
*
|
||||
* @since 4.7.0
|
||||
* @var string
|
||||
*/
|
||||
private $parent_base;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param string $parent_post_type Post type of the parent.
|
||||
*/
|
||||
public function __construct( $parent_post_type ) {
|
||||
$this->parent_post_type = $parent_post_type;
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'revisions';
|
||||
$post_type_object = get_post_type_object( $parent_post_type );
|
||||
$this->parent_base = ! empty( $post_type_object->rest_base ) ? $post_type_object->rest_base : $post_type_object->name;
|
||||
$this->parent_controller = $post_type_object->get_rest_controller();
|
||||
|
||||
if ( ! $this->parent_controller ) {
|
||||
$this->parent_controller = new WP_REST_Posts_Controller( $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 Supplied ID.
|
||||
* @return WP_Post|WP_Error Post object if ID is valid, WP_Error otherwise.
|
||||
*/
|
||||
protected function get_parent( $parent ) {
|
||||
$error = new WP_Error(
|
||||
'rest_post_invalid_parent',
|
||||
__( 'Invalid post parent ID.' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
if ( (int) $parent <= 0 ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$parent = get_post( (int) $parent );
|
||||
if ( empty( $parent ) || empty( $parent->ID ) || $this->parent_post_type !== $parent->post_type ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
return $parent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get revisions.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full 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 = ceil( $total_revisions / (int) $revisions_query->query_vars['posts_per_page'] );
|
||||
} else {
|
||||
$max_pages = $total_revisions > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
if ( $total_revisions > 0 ) {
|
||||
if ( $offset >= $total_revisions ) {
|
||||
return new WP_Error(
|
||||
'rest_revision_invalid_offset_number',
|
||||
__( 'The offset number requested is larger than or equal to the number of available revisions.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
} elseif ( ! $offset && $page > $max_pages ) {
|
||||
return new WP_Error(
|
||||
'rest_revision_invalid_page_number',
|
||||
__( 'The page number requested is larger than the number of pages available.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$revisions = array();
|
||||
$total_revisions = 0;
|
||||
$max_pages = 0;
|
||||
$page = (int) $request['page'];
|
||||
}
|
||||
|
||||
$response = array();
|
||||
|
||||
foreach ( $revisions as $revision ) {
|
||||
$data = $this->prepare_item_for_response( $revision, $request );
|
||||
$response[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
$response = rest_ensure_response( $response );
|
||||
|
||||
$response->header( 'X-WP-Total', (int) $total_revisions );
|
||||
$response->header( 'X-WP-TotalPages', (int) $max_pages );
|
||||
|
||||
$request_params = $request->get_query_params();
|
||||
$base = add_query_arg( urlencode_deep( $request_params ), rest_url( sprintf( '%s/%s/%d/%s', $this->namespace, $this->parent_base, $request['parent'], $this->rest_base ) ) );
|
||||
|
||||
if ( $page > 1 ) {
|
||||
$prev_page = $page - 1;
|
||||
|
||||
if ( $prev_page > $max_pages ) {
|
||||
$prev_page = $max_pages;
|
||||
}
|
||||
|
||||
$prev_link = add_query_arg( 'page', $prev_page, $base );
|
||||
$response->link_header( 'prev', $prev_link );
|
||||
}
|
||||
if ( $max_pages > $page ) {
|
||||
$next_page = $page + 1;
|
||||
$next_link = add_query_arg( 'page', $next_page, $base );
|
||||
|
||||
$response->link_header( 'next', $next_link );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to get a specific revision.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full 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
|
||||
*
|
||||
* @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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
$parent_post_type = get_post_type_object( $parent->post_type );
|
||||
|
||||
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
|
||||
*
|
||||
* @param WP_Post $post Post revision object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $post, $request ) {
|
||||
$GLOBALS['post'] = $post;
|
||||
|
||||
setup_postdata( $post );
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( in_array( 'author', $fields, true ) ) {
|
||||
$data['author'] = (int) $post->post_author;
|
||||
}
|
||||
|
||||
if ( in_array( 'date', $fields, true ) ) {
|
||||
$data['date'] = $this->prepare_date_response( $post->post_date_gmt, $post->post_date );
|
||||
}
|
||||
|
||||
if ( in_array( 'date_gmt', $fields, true ) ) {
|
||||
$data['date_gmt'] = $this->prepare_date_response( $post->post_date_gmt );
|
||||
}
|
||||
|
||||
if ( in_array( 'id', $fields, true ) ) {
|
||||
$data['id'] = $post->ID;
|
||||
}
|
||||
|
||||
if ( in_array( 'modified', $fields, true ) ) {
|
||||
$data['modified'] = $this->prepare_date_response( $post->post_modified_gmt, $post->post_modified );
|
||||
}
|
||||
|
||||
if ( in_array( 'modified_gmt', $fields, true ) ) {
|
||||
$data['modified_gmt'] = $this->prepare_date_response( $post->post_modified_gmt );
|
||||
}
|
||||
|
||||
if ( in_array( 'parent', $fields, true ) ) {
|
||||
$data['parent'] = (int) $post->post_parent;
|
||||
}
|
||||
|
||||
if ( in_array( 'slug', $fields, true ) ) {
|
||||
$data['slug'] = $post->post_name;
|
||||
}
|
||||
|
||||
if ( in_array( 'guid', $fields, true ) ) {
|
||||
$data['guid'] = array(
|
||||
/** This filter is documented in wp-includes/post-template.php */
|
||||
'rendered' => apply_filters( 'get_the_guid', $post->guid, $post->ID ),
|
||||
'raw' => $post->guid,
|
||||
);
|
||||
}
|
||||
|
||||
if ( in_array( 'title', $fields, true ) ) {
|
||||
$data['title'] = array(
|
||||
'raw' => $post->post_title,
|
||||
'rendered' => get_the_title( $post->ID ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( in_array( 'content', $fields, true ) ) {
|
||||
|
||||
$data['content'] = array(
|
||||
'raw' => $post->post_content,
|
||||
/** This filter is documented in wp-includes/post-template.php */
|
||||
'rendered' => apply_filters( 'the_content', $post->post_content ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( in_array( 'excerpt', $fields, true ) ) {
|
||||
$data['excerpt'] = array(
|
||||
'raw' => $post->post_excerpt,
|
||||
'rendered' => $this->prepare_excerpt_response( $post->post_excerpt, $post ),
|
||||
);
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
if ( ! empty( $data['parent'] ) ) {
|
||||
$response->add_link( 'parent', rest_url( sprintf( '%s/%s/%d', $this->namespace, $this->parent_base, $data['parent'] ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters a revision returned from the 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'];
|
||||
}
|
||||
|
||||
$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,384 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Search_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class to search through all WordPress content via the REST API.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Search_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* ID property name.
|
||||
*/
|
||||
const PROP_ID = 'id';
|
||||
|
||||
/**
|
||||
* Title property name.
|
||||
*/
|
||||
const PROP_TITLE = 'title';
|
||||
|
||||
/**
|
||||
* URL property name.
|
||||
*/
|
||||
const PROP_URL = 'url';
|
||||
|
||||
/**
|
||||
* Type property name.
|
||||
*/
|
||||
const PROP_TYPE = 'type';
|
||||
|
||||
/**
|
||||
* Subtype property name.
|
||||
*/
|
||||
const PROP_SUBTYPE = 'subtype';
|
||||
|
||||
/**
|
||||
* Identifier for the 'any' type.
|
||||
*/
|
||||
const TYPE_ANY = 'any';
|
||||
|
||||
/**
|
||||
* Search handlers used by the controller.
|
||||
*
|
||||
* @since 5.0.0
|
||||
* @var array
|
||||
*/
|
||||
protected $search_handlers = array();
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param array $search_handlers List of search handlers to use in the controller. Each search
|
||||
* handler instance must extend the `WP_REST_Search_Handler` class.
|
||||
*/
|
||||
public function __construct( array $search_handlers ) {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'search';
|
||||
|
||||
foreach ( $search_handlers as $search_handler ) {
|
||||
if ( ! $search_handler instanceof WP_REST_Search_Handler ) {
|
||||
_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 = 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.
|
||||
*
|
||||
* @param int|string $id ID of the item to prepare.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $id, $request ) {
|
||||
$handler = $this->get_search_handler( $request );
|
||||
if ( is_wp_error( $handler ) ) {
|
||||
return new WP_REST_Response();
|
||||
}
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
$data = $handler->prepare_item( $id, $fields );
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
$links = $handler->prepare_item_links( $id );
|
||||
$links['collection'] = array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
);
|
||||
$response->add_links( $links );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the item schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
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' ),
|
||||
);
|
||||
|
||||
return $query_params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the list of subtypes, to ensure only subtypes of the passed type are included.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param string|array $subtypes One or more subtypes.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @param string $parameter Parameter name.
|
||||
* @return array|WP_Error List of valid subtypes, or WP_Error object on failure.
|
||||
*/
|
||||
public function sanitize_subtypes( $subtypes, $request, $parameter ) {
|
||||
$subtypes = wp_parse_slug_list( $subtypes );
|
||||
|
||||
$subtypes = rest_parse_request_arg( $subtypes, $request, $parameter );
|
||||
if ( is_wp_error( $subtypes ) ) {
|
||||
return $subtypes;
|
||||
}
|
||||
|
||||
// 'any' overrides any other subtype.
|
||||
if ( in_array( self::TYPE_ANY, $subtypes, true ) ) {
|
||||
return array( self::TYPE_ANY );
|
||||
}
|
||||
|
||||
$handler = $this->get_search_handler( $request );
|
||||
if ( is_wp_error( $handler ) ) {
|
||||
return $handler;
|
||||
}
|
||||
|
||||
return array_intersect( $subtypes, $handler->get_subtypes() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the search handler to handle the current request.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Search_Handler|WP_Error Search handler for the request type, or WP_Error object on failure.
|
||||
*/
|
||||
protected function get_search_handler( $request ) {
|
||||
$type = $request->get_param( self::PROP_TYPE );
|
||||
|
||||
if ( ! $type || ! isset( $this->search_handlers[ $type ] ) ) {
|
||||
return new WP_Error(
|
||||
'rest_search_invalid_type',
|
||||
__( 'Invalid type parameter.' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
return $this->search_handlers[ $type ];
|
||||
}
|
||||
}
|
@@ -0,0 +1,352 @@
|
||||
<?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'] = $this->set_additional_properties_to_false( $rest_args['schema'] );
|
||||
|
||||
$rest_options[ $rest_args['name'] ] = $rest_args;
|
||||
}
|
||||
|
||||
return $rest_options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the site setting schema, conforming to JSON Schema.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @return array Item schema data.
|
||||
*/
|
||||
public function get_item_schema() {
|
||||
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.
|
||||
*
|
||||
* This is need to restrict properties of objects in settings values to only
|
||||
* registered items, as the REST API will allow additional properties by
|
||||
* default.
|
||||
*
|
||||
* @since 4.9.0
|
||||
*
|
||||
* @param array $schema The schema array.
|
||||
* @return array
|
||||
*/
|
||||
protected function set_additional_properties_to_false( $schema ) {
|
||||
switch ( $schema['type'] ) {
|
||||
case 'object':
|
||||
foreach ( $schema['properties'] as $key => $child_schema ) {
|
||||
$schema['properties'][ $key ] = $this->set_additional_properties_to_false( $child_schema );
|
||||
}
|
||||
|
||||
$schema['additionalProperties'] = false;
|
||||
break;
|
||||
case 'array':
|
||||
$schema['items'] = $this->set_additional_properties_to_false( $schema['items'] );
|
||||
break;
|
||||
}
|
||||
|
||||
return $schema;
|
||||
}
|
||||
}
|
@@ -0,0 +1,455 @@
|
||||
<?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 {
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
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|WP_Error Response object on success, or WP_Error object on failure.
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
retrieve_widgets();
|
||||
|
||||
$data = array();
|
||||
foreach ( wp_get_sidebars_widgets() as $id => $widgets ) {
|
||||
$sidebar = $this->get_sidebar( $id );
|
||||
|
||||
if ( ! $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 ) {
|
||||
return $this->do_permissions_check();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
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
|
||||
*
|
||||
* @global array $wp_registered_sidebars The registered sidebars.
|
||||
*
|
||||
* @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 ) {
|
||||
global $wp_registered_sidebars;
|
||||
|
||||
foreach ( (array) $wp_registered_sidebars as $sidebar ) {
|
||||
if ( $sidebar['id'] === $id ) {
|
||||
return $sidebar;
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'wp_inactive_widgets' === $id ) {
|
||||
return array(
|
||||
'id' => 'wp_inactive_widgets',
|
||||
'name' => __( 'Inactive widgets' ),
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single sidebar output for response.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @global array $wp_registered_sidebars The registered sidebars.
|
||||
* @global array $wp_registered_widgets The registered widgets.
|
||||
*
|
||||
* @param array $raw_sidebar 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( $raw_sidebar, $request ) {
|
||||
global $wp_registered_sidebars, $wp_registered_widgets;
|
||||
|
||||
$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'] = '';
|
||||
}
|
||||
|
||||
$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 );
|
||||
|
||||
$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,376 @@
|
||||
<?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
|
||||
*
|
||||
* @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( 'debug_enabled' ) && ! is_multisite();
|
||||
},
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,425 @@
|
||||
<?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
|
||||
*
|
||||
* @param WP_Taxonomy $taxonomy Taxonomy data.
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $taxonomy, $request ) {
|
||||
$base = ! empty( $taxonomy->rest_base ) ? $taxonomy->rest_base : $taxonomy->name;
|
||||
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
$data = array();
|
||||
|
||||
if ( in_array( 'name', $fields, true ) ) {
|
||||
$data['name'] = $taxonomy->label;
|
||||
}
|
||||
|
||||
if ( in_array( 'slug', $fields, true ) ) {
|
||||
$data['slug'] = $taxonomy->name;
|
||||
}
|
||||
|
||||
if ( in_array( 'capabilities', $fields, true ) ) {
|
||||
$data['capabilities'] = $taxonomy->cap;
|
||||
}
|
||||
|
||||
if ( in_array( 'description', $fields, true ) ) {
|
||||
$data['description'] = $taxonomy->description;
|
||||
}
|
||||
|
||||
if ( in_array( 'labels', $fields, true ) ) {
|
||||
$data['labels'] = $taxonomy->labels;
|
||||
}
|
||||
|
||||
if ( in_array( 'types', $fields, true ) ) {
|
||||
$data['types'] = 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( 'visibility', $fields, true ) ) {
|
||||
$data['visibility'] = array(
|
||||
'public' => (bool) $taxonomy->public,
|
||||
'publicly_queryable' => (bool) $taxonomy->publicly_queryable,
|
||||
'show_admin_column' => (bool) $taxonomy->show_admin_column,
|
||||
'show_in_nav_menus' => (bool) $taxonomy->show_in_nav_menus,
|
||||
'show_in_quick_edit' => (bool) $taxonomy->show_in_quick_edit,
|
||||
'show_ui' => (bool) $taxonomy->show_ui,
|
||||
);
|
||||
}
|
||||
|
||||
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
$data = $this->filter_response_by_context( $data, $context );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
$response->add_links(
|
||||
array(
|
||||
'collection' => array(
|
||||
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
|
||||
),
|
||||
'https://api.w.org/items' => array(
|
||||
'href' => rest_url( sprintf( 'wp/v2/%s', $base ) ),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Filters a taxonomy returned from the REST API.
|
||||
*
|
||||
* Allows modification of the taxonomy data right before it is returned.
|
||||
*
|
||||
* @since 4.7.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @param 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the taxonomy'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' => 'taxonomy',
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'capabilities' => array(
|
||||
'description' => __( 'All capabilities used by the taxonomy.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'A human-readable description of the taxonomy.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'hierarchical' => array(
|
||||
'description' => __( 'Whether or not the taxonomy should have children.' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'labels' => array(
|
||||
'description' => __( 'Human-readable labels for the taxonomy for various contexts.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'name' => array(
|
||||
'description' => __( 'The title for the taxonomy.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'slug' => array(
|
||||
'description' => __( 'An alphanumeric identifier for the taxonomy.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'show_cloud' => array(
|
||||
'description' => __( 'Whether or not the term cloud should be displayed.' ),
|
||||
'type' => 'boolean',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'types' => array(
|
||||
'description' => __( 'Types associated with the taxonomy.' ),
|
||||
'type' => 'array',
|
||||
'items' => array(
|
||||
'type' => 'string',
|
||||
),
|
||||
'context' => array( 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'rest_base' => array(
|
||||
'description' => __( 'REST base route for the taxonomy.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'visibility' => array(
|
||||
'description' => __( 'The visibility settings for the taxonomy.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'edit' ),
|
||||
'readonly' => true,
|
||||
'properties' => array(
|
||||
'public' => array(
|
||||
'description' => __( 'Whether a taxonomy is intended for use publicly either via the admin interface or by front-end users.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'publicly_queryable' => array(
|
||||
'description' => __( 'Whether the taxonomy is publicly queryable.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'show_ui' => array(
|
||||
'description' => __( 'Whether to generate a default UI for managing this taxonomy.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'show_admin_column' => array(
|
||||
'description' => __( 'Whether to allow automatic creation of taxonomy columns on associated post-types table.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'show_in_nav_menus' => array(
|
||||
'description' => __( 'Whether to make the taxonomy available for selection in navigation menus.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
'show_in_quick_edit' => array(
|
||||
'description' => __( 'Whether to show the taxonomy in the quick/bulk edit panel.' ),
|
||||
'type' => 'boolean',
|
||||
),
|
||||
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$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,600 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Templates_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.8.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base Templates REST API Controller.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Templates_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Post type.
|
||||
*
|
||||
* @since 5.8.0
|
||||
* @var string
|
||||
*/
|
||||
protected $post_type;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param string $post_type Post type.
|
||||
*/
|
||||
public function __construct( $post_type ) {
|
||||
$this->post_type = $post_type;
|
||||
$this->namespace = 'wp/v2';
|
||||
$obj = get_post_type_object( $post_type );
|
||||
$this->rest_base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the controllers routes.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*/
|
||||
public function register_routes() {
|
||||
// Lists all templates.
|
||||
register_rest_route(
|
||||
$this->namespace,
|
||||
'/' . $this->rest_base,
|
||||
array(
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => array( $this, 'get_items' ),
|
||||
'permission_callback' => array( $this, 'get_items_permissions_check' ),
|
||||
'args' => $this->get_collection_params(),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => array( $this, 'create_item' ),
|
||||
'permission_callback' => array( $this, 'create_item_permissions_check' ),
|
||||
'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::CREATABLE ),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
|
||||
// Lists/updates a single template 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',
|
||||
),
|
||||
),
|
||||
),
|
||||
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(
|
||||
'type' => 'boolean',
|
||||
'default' => false,
|
||||
'description' => __( 'Whether to bypass Trash and force deletion.' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'schema' => array( $this, 'get_public_item_schema' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the user has permissions to make the request.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
protected function 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_templates',
|
||||
__( 'Sorry, you are not allowed to access the templates on this site.' ),
|
||||
array(
|
||||
'status' => rest_authorization_required_code(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read templates.
|
||||
*
|
||||
* @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->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of templates.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request instance.
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function get_items( $request ) {
|
||||
$query = array();
|
||||
if ( isset( $request['wp_id'] ) ) {
|
||||
$query['wp_id'] = $request['wp_id'];
|
||||
}
|
||||
if ( isset( $request['area'] ) ) {
|
||||
$query['area'] = $request['area'];
|
||||
}
|
||||
|
||||
$templates = array();
|
||||
foreach ( get_block_templates( $query, $this->post_type ) as $template ) {
|
||||
$data = $this->prepare_item_for_response( $template, $request );
|
||||
$templates[] = $this->prepare_response_for_collection( $data );
|
||||
}
|
||||
|
||||
return rest_ensure_response( $templates );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to read a single template.
|
||||
*
|
||||
* @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 ) {
|
||||
return $this->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given template
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Request $request The request instance.
|
||||
* @return WP_REST_Response|WP_Error
|
||||
*/
|
||||
public function get_item( $request ) {
|
||||
$template = get_block_template( $request['id'], $this->post_type );
|
||||
|
||||
if ( ! $template ) {
|
||||
return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response( $template, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to write a single template.
|
||||
*
|
||||
* @since 5.8.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 ) {
|
||||
return $this->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single template.
|
||||
*
|
||||
* @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 update_item( $request ) {
|
||||
$template = get_block_template( $request['id'], $this->post_type );
|
||||
if ( ! $template ) {
|
||||
return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$changes = $this->prepare_item_for_database( $request );
|
||||
|
||||
if ( 'custom' === $template->source ) {
|
||||
$result = wp_update_post( wp_slash( (array) $changes ), true );
|
||||
} else {
|
||||
$result = wp_insert_post( wp_slash( (array) $changes ), true );
|
||||
}
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$template = get_block_template( $request['id'], $this->post_type );
|
||||
$fields_update = $this->update_additional_fields_for_object( $template, $request );
|
||||
if ( is_wp_error( $fields_update ) ) {
|
||||
return $fields_update;
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response(
|
||||
get_block_template( $request['id'], $this->post_type ),
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to create a template.
|
||||
*
|
||||
* @since 5.8.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 $this->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a single template.
|
||||
*
|
||||
* @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 ) {
|
||||
$changes = $this->prepare_item_for_database( $request );
|
||||
$changes->post_name = $request['slug'];
|
||||
$result = wp_insert_post( wp_slash( (array) $changes ), true );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
$posts = get_block_templates( array( 'wp_id' => $result ), $this->post_type );
|
||||
if ( ! count( $posts ) ) {
|
||||
return new WP_Error( 'rest_template_insert_error', __( 'No templates exist with that id.' ) );
|
||||
}
|
||||
$id = $posts[0]->id;
|
||||
$template = get_block_template( $id, $this->post_type );
|
||||
$fields_update = $this->update_additional_fields_for_object( $template, $request );
|
||||
if ( is_wp_error( $fields_update ) ) {
|
||||
return $fields_update;
|
||||
}
|
||||
|
||||
return $this->prepare_item_for_response(
|
||||
get_block_template( $id, $this->post_type ),
|
||||
$request
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given request has access to delete a single template.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Request $request Full details about the request.
|
||||
* @return true|WP_Error True if the request has delete access for the item, WP_Error object otherwise.
|
||||
*/
|
||||
public function delete_item_permissions_check( $request ) {
|
||||
return $this->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a single template.
|
||||
*
|
||||
* @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 delete_item( $request ) {
|
||||
$template = get_block_template( $request['id'], $this->post_type );
|
||||
if ( ! $template ) {
|
||||
return new WP_Error( 'rest_template_not_found', __( 'No templates exist with that id.' ), array( 'status' => 404 ) );
|
||||
}
|
||||
if ( 'custom' !== $template->source ) {
|
||||
return new WP_Error( 'rest_invalid_template', __( 'Templates based on theme files can\'t be removed.' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$id = $template->wp_id;
|
||||
$force = (bool) $request['force'];
|
||||
|
||||
// If we're forcing, then delete permanently.
|
||||
if ( $force ) {
|
||||
$previous = $this->prepare_item_for_response( $template, $request );
|
||||
wp_delete_post( $id, true );
|
||||
$response = new WP_REST_Response();
|
||||
$response->set_data(
|
||||
array(
|
||||
'deleted' => true,
|
||||
'previous' => $previous->get_data(),
|
||||
)
|
||||
);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Otherwise, only trash if we haven't already.
|
||||
if ( 'trash' === $template->status ) {
|
||||
return new WP_Error(
|
||||
'rest_template_already_trashed',
|
||||
__( 'The template has already been deleted.' ),
|
||||
array( 'status' => 410 )
|
||||
);
|
||||
}
|
||||
|
||||
wp_trash_post( $id );
|
||||
$template->status = 'trash';
|
||||
return $this->prepare_item_for_response( $template, $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares a single template for create or update.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return stdClass Changes to pass to wp_update_post.
|
||||
*/
|
||||
protected function prepare_item_for_database( $request ) {
|
||||
$template = $request['id'] ? get_block_template( $request['id'], $this->post_type ) : null;
|
||||
$changes = new stdClass();
|
||||
if ( null === $template ) {
|
||||
$changes->post_type = $this->post_type;
|
||||
$changes->post_status = 'publish';
|
||||
$changes->tax_input = array(
|
||||
'wp_theme' => isset( $request['theme'] ) ? $request['theme'] : wp_get_theme()->get_stylesheet(),
|
||||
);
|
||||
} elseif ( 'custom' !== $template->source ) {
|
||||
$changes->post_name = $template->slug;
|
||||
$changes->post_type = $this->post_type;
|
||||
$changes->post_status = 'publish';
|
||||
$changes->tax_input = array(
|
||||
'wp_theme' => $template->theme,
|
||||
);
|
||||
} else {
|
||||
$changes->post_name = $template->slug;
|
||||
$changes->ID = $template->wp_id;
|
||||
$changes->post_status = 'publish';
|
||||
}
|
||||
if ( isset( $request['content'] ) ) {
|
||||
$changes->post_content = $request['content'];
|
||||
} elseif ( null !== $template && 'custom' !== $template->source ) {
|
||||
$changes->post_content = $template->content;
|
||||
}
|
||||
if ( isset( $request['title'] ) ) {
|
||||
$changes->post_title = $request['title'];
|
||||
} elseif ( null !== $template && 'custom' !== $template->source ) {
|
||||
$changes->post_title = $template->title;
|
||||
}
|
||||
if ( isset( $request['description'] ) ) {
|
||||
$changes->post_excerpt = $request['description'];
|
||||
} elseif ( null !== $template && 'custom' !== $template->source ) {
|
||||
$changes->post_excerpt = $template->description;
|
||||
}
|
||||
|
||||
return $changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a single template output for response
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_Block_Template $template Template instance.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response $data
|
||||
*/
|
||||
public function prepare_item_for_response( $template, $request ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
$result = array(
|
||||
'id' => $template->id,
|
||||
'theme' => $template->theme,
|
||||
'content' => array( 'raw' => $template->content ),
|
||||
'slug' => $template->slug,
|
||||
'source' => $template->source,
|
||||
'type' => $template->type,
|
||||
'description' => $template->description,
|
||||
'title' => array(
|
||||
'raw' => $template->title,
|
||||
'rendered' => $template->title,
|
||||
),
|
||||
'status' => $template->status,
|
||||
'wp_id' => $template->wp_id,
|
||||
'has_theme_file' => $template->has_theme_file,
|
||||
);
|
||||
|
||||
if ( 'wp_template_part' === $template->type ) {
|
||||
$result['area'] = $template->area;
|
||||
}
|
||||
|
||||
$result = $this->add_additional_fields_to_object( $result, $request );
|
||||
|
||||
$response = rest_ensure_response( $result );
|
||||
$links = $this->prepare_links( $template->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.8.0
|
||||
*
|
||||
* @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 ),
|
||||
),
|
||||
'collection' => array(
|
||||
'href' => rest_url( $base ),
|
||||
),
|
||||
'about' => array(
|
||||
'href' => rest_url( 'wp/v2/types/' . $this->post_type ),
|
||||
),
|
||||
);
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link relations available for the post and current user.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @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( 'unfiltered_html' ) ) {
|
||||
$rels[] = 'https://api.w.org/action-unfiltered-html';
|
||||
}
|
||||
|
||||
return $rels;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the query params for the posts collection.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @return array Collection parameters.
|
||||
*/
|
||||
public function get_collection_params() {
|
||||
return array(
|
||||
'context' => $this->get_context_param(),
|
||||
'wp_id' => array(
|
||||
'description' => __( 'Limit to the specified post id.' ),
|
||||
'type' => 'integer',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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' => $this->post_type,
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'description' => __( 'ID of template.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'slug' => array(
|
||||
'description' => __( 'Unique slug identifying the template.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'required' => true,
|
||||
'minLength' => 1,
|
||||
'pattern' => '[a-zA-Z_\-]+',
|
||||
),
|
||||
'theme' => array(
|
||||
'description' => __( 'Theme identifier for the template.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
),
|
||||
'source' => array(
|
||||
'description' => __( 'Source of template' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'content' => array(
|
||||
'description' => __( 'Content of template.' ),
|
||||
'type' => array( 'object', 'string' ),
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
),
|
||||
'title' => array(
|
||||
'description' => __( 'Title of template.' ),
|
||||
'type' => array( 'object', 'string' ),
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
),
|
||||
'description' => array(
|
||||
'description' => __( 'Description of template.' ),
|
||||
'type' => 'string',
|
||||
'default' => '',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
),
|
||||
'status' => array(
|
||||
'description' => __( 'Status of template.' ),
|
||||
'type' => 'string',
|
||||
'default' => 'publish',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
),
|
||||
'wp_id' => array(
|
||||
'description' => __( 'Post ID.' ),
|
||||
'type' => 'integer',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
'has_theme_file' => array(
|
||||
'description' => __( 'Theme file exists.' ),
|
||||
'type' => 'bool',
|
||||
'context' => array( 'embed', 'view', 'edit' ),
|
||||
'readonly' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$this->schema = $schema;
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,617 @@
|
||||
<?php
|
||||
/**
|
||||
* REST API: WP_REST_Themes_Controller class
|
||||
*
|
||||
* @package WordPress
|
||||
* @subpackage REST_API
|
||||
* @since 5.0.0
|
||||
*/
|
||||
|
||||
/**
|
||||
* Core class used to manage themes via the REST API.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*
|
||||
* @see WP_REST_Controller
|
||||
*/
|
||||
class WP_REST_Themes_Controller extends WP_REST_Controller {
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->namespace = 'wp/v2';
|
||||
$this->rest_base = 'themes';
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the routes for 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,
|
||||
'/' . $this->rest_base . '/(?P<stylesheet>[\w-]+)',
|
||||
array(
|
||||
'args' => array(
|
||||
'stylesheet' => array(
|
||||
'description' => __( "The theme's stylesheet. This uniquely identifies the theme." ),
|
||||
'type' => 'string',
|
||||
),
|
||||
),
|
||||
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' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 bool|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 bool|WP_Error Whether the theme can be read.
|
||||
*/
|
||||
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
|
||||
*
|
||||
* @param WP_Theme $theme Theme object.
|
||||
* @param WP_REST_Request $request Request object.
|
||||
* @return WP_REST_Response Response object.
|
||||
*/
|
||||
public function prepare_item_for_response( $theme, $request ) {
|
||||
$data = array();
|
||||
$fields = $this->get_fields_for_response( $request );
|
||||
|
||||
if ( 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;
|
||||
}
|
||||
}
|
||||
|
||||
$data = $this->add_additional_fields_to_object( $data, $request );
|
||||
|
||||
// Wrap the data in a response object.
|
||||
$response = rest_ensure_response( $data );
|
||||
|
||||
$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 ) {
|
||||
return 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 ) ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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',
|
||||
),
|
||||
),
|
||||
),
|
||||
'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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,569 @@
|
||||
<?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' => function( $string ) {
|
||||
$array = array();
|
||||
wp_parse_str( $string, $array );
|
||||
return $array;
|
||||
},
|
||||
),
|
||||
),
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'permission_callback' => array( $this, 'get_item_permissions_check' ),
|
||||
'callback' => array( $this, 'encode_form_data' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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
|
||||
*
|
||||
* @param array $widget_type 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( $widget_type, $request ) {
|
||||
$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 );
|
||||
|
||||
$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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,807 @@
|
||||
<?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 {
|
||||
|
||||
/**
|
||||
* 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' => array( 'v1' => true ),
|
||||
'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' => array( 'v1' => true ),
|
||||
'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 ) {
|
||||
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 ) {
|
||||
retrieve_widgets();
|
||||
|
||||
$prepared = array();
|
||||
|
||||
foreach ( wp_get_sidebars_widgets() as $sidebar_id => $widget_ids ) {
|
||||
if ( isset( $request['sidebar'] ) && $sidebar_id !== $request['sidebar'] ) {
|
||||
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 ) {
|
||||
return $this->permissions_check( $request );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
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();
|
||||
|
||||
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();
|
||||
|
||||
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 $response The response data.
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 );
|
||||
|
||||
$response->add_links( $this->prepare_links( $prepared ) );
|
||||
|
||||
/**
|
||||
* Filters the REST API response for a widget.
|
||||
*
|
||||
* @since 5.8.0
|
||||
*
|
||||
* @param WP_REST_Response $response The response object.
|
||||
* @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( 'view', 'edit', 'embed' ),
|
||||
'default' => null,
|
||||
'properties' => array(
|
||||
'encoded' => array(
|
||||
'description' => __( 'Base64 encoded representation of the instance settings.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'hash' => array(
|
||||
'description' => __( 'Cryptographic hash of the instance settings.' ),
|
||||
'type' => 'string',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
'raw' => array(
|
||||
'description' => __( 'Unencoded instance settings, if supported.' ),
|
||||
'type' => 'object',
|
||||
'context' => array( 'view', 'edit', 'embed' ),
|
||||
),
|
||||
),
|
||||
),
|
||||
'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' => function( $string ) {
|
||||
$array = array();
|
||||
wp_parse_str( $string, $array );
|
||||
return $array;
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return $this->add_additional_fields_schema( $this->schema );
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user