370 lines
11 KiB
PHP
370 lines
11 KiB
PHP
<?php
|
|
|
|
namespace ImageOptimization\Classes;
|
|
|
|
use ReflectionClass;
|
|
use WP_Error;
|
|
use WP_REST_Request;
|
|
use WP_REST_Response;
|
|
|
|
if ( ! defined( 'ABSPATH' ) ) {
|
|
exit; // Exit if accessed directly.
|
|
}
|
|
|
|
abstract class Route {
|
|
|
|
/**
|
|
* Should the endpoint be validated for user authentication?
|
|
* If set to TRUE, the default permission callback will make sure the user is logged in and has a valid user id
|
|
* @var bool
|
|
*/
|
|
protected $auth = true;
|
|
|
|
/**
|
|
* holds current authenticated user id
|
|
* @var int
|
|
*/
|
|
protected $current_user_id;
|
|
|
|
protected $override = false;
|
|
|
|
/**
|
|
* Rest Endpoint namespace
|
|
* @var string
|
|
*/
|
|
protected $namespace = 'image-optimizer/v1';
|
|
|
|
/**
|
|
* @var array The valid HTTP methods. The list represents the general REST methods. Do not modify.
|
|
*/
|
|
private $valid_http_methods = [
|
|
'GET',
|
|
'PATCH',
|
|
'POST',
|
|
'PUT',
|
|
'DELETE',
|
|
];
|
|
|
|
/**
|
|
* Route_Base constructor.
|
|
*/
|
|
public function __construct() {
|
|
add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
|
|
}
|
|
|
|
/**
|
|
* rest_api_init
|
|
*
|
|
* Registers REST endpoints.
|
|
* Loops through the REST methods for this route, creates an endpoint configuration for
|
|
* each of them and registers all the endpoints with the WordPress system.
|
|
*/
|
|
public function rest_api_init(): void {
|
|
$methods = $this->get_methods();
|
|
|
|
if ( empty( $methods ) ) {
|
|
return;
|
|
}
|
|
|
|
$callbacks = [];
|
|
|
|
foreach ( $methods as $method ) {
|
|
if ( ! in_array( $method, $this->valid_http_methods, true ) ) {
|
|
continue;
|
|
}
|
|
|
|
$callbacks[] = $this->build_endpoint_method_config( $method );
|
|
}
|
|
|
|
$arguments = $this->get_arguments();
|
|
|
|
if ( ! $callbacks && empty( $arguments ) ) {
|
|
return;
|
|
}
|
|
|
|
$arguments = array_merge( $arguments, $callbacks );
|
|
|
|
register_rest_route( $this->namespace, '/' . $this->get_endpoint() . '/', $arguments, $this->override );
|
|
}
|
|
|
|
/**
|
|
* get_methods
|
|
* Rest Endpoint methods
|
|
*
|
|
* Returns an array of the supported REST methods for this route
|
|
* @return array<string> REST methods being configured for this route.
|
|
*/
|
|
abstract public function get_methods(): array;
|
|
|
|
/**
|
|
* get_callback
|
|
*
|
|
* Returns a reference to the callback function to handle the REST method specified by the /method/ parameter.
|
|
* @param string $method The REST method name
|
|
*
|
|
* @return callable A reference to a member function with the same name as the REST method being passed as a parameter,
|
|
* or a reference to the default function /callback/.
|
|
*/
|
|
public function get_callback_method( string $method ): callable {
|
|
$method_name = strtolower( $method );
|
|
$callback = $this->method_exists_in_current_class( $method_name ) ? $method_name : 'callback';
|
|
return [ $this, $callback ];
|
|
}
|
|
|
|
/**
|
|
* get_permission_callback_method
|
|
*
|
|
* Returns a reference to the permission callback for the method if exists or the default one if it doesn't.
|
|
* @param string $method The REST method name
|
|
*
|
|
* @return callable If a method called (rest-method)_permission_callback exists, returns a reference to it, otherwise
|
|
* returns a reference to the default member method /permission_callback/.
|
|
*/
|
|
public function get_permission_callback_method( string $method ): callable {
|
|
$method_name = strtolower( $method );
|
|
$permission_callback_method = $method_name . '_permission_callback';
|
|
$permission_callback = $this->method_exists_in_current_class( $permission_callback_method ) ? $permission_callback_method : 'permission_callback';
|
|
return [ $this, $permission_callback ];
|
|
}
|
|
|
|
/**
|
|
* maybe_add_args_to_config
|
|
*
|
|
* Checks if the class has a method call (rest-method)_args.
|
|
* If it does, the function calls it and adds its response to the config object passed to the function, under the /args/ key.
|
|
* @param string $method The REST method name being configured
|
|
* @param array $config The configuration object for the method
|
|
*
|
|
* @return array The configuration object for the method, possibly after being amended
|
|
*/
|
|
public function maybe_add_args_to_config( string $method, array $config ): array {
|
|
$method_name = strtolower( $method );
|
|
$method_args = $method_name . '_args';
|
|
if ( $this->method_exists_in_current_class( $method_args ) ) {
|
|
$config['args'] = $this->{$method_args}();
|
|
}
|
|
return $config;
|
|
}
|
|
|
|
/**
|
|
* maybe_add_response_to_swagger
|
|
*
|
|
* If the function method /(rest-method)_response_callback/ exists, adds the filter
|
|
* /swagger_api_response_(namespace with slashes replaced with underscores)_(endpoint with slashes replaced with underscores)/
|
|
* with the aforementioned function method.
|
|
* This filter is used with the WP API Swagger UI plugin to create documentation for the API.
|
|
* The value being passed is an array: [
|
|
'200' => ['description' => 'OK'],
|
|
'404' => ['description' => 'Not Found'],
|
|
'400' => ['description' => 'Bad Request']
|
|
]
|
|
* @param string $method REST method name
|
|
*/
|
|
public function maybe_add_response_to_swagger( string $method ): void {
|
|
$method_name = strtolower( $method );
|
|
$method_response_callback = $method_name . '_response_callback';
|
|
if ( $this->method_exists_in_current_class( $method_response_callback ) ) {
|
|
$response_filter = $method_name . '_' . str_replace(
|
|
'/',
|
|
'_',
|
|
$this->namespace . '/' . $this->get_endpoint()
|
|
);
|
|
add_filter( 'swagger_api_responses_' . $response_filter, [ $this, $method_response_callback ] );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* build_endpoint_method_config
|
|
*
|
|
* Builds a configuration array for the endpoint based on the presence of the callback, permission, additional parameters,
|
|
* and response to Swagger member functions.
|
|
* @param string $method The REST method for the endpoint
|
|
*
|
|
* @return array The endpoint configuration for the method specified by the parameter
|
|
*/
|
|
private function build_endpoint_method_config( string $method ): array {
|
|
$config = [
|
|
'methods' => $method,
|
|
'callback' => $this->get_callback_method( $method ),
|
|
'permission_callback' => $this->get_permission_callback_method( $method ),
|
|
];
|
|
$config = $this->maybe_add_args_to_config( $method, $config );
|
|
return $config;
|
|
}
|
|
|
|
/**
|
|
* method_exists_in_current_class
|
|
*
|
|
* Uses reflection to check if this class has the /method/ method.
|
|
* @param string $method The name of the method being checked.
|
|
*
|
|
* @return bool TRUE if the class has the /method/ method, FALSE otherwise.
|
|
*/
|
|
private function method_exists_in_current_class( string $method ): bool {
|
|
$class_name = get_class( $this );
|
|
try {
|
|
$reflection = new ReflectionClass( $class_name );
|
|
} catch ( \ReflectionException $e ) {
|
|
return false;
|
|
}
|
|
if ( ! $reflection->hasMethod( $method ) ) {
|
|
return false;
|
|
}
|
|
$method_ref = $reflection->getMethod( $method );
|
|
|
|
return ( $method_ref && $class_name === $method_ref->class );
|
|
}
|
|
|
|
/**
|
|
* permission_callback
|
|
* Permissions callback fallback for the endpoint
|
|
* Gets the current user ID and sets the /current_user_id/ property.
|
|
* If the /auth/ property is set to /true/ will make sure that the user is logged in (has an id greater than 0)
|
|
*
|
|
* @param WP_REST_Request $request unused
|
|
*
|
|
* @return bool TRUE, if permission granted, FALSE otherwise
|
|
*/
|
|
public function permission_callback( WP_REST_Request $request ): bool {
|
|
// try to get current user
|
|
$this->current_user_id = get_current_user_id();
|
|
if ( $this->auth ) {
|
|
return $this->current_user_id > 0;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* callback
|
|
* Fallback callback function, returns a response consisting of the string /ok/.
|
|
*
|
|
* @param WP_REST_Request $request unused
|
|
*
|
|
* @return WP_REST_Response Default Response of the string 'ok'.
|
|
*/
|
|
public function callback( WP_REST_Request $request ): WP_REST_Response {
|
|
return rest_ensure_response( [ 'OK' ] );
|
|
}
|
|
|
|
/**
|
|
* respond_wrong_method
|
|
*
|
|
* Creates a WordPress error object with the /rest_no_route/ code and the message and code supplied or the defaults.
|
|
* @param null $message The error message for the wrong method.
|
|
* Optional.
|
|
* Defaults to null, which makes sets the message to /No route was found matching the URL and request method/
|
|
* @param int $code The HTTP status code.
|
|
* Optional.
|
|
* Defaults to 404 (Not found).
|
|
*
|
|
* @return WP_Error The WordPress error object with the error message and status code supplied
|
|
*/
|
|
public function respond_wrong_method( $message = null, int $code = 404 ): WP_Error {
|
|
if ( null === $message ) {
|
|
$message = 'No route was found matching the URL and request method';
|
|
}
|
|
|
|
return new WP_Error( 'rest_no_route', $message, [ 'status' => $code ] );
|
|
}
|
|
|
|
/**
|
|
* respond_with_code
|
|
* Create a new /WP_REST_Response/ object with the specified data and HTTP response code.
|
|
*
|
|
* @param array|null $data The data to return in this response
|
|
* @param int $code The HTTP response code.
|
|
* Optional.
|
|
* Defaults to 200 (OK).
|
|
*
|
|
* @return WP_REST_Response The WordPress response object loaded with the data and the response code.
|
|
*/
|
|
public function respond_with_code( ?array $data = null, int $code = 200 ): WP_REST_Response {
|
|
return new WP_REST_Response( $data, $code );
|
|
}
|
|
|
|
/**
|
|
* get_user_from_request
|
|
*
|
|
* Returns the current user object.
|
|
* Depends on the property /current_user_id/ to be set.
|
|
* @return WP_User|false The user object or false if not found or on error.
|
|
*/
|
|
public function get_user_from_request() {
|
|
return get_user_by( 'id', $this->current_user_id );
|
|
}
|
|
|
|
/**
|
|
* get_arguments
|
|
* Rest Endpoint extra arguments
|
|
* @return array Additional arguments for the route configuration
|
|
*/
|
|
public function get_arguments(): array {
|
|
return [];
|
|
}
|
|
|
|
/**
|
|
* get_endpoint
|
|
* Rest route Endpoint
|
|
* @return string Endpoint uri component (comes after the route namespace)
|
|
*/
|
|
abstract public function get_endpoint(): string;
|
|
|
|
/**
|
|
* get_name
|
|
* @return string The name of the route
|
|
*/
|
|
abstract public function get_name(): string;
|
|
|
|
public function get_self_url( $endpoint = '' ): string {
|
|
return rest_url( $this->namespace . '/' . $endpoint );
|
|
}
|
|
|
|
public function respond_success_json( $data = [] ): WP_REST_Response {
|
|
return new WP_REST_Response([
|
|
'success' => true,
|
|
'data' => $data,
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* @param array{message: string, code: string} $data
|
|
*
|
|
* @return WP_Error
|
|
*/
|
|
public function respond_error_json( array $data ): WP_Error {
|
|
if ( ! isset( $data['message'] ) || ! isset( $data['code'] ) ) {
|
|
_doing_it_wrong(
|
|
__FUNCTION__,
|
|
esc_html__( 'Both `message` and `code` keys must be provided', 'image-optimization' ),
|
|
'1.0.0'
|
|
); // @codeCoverageIgnore
|
|
}
|
|
|
|
return new WP_Error(
|
|
$data['code'] ?? 'internal_server_error',
|
|
$data['message'] ?? esc_html__( 'Internal server error', 'image-optimization' ),
|
|
);
|
|
}
|
|
|
|
public function verify_nonce( $nonce = '', $name = '' ) {
|
|
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), $name ) ) {
|
|
return $this->respond_error_json([
|
|
'message' => esc_html__( 'Invalid nonce', 'image-optimization' ),
|
|
'code' => 'bad_request',
|
|
]);
|
|
}
|
|
}
|
|
|
|
public function verify_nonce_and_capability( $nonce = '', $name = '', $capability = 'manage_options' ) {
|
|
$this->verify_nonce( $nonce, $name );
|
|
|
|
if ( ! current_user_can( $capability ) ) {
|
|
return $this->respond_error_json([
|
|
'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'image-optimization' ),
|
|
'code' => 'bad_request',
|
|
]);
|
|
}
|
|
}
|
|
}
|