initial
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API;
|
||||
|
||||
use Automattic\Jetpack_Boost\REST_API\Contracts\Endpoint;
|
||||
|
||||
class REST_API {
|
||||
|
||||
/**
|
||||
* @var Route[]
|
||||
*/
|
||||
protected $routes = array();
|
||||
|
||||
/**
|
||||
* @param Endpoint[] $routes
|
||||
*/
|
||||
public function __construct( $routes ) {
|
||||
foreach ( $routes as $route_class ) {
|
||||
$this->routes[] = new Route( $route_class );
|
||||
}
|
||||
}
|
||||
|
||||
public function register_rest_routes() {
|
||||
foreach ( $this->routes as $route ) {
|
||||
$route->register_rest_route();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Endpoint|Endpoint[]|string $endpoints
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register( $endpoints ) {
|
||||
// If endpoints are passed as a string,
|
||||
// (array) will convert it to an array.
|
||||
$rest_api = new REST_API( (array) $endpoints );
|
||||
add_action( 'rest_api_init', array( $rest_api, 'register_rest_routes' ) );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API;
|
||||
|
||||
class Route {
|
||||
|
||||
/**
|
||||
* @var \Automattic\Jetpack_Boost\REST_API\Contracts\Endpoint
|
||||
*/
|
||||
protected $endpoint;
|
||||
|
||||
protected $permissions;
|
||||
|
||||
public function __construct( $endpoint ) {
|
||||
$this->endpoint = new $endpoint();
|
||||
$this->permissions = $this->endpoint->permissions();
|
||||
}
|
||||
|
||||
public function register_rest_route() {
|
||||
register_rest_route(
|
||||
JETPACK_BOOST_REST_NAMESPACE,
|
||||
JETPACK_BOOST_REST_PREFIX . '/' . $this->endpoint->name(),
|
||||
array(
|
||||
'methods' => $this->endpoint->request_methods(),
|
||||
'callback' => array( $this->endpoint, 'response' ),
|
||||
'permission_callback' => array( $this, 'verify_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is going to run and try to verify that
|
||||
* all the permission callbacks are successful.
|
||||
*
|
||||
* If any of them fail - return false immediately.
|
||||
*
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function verify_permissions( $request ) {
|
||||
foreach ( $this->permissions as $permission ) {
|
||||
if ( true !== $permission->verify( $request ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Contracts;
|
||||
|
||||
interface Endpoint {
|
||||
|
||||
public function name();
|
||||
|
||||
public function request_methods();
|
||||
|
||||
public function response( $request );
|
||||
|
||||
public function permissions();
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Contracts;
|
||||
|
||||
/**
|
||||
* Interface for defining classes that provide endpoints which are always available.
|
||||
*/
|
||||
interface Has_Always_Available_Endpoints {
|
||||
|
||||
/**
|
||||
* Retrieves a list of endpoints that are always available.
|
||||
*
|
||||
* @return Endpoint[]
|
||||
*/
|
||||
public function get_always_available_endpoints();
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Contracts;
|
||||
|
||||
interface Has_Endpoints {
|
||||
|
||||
/**
|
||||
* @return Endpoint[]
|
||||
*/
|
||||
public function get_endpoints();
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Contracts;
|
||||
|
||||
/**
|
||||
* API Endpoints have permissions that are checked by WordPress on `permission_callback`.
|
||||
*
|
||||
* These permissions repeat themselves, for example:
|
||||
* * current_user_can
|
||||
* * wp_verify_nonce
|
||||
*
|
||||
* And in the case of nonces - they also need to interact with the rest of the application.
|
||||
* Permission contract helps make the permission callbacks more predictable.
|
||||
* This is especially necessary to deal with nonces
|
||||
* (or more on that, read `permissions/class-nonce.php`
|
||||
*/
|
||||
interface Permission {
|
||||
/**
|
||||
* A method to verify whether this request
|
||||
* can be run in the current environment.
|
||||
*
|
||||
* @param \WP_REST_Request $request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function verify( $request );
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Create a new request for cornerstone pages.
|
||||
*
|
||||
* Handler for GET '/list-cornerstone-pages'.
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Endpoints;
|
||||
|
||||
use Automattic\Jetpack_Boost\Lib\Cornerstone\Cornerstone_Utils;
|
||||
use Automattic\Jetpack_Boost\REST_API\Contracts\Endpoint;
|
||||
use Automattic\Jetpack_Boost\REST_API\Permissions\Signed_With_Blog_Token;
|
||||
|
||||
class List_Cornerstone_Pages implements Endpoint {
|
||||
|
||||
public function request_methods() {
|
||||
return \WP_REST_Server::READABLE;
|
||||
}
|
||||
|
||||
public function response( $_request ) {
|
||||
return rest_ensure_response( Cornerstone_Utils::get_list() );
|
||||
}
|
||||
|
||||
public function permissions() {
|
||||
return array(
|
||||
new Signed_With_Blog_Token(),
|
||||
);
|
||||
}
|
||||
|
||||
public function name() {
|
||||
return '/list-cornerstone-pages';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Create a new request for LCP analysis data.
|
||||
*
|
||||
* Handler for GET '/list-lcp-analysis'.
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Endpoints;
|
||||
|
||||
use Automattic\Jetpack_Boost\Modules\Optimizations\Lcp\LCP_Utils;
|
||||
use Automattic\Jetpack_Boost\REST_API\Contracts\Endpoint;
|
||||
use Automattic\Jetpack_Boost\REST_API\Permissions\Signed_With_Blog_Token;
|
||||
|
||||
class List_LCP_Analysis implements Endpoint {
|
||||
|
||||
public function request_methods() {
|
||||
return \WP_REST_Server::READABLE;
|
||||
}
|
||||
|
||||
public function response( $_request ) {
|
||||
return rest_ensure_response( LCP_Utils::get_analysis_data() );
|
||||
}
|
||||
|
||||
public function permissions() {
|
||||
return array(
|
||||
new Signed_With_Blog_Token(),
|
||||
);
|
||||
}
|
||||
|
||||
public function name() {
|
||||
return '/list-lcp-analysis';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
/**
|
||||
* Create a new request for site urls.
|
||||
*
|
||||
* Handler for GET '/list-site-urls'.
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Endpoints;
|
||||
|
||||
use Automattic\Jetpack_Boost\Lib\Site_Urls;
|
||||
use Automattic\Jetpack_Boost\REST_API\Contracts\Endpoint;
|
||||
use Automattic\Jetpack_Boost\REST_API\Permissions\Signed_With_Blog_Token;
|
||||
|
||||
class List_Site_Urls implements Endpoint {
|
||||
|
||||
public function request_methods() {
|
||||
return \WP_REST_Server::READABLE;
|
||||
}
|
||||
|
||||
public function response( $_request ) {
|
||||
return rest_ensure_response( Site_Urls::get() );
|
||||
}
|
||||
|
||||
public function permissions() {
|
||||
return array(
|
||||
new Signed_With_Blog_Token(),
|
||||
);
|
||||
}
|
||||
|
||||
public function name() {
|
||||
return '/list-site-urls';
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
/**
|
||||
* Create a new request for source providers.
|
||||
*
|
||||
* Handler for GET '/list-source-providers'.
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Endpoints;
|
||||
|
||||
use Automattic\Jetpack_Boost\Lib\Critical_CSS\Source_Providers\Source_Providers;
|
||||
use Automattic\Jetpack_Boost\REST_API\Contracts\Endpoint;
|
||||
use Automattic\Jetpack_Boost\REST_API\Permissions\Signed_With_Blog_Token;
|
||||
|
||||
class List_Source_Providers implements Endpoint {
|
||||
|
||||
public function request_methods() {
|
||||
return \WP_REST_Server::READABLE;
|
||||
}
|
||||
|
||||
public function response( $_request ) {
|
||||
$providers = new Source_Providers();
|
||||
return rest_ensure_response( $providers->get_provider_sources() );
|
||||
}
|
||||
|
||||
public function permissions() {
|
||||
return array(
|
||||
new Signed_With_Blog_Token(),
|
||||
);
|
||||
}
|
||||
|
||||
public function name() {
|
||||
return '/list-source-providers';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
/**
|
||||
* Save generated cloud critical CSS.
|
||||
*
|
||||
* This endpoint is used by WP.com to push the generated CSS to the boost plugin.
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Endpoints;
|
||||
|
||||
use Automattic\Jetpack_Boost\Lib\Critical_CSS\Critical_CSS_State;
|
||||
use Automattic\Jetpack_Boost\Lib\Critical_CSS\Critical_CSS_Storage;
|
||||
use Automattic\Jetpack_Boost\REST_API\Contracts\Endpoint;
|
||||
use Automattic\Jetpack_Boost\REST_API\Permissions\Signed_With_Blog_Token;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Handler for POST cloud-css/update. Expects the following body params:
|
||||
* - success: boolean - False if the whole Critical CSS job failed.
|
||||
* - message: string containing an error message if success is false.
|
||||
* - providers: Object containing one result for each provider_key:
|
||||
*
|
||||
* Each provider key contains:
|
||||
* - success: boolean - False if this provider key failed.
|
||||
* - data: Either a successful CSS block, or a CSS error.
|
||||
*
|
||||
* Each CSS block looks like:
|
||||
* - css: string - containing CSS data.
|
||||
*
|
||||
* Each CSS error looks like:
|
||||
* - urls: Object describing each URL which failed. Keys are URLs.
|
||||
*
|
||||
* Each URL failure looks like:
|
||||
* - message: string - containing an error message.
|
||||
* - type: string - machine readable error type.
|
||||
* - meta: Object - JSON string compatible object containing extra metadata for consumption in the UI.
|
||||
*/
|
||||
class Update_Cloud_CSS implements Endpoint {
|
||||
|
||||
public function name() {
|
||||
return 'cloud-css/update';
|
||||
}
|
||||
|
||||
public function request_methods() {
|
||||
return WP_REST_Server::EDITABLE;
|
||||
}
|
||||
|
||||
public function response( $request ) {
|
||||
$state = new Critical_CSS_State();
|
||||
$storage = new Critical_CSS_Storage();
|
||||
$params = $request->get_params();
|
||||
$providers = empty( $params['providers'] ) || ! is_array( $params['providers'] ) ? array() : $params['providers'];
|
||||
$api_successful = array( 'success' => true );
|
||||
|
||||
// If success is false, the whole Cloud CSS generation process failed.
|
||||
if ( empty( $params['success'] ) ) {
|
||||
if ( empty( $params['message'] ) || ! is_string( $params['message'] ) ) {
|
||||
$error = __( 'An unknown error occurred', 'jetpack-boost' );
|
||||
} else {
|
||||
$error = $params['message'];
|
||||
}
|
||||
|
||||
$state->set_error( $error );
|
||||
$state->save();
|
||||
|
||||
return $api_successful;
|
||||
}
|
||||
|
||||
// Update each provider.
|
||||
foreach ( $providers as $provider_key => $result ) {
|
||||
if ( ! isset( $result['data'] ) ) {
|
||||
return new \WP_Error( 'invalid_data', __( 'Invalid request; missing data element', 'jetpack-boost' ) );
|
||||
}
|
||||
|
||||
$data = $result['data'];
|
||||
|
||||
// Success
|
||||
if ( ! empty( $result['success'] ) && ! empty( $data['css'] ) && is_string( $data['css'] ) ) {
|
||||
$storage->store_css( $provider_key, $data['css'] );
|
||||
$state->set_provider_success( $provider_key );
|
||||
continue;
|
||||
}
|
||||
|
||||
// Failures must have an array of urls.
|
||||
if ( empty( $data['urls'] ) || ! is_array( $data['urls'] ) ) {
|
||||
return new \WP_Error( 'invalid_data', __( 'Invalid request; missing urls element', 'jetpack-boost' ) );
|
||||
}
|
||||
|
||||
$state->set_provider_errors( $provider_key, $this->flatten_url_errors( $data['urls'] ) );
|
||||
}
|
||||
|
||||
// Save the state changes.
|
||||
$state->save();
|
||||
|
||||
return $api_successful;
|
||||
}
|
||||
|
||||
/**
|
||||
* Errors arrive from Shield in an associative array with the URL as the key.
|
||||
* This function flattens the array into a list of assoc arrays with the URL in each member.
|
||||
*/
|
||||
private function flatten_url_errors( $errors ) {
|
||||
$flat_errors = array();
|
||||
|
||||
foreach ( $errors as $url => $error ) {
|
||||
$flat_errors[] = array_merge( array( 'url' => $url ), $error );
|
||||
}
|
||||
|
||||
return $flat_errors;
|
||||
}
|
||||
|
||||
public function permissions() {
|
||||
return array(
|
||||
new Signed_With_Blog_Token(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
/**
|
||||
* Save generated LCP data.
|
||||
*
|
||||
* This endpoint is used by WP.com to push the generated LCP data to the boost plugin.
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Endpoints;
|
||||
|
||||
use Automattic\Jetpack_Boost\Modules\Optimizations\Lcp\LCP_State;
|
||||
use Automattic\Jetpack_Boost\Modules\Optimizations\Lcp\LCP_Storage;
|
||||
use Automattic\Jetpack_Boost\REST_API\Contracts\Endpoint;
|
||||
use Automattic\Jetpack_Boost\REST_API\Permissions\Signed_With_Blog_Token;
|
||||
use WP_REST_Server;
|
||||
|
||||
/**
|
||||
* Handler for POST lcp/update. Expects the following body params:
|
||||
* - success: boolean - False if the whole LCP job failed.
|
||||
* - message: string - Error message if success is false.
|
||||
* - data: object - All results from the LCP job:
|
||||
*
|
||||
* Each data key contains:
|
||||
* - key: string - The key of the page.
|
||||
* - url: string - The URL of the page.
|
||||
* - devices: object - The LCP data for both mobile and desktop.
|
||||
*
|
||||
* Each device key contains:
|
||||
* - success: boolean - False if this device key failed.
|
||||
* - element: string - The selector of the LCP element.
|
||||
* - type: string - The type of the LCP element. Either 'img' or 'background-image'.
|
||||
* - url: string - Only for 'img' elements. The URL of LCP element.
|
||||
* - html: string - The HTML of the LCP element.
|
||||
* - report: object - The full report of the LCP element.
|
||||
*/
|
||||
class Update_LCP implements Endpoint {
|
||||
|
||||
public function name() {
|
||||
return 'lcp/update';
|
||||
}
|
||||
|
||||
public function request_methods() {
|
||||
return WP_REST_Server::EDITABLE;
|
||||
}
|
||||
|
||||
public function response( $request ) {
|
||||
$state = new LCP_State();
|
||||
$storage = new LCP_Storage();
|
||||
$params = $request->get_params();
|
||||
$pages = empty( $params['data'] ) || ! is_array( $params['data'] ) ? array() : $params['data'];
|
||||
$api_successful = array( 'success' => true );
|
||||
|
||||
// If success is false, the whole LCP generation process failed.
|
||||
if ( empty( $params['success'] ) ) {
|
||||
if ( empty( $params['message'] ) || ! is_string( $params['message'] ) ) {
|
||||
$error = __( 'An unknown error occurred', 'jetpack-boost' );
|
||||
} else {
|
||||
$error = $params['message'];
|
||||
}
|
||||
|
||||
$state->set_error( $error );
|
||||
$state->save();
|
||||
|
||||
return $api_successful;
|
||||
}
|
||||
|
||||
foreach ( $pages as $entry ) {
|
||||
if ( $entry['success'] ) {
|
||||
$state->set_page_success( $entry['key'] );
|
||||
} else {
|
||||
$errors = array();
|
||||
foreach ( $entry['reports'] as $report ) {
|
||||
if ( isset( $report['success'] ) && false === $report['success'] && ! empty( $report['data'] ) ) {
|
||||
$errors[] = $report['data'];
|
||||
}
|
||||
}
|
||||
|
||||
$state->set_page_errors( $entry['key'], $errors );
|
||||
}
|
||||
|
||||
// Store the LCP data for this page.
|
||||
$storage->store_lcp( $entry['key'], $entry['reports'] );
|
||||
|
||||
// Failures must have an array of urls.
|
||||
// @TODO: figure out what to do with failures.
|
||||
}
|
||||
|
||||
// Save the state changes.
|
||||
$state->save();
|
||||
|
||||
return $api_successful;
|
||||
}
|
||||
|
||||
public function permissions() {
|
||||
return array(
|
||||
new Signed_With_Blog_Token(),
|
||||
);
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Permissions;
|
||||
|
||||
use Automattic\Jetpack_Boost\REST_API\Contracts\Permission;
|
||||
|
||||
class Current_User_Admin implements Permission {
|
||||
|
||||
// $request is required to adhere to the contract.
|
||||
//phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
public function verify( $request ) {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Permissions;
|
||||
|
||||
use Automattic\Jetpack_Boost\REST_API\Contracts\Permission;
|
||||
|
||||
/**
|
||||
* Nonces are tricky in REST.
|
||||
*
|
||||
* `rest_api_init` action is only tirgered when visiting an URL that looks like a REST Endopint.
|
||||
* This means that if nonces are generated there, they won't be available in regular
|
||||
* `init` or `admin_init` parts of the app. But that's exactly where we need them.
|
||||
*
|
||||
* So we need a way to both generate named nonces, but also know what nonces
|
||||
* we have generated and pass them to the front-end of the application.
|
||||
*
|
||||
* To do this without scattering nonce names across the application,
|
||||
* this class is using static properties while complying to with
|
||||
* the Permission contract and keeping track of the nonces
|
||||
* that have been generated, that way they can be
|
||||
* retrieved later using:
|
||||
*
|
||||
* Nonce::get_generated_nonces()
|
||||
*/
|
||||
class Nonce implements Permission {
|
||||
|
||||
/**
|
||||
* WordPress calls nonce keys "actions"
|
||||
*
|
||||
* @var string The nonce key to validate
|
||||
*/
|
||||
private $action;
|
||||
|
||||
/**
|
||||
* @var string Key used by `verify` method to validate \WP_Request
|
||||
*/
|
||||
private $request_key;
|
||||
|
||||
/**
|
||||
* Whenever this class is invoked, it will statically save the generated nonce
|
||||
* So that they can be retrieved and passed to the admin UI
|
||||
*
|
||||
* @var array Associate array of nonces
|
||||
*/
|
||||
private static $saved_nonces = array();
|
||||
|
||||
public function __construct( $action, $request_key = 'nonce' ) {
|
||||
$this->action = $action;
|
||||
$this->request_key = $request_key;
|
||||
$this->generate_nonce();
|
||||
}
|
||||
|
||||
public function verify( $request ) {
|
||||
if ( ! isset( $request[ $this->request_key ] ) ) {
|
||||
return false;
|
||||
}
|
||||
return false !== wp_verify_nonce( $request[ $this->request_key ], $this->action );
|
||||
}
|
||||
|
||||
public function generate_nonce() {
|
||||
$nonce = wp_create_nonce( $this->action );
|
||||
static::save_generated_nonce( $this->action, $nonce );
|
||||
|
||||
return $nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep track of the nonces created using this class.
|
||||
*
|
||||
* @param string $action - The action where this nonce is used.
|
||||
* @param string $nonce - The nonce value.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function save_generated_nonce( $action, $nonce ) {
|
||||
static::$saved_nonces[ $action ] = $nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array Array of saved [action => nonce] pairs.
|
||||
*/
|
||||
public static function get_generated_nonces() {
|
||||
return static::$saved_nonces;
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Automattic\Jetpack_Boost\REST_API\Permissions;
|
||||
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication;
|
||||
use Automattic\Jetpack_Boost\REST_API\Contracts\Permission;
|
||||
|
||||
class Signed_With_Blog_Token implements Permission {
|
||||
|
||||
// $request is required to adhere to the contract.
|
||||
//phpcs:disable VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
public function verify( $request ) {
|
||||
|
||||
/**
|
||||
* Filters the signed with blog token verification.
|
||||
*
|
||||
* @param bool $is_signed True if the request is signed with the blog token, false otherwise.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
return apply_filters(
|
||||
'jetpack_boost_signed_with_blog_token_verify',
|
||||
Rest_Authentication::is_signed_with_blog_token()
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user