PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / 1.4.1
Image Optimizer – Compress Images and Convert to WebP or AVIF v1.4.1
1.7.7 1.7.6 1.7.5 1.7.4 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.2.0 1.2.1 1.3.0 1.4.0 1.4.1 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 All 33 releases
image-optimization / classes / rest / route.php

route.php in Image Optimizer – Compress Images and Convert to WebP or AVIF 1.4.1, at classes/rest/route.php

424 lines 13.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace ImageOptimization\Classes\Rest;
4
5 use ReflectionClass;
6 use WP_Error;
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use WP_User;
10
11 abstract class Route {
12
13 /**
14 * Should the endpoint be validated for user authentication?
15 * If set to TRUE, the default permission callback will make sure the user is logged in and has a valid user id
16 * @var bool
17 */
18 protected $auth = true;
19
20 /**
21 * holds current authenticated user id
22 * @var int
23 */
24 protected $current_user_id;
25
26
27 /**
28 * Should the endpoint override an existing one?
29 * @var bool
30 */
31 protected bool $override = false;
32
33 /**
34 * Rest Endpoint namespace
35 * @var string
36 */
37 protected string $namespace = 'image-optimizer/v1';
38
39 /**
40 * @var array The valid HTTP methods. The list represents the general REST methods. Do not modify.
41 */
42 private array $valid_http_methods = [
43 'GET',
44 'PATCH',
45 'POST',
46 'PUT',
47 'DELETE',
48 'HEAD',
49 ];
50
51 /**
52 * Route_Base constructor.
53 */
54 public function __construct() {
55 add_action( 'rest_api_init', [ $this, 'rest_api_init' ] );
56 }
57
58 /**
59 * rest_api_init
60 *
61 * Registers REST endpoints.
62 * Loops through the REST methods for this route, creates an endpoint configuration for
63 * each of them and registers all the endpoints with the WordPress system.
64 */
65 public function rest_api_init(): void {
66 $methods = $this->get_methods();
67 if ( empty( $methods ) ) {
68 return;
69 }
70
71 $callbacks = false;
72 foreach ( (array) $methods as $method ) {
73 if ( ! in_array( $method, $this->valid_http_methods ) ) {
74 continue;
75 }
76 if ( $method && ! $callbacks ) {
77 $callbacks = [];
78 }
79 $callbacks[] = $this->build_endpoint_method_config( $method );
80 }
81
82 $arguments = $this->get_arguments();
83
84 if ( ! $callbacks && empty( $arguments ) ) {
85 return;
86 }
87
88 $arguments = array_merge( $arguments, (array) $callbacks );
89 register_rest_route( $this->namespace, '/' . $this->get_endpoint() . '/', $arguments, $this->override );
90 }
91
92 /**
93 * get_methods
94 * Rest Endpoint methods
95 *
96 * Returns an array of the supported REST methods for this route
97 * @return array<string> REST methods being configured for this route.
98 */
99 abstract public function get_methods(): array;
100
101 /**
102 * get_callback
103 *
104 * Returns a reference to the callback function to handle the REST method specified by the /method/ parameter.
105 * @param string $method The REST method name
106 *
107 * @return callable A reference to a member function with the same name as the REST method being passed as a parameter,
108 * or a reference to the default function /callback/.
109 */
110 public function get_callback_method( string $method ): callable {
111 $method_name = strtolower( $method );
112 $callback = $this->method_exists_in_current_class( $method_name ) ? $method_name : 'callback';
113 return [ $this, $callback ];
114 }
115
116 /**
117 * get_permission_callback_method
118 *
119 * Returns a reference to the permission callback for the method if exists or the default one if it doesn't.
120 * @param string $method The REST method name
121 *
122 * @return callable If a method called (rest-method)_permission_callback exists, returns a reference to it, otherwise
123 * returns a reference to the default member method /permission_callback/.
124 */
125 public function get_permission_callback_method( string $method ): callable {
126 $method_name = strtolower( $method );
127 $permission_callback_method = $method_name . '_permission_callback';
128 $permission_callback = $this->method_exists_in_current_class( $permission_callback_method ) ? $permission_callback_method : 'permission_callback';
129 return [ $this, $permission_callback ];
130 }
131
132 /**
133 * maybe_add_args_to_config
134 *
135 * Checks if the class has a method call (rest-method)_args.
136 * If it does, the function calls it and adds its response to the config object passed to the function, under the /args/ key.
137 * if the function (rest-method)_consumes exists, it will add the response to the config object under the /consumes/ key.
138 * if the function (rest-method)_produces exists, it will add the response to the config object under the /produces/ key.
139 * if the function (rest-method)_summary exists, it will add the response to the config object under the /summary/ key.
140 * if the function (rest-method)_description exists, it will add the response to the config object under the /description/ key.
141 * @param string $method The REST method name being configured
142 * @param array $config The configuration object for the method
143 *
144 * @return array The configuration object for the method, possibly after being amended
145 */
146 public function maybe_add_args_to_config( string $method, array $config ): array {
147 $method_name = strtolower( $method );
148 $method_args = $method_name . '_args';
149 if ( $this->method_exists_in_current_class( $method_args ) ) {
150 $config['args'] = $this->{$method_args}();
151 }
152 $config['consumes'] =[ 'application/json' ];
153 if ( $this->method_exists_in_current_class( $method . '_consumes' ) ) {
154 $config['consumes'] = $this->{$method . '_consumes'}();
155 }
156 $config['produces'] = [ 'application/json' ];
157 if ( $this->method_exists_in_current_class( $method . '_produces' ) ) {
158 $config['produces'] = $this->{$method . '_produces'}();
159 }
160 if ( $this->method_exists_in_current_class( $method . '_summary' ) ) {
161 $config['summary'] = $this->{$method . '_summary'}();
162 }
163 if ( $this->method_exists_in_current_class( $method . '_description' ) ) {
164 $config['description'] = $this->{$method . '_description'}();
165 }
166 return $config;
167 }
168
169 /**
170 * maybe_add_response_to_swagger
171 *
172 * If the function method /(rest-method)_response_callback/ exists, adds the filter
173 * /swagger_api_response_(namespace with slashes replaced with underscores)_(endpoint with slashes replaced with underscores)/
174 * with the aforementioned function method.
175 * This filter is used with the WP API Swagger UI plugin to create documentation for the API.
176 * The value being passed is an array: [
177 '200' => ['description' => 'OK'],
178 '404' => ['description' => 'Not Found'],
179 '400' => ['description' => 'Bad Request']
180 ]
181 * @param string $method REST method name
182 */
183 public function maybe_add_response_to_swagger( string $method ): void {
184 $method_name = strtolower( $method );
185 $method_response_callback = $method_name . '_response_callback';
186 if ( $this->method_exists_in_current_class( $method_response_callback ) ) {
187 $response_filter = $method_name . '_' . str_replace(
188 '/',
189 '_',
190 $this->namespace . '/' . $this->get_endpoint()
191 );
192 add_filter( 'swagger_api_responses_' . $response_filter, [ $this, $method_response_callback ] );
193 }
194 }
195
196 /**
197 * build_endpoint_method_config
198 *
199 * Builds a configuration array for the endpoint based on the presence of the callback, permission, additional parameters,
200 * and response to Swagger member functions.
201 * @param string $method The REST method for the endpoint
202 *
203 * @return array The endpoint configuration for the method specified by the parameter
204 */
205 private function build_endpoint_method_config( string $method ): array {
206 $config = [
207 'methods' => $method,
208 'callback' => $this->get_callback_method( $method ),
209 'permission_callback' => $this->get_permission_callback_method( $method ),
210 ];
211 $this->maybe_add_response_to_swagger( $method );
212 return $this->maybe_add_args_to_config( $method, $config );
213
214 }
215
216 /**
217 * method_exists_in_current_class
218 *
219 * Uses reflection to check if this class has the /method/ method.
220 * @param string $method The name of the method being checked.
221 *
222 * @return bool TRUE if the class has the /method/ method, FALSE otherwise.
223 */
224 private function method_exists_in_current_class( string $method ): bool {
225 $class_name = get_class( $this );
226 try {
227 $reflection = new ReflectionClass( $class_name );
228 } catch( \ReflectionException $e ) {
229 return false;
230 }
231 if ( ! $reflection->hasMethod( $method ) ) {
232 return false;
233 }
234 $method_ref = $reflection->getMethod( $method );
235
236 return ( $method_ref && $class_name === $method_ref->class );
237 }
238
239 /**
240 * permission_callback
241 * Permissions callback fallback for the endpoint
242 * Gets the current user ID and sets the /current_user_id/ property.
243 * If the /auth/ property is set to /true/ will make sure that the user is logged in (has an id greater than 0)
244 *
245 * @param WP_REST_Request $request unused
246 *
247 * @return bool TRUE, if permission granted, FALSE otherwise
248 */
249 public function permission_callback( WP_REST_Request $request ): bool {
250 // try to get current user
251 $this->current_user_id = get_current_user_id();
252 if ( $this->auth ) {
253 return $this->current_user_id > 0;
254 }
255
256 return true;
257 }
258
259 /**
260 * callback
261 * Fallback callback function, returns a response consisting of the string /ok/.
262 *
263 * @param WP_REST_Request $request unused
264 *
265 * @return WP_REST_Response Default Response of the string 'ok'.
266 */
267 public function callback( WP_REST_Request $request ): WP_REST_Response {
268 return rest_ensure_response( [ 'OK' ] );
269 }
270
271 /**
272 * respond_wrong_method
273 *
274 * Creates a WordPress error object with the /rest_no_route/ code and the message and code supplied or the defaults.
275 * @param null $message The error message for the wrong method.
276 * Optional.
277 * Defaults to null, which makes sets the message to /No route was found matching the URL and request method/
278 * @param int $code The HTTP status code.
279 * Optional.
280 * Defaults to 404 (Not found).
281 *
282 * @return WP_Error The WordPress error object with the error message and status code supplied
283 */
284 public function respond_wrong_method( $message = null, int $code = 404 ): WP_Error {
285 if ( null === $message ) {
286 $message = __( 'No route was found matching the URL and request method', 'cloud-backup' );
287 }
288
289 return new WP_Error( 'rest_no_route', $message, [ 'status' => $code ] );
290 }
291
292 /**
293 * respond_with_code
294 * Create a new /WP_REST_Response/ object with the specified data and HTTP response code.
295 *
296 * @param array|null $data The data to return in this response
297 * @param int $code The HTTP response code.
298 * Optional.
299 * Defaults to 200 (OK).
300 *
301 * @return WP_REST_Response The WordPress response object loaded with the data and the response code.
302 */
303 public function respond_with_code( ?array $data = null, int $code = 200 ): WP_REST_Response {
304 return new WP_REST_Response( $data, $code );
305 }
306
307 /**
308 * get_user_from_request
309 *
310 * Returns the current user object.
311 * Depends on the property /current_user_id/ to be set.
312 * @return WP_User|false The user object or false if not found or on error.
313 */
314 public function get_user_from_request() {
315 return get_user_by( 'id', $this->current_user_id );
316 }
317
318 /**
319 * get_arguments
320 * Rest Endpoint extra arguments
321 * @return array Additional arguments for the route configuration
322 */
323 public function get_arguments(): array {
324 return [];
325 }
326
327 /**
328 * get_endpoint
329 * Rest route Endpoint
330 * @return string Endpoint uri component (comes after the route namespace)
331 */
332 abstract public function get_endpoint(): string;
333
334 /**
335 * get_name
336 * @return string The name of the route
337 */
338 abstract public function get_name(): string;
339
340 /**
341 * get_self_url
342 *
343 * @param string $endpoint
344 *
345 * @return string
346 */
347 public function get_self_url( string $endpoint = '' ): string {
348 return rest_url( $this->namespace . '/' . $endpoint );
349 }
350
351 public function respond_success_json( $data = [] ): WP_REST_Response {
352 return new WP_REST_Response([
353 'success' => true,
354 'data' => $data,
355 ]);
356 }
357
358 /**
359 * @param array{message: string, code: string} $data
360 *
361 * @return WP_Error
362 */
363 public function respond_error_json( array $data ): WP_Error {
364 if ( ! isset( $data['message'] ) || ! isset( $data['code'] ) ) {
365 _doing_it_wrong(
366 __FUNCTION__,
367 esc_html__( 'Both `message` and `code` keys must be provided', 'image-optimization' ),
368 '1.0.0'
369 ); // @codeCoverageIgnore
370 }
371
372 return new WP_Error(
373 $data['code'] ?? 'internal_server_error',
374 $data['message'] ?? esc_html__( 'Internal server error', 'image-optimization' ),
375 );
376 }
377
378 public function verify_nonce( $nonce = '', $name = '' ) {
379 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), $name ) ) {
380 return $this->respond_error_json([
381 'message' => esc_html__( 'Invalid nonce', 'image-optimization' ),
382 'code' => 'bad_request',
383 ]);
384 }
385 }
386
387 public function verify_capability( $capability = 'manage_options' ) {
388 if ( ! current_user_can( $capability ) ) {
389 return $this->respond_error_json([
390 'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'image-optimization' ),
391 'code' => 'bad_request',
392 ]);
393 }
394 }
395
396 public function verify_nonce_and_capability( $nonce = '', $name = '', $capability = 'manage_options' ) {
397 $this->verify_nonce( $nonce, $name );
398
399 if ( ! current_user_can( $capability ) ) {
400 return $this->respond_error_json([
401 'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'image-optimization' ),
402 'code' => 'bad_request',
403 ]);
404 }
405 }
406
407 public function trigger_internal( $method, $endpoint, $args = [] ): array {
408 $request = new WP_REST_Request( $method, $this->get_self_url( $endpoint ) );
409 if ( ! empty( $args['body'] ) ) {
410 $request->set_body_params( $args['body'] );
411 }
412 if ( ! empty( $args['headers'] ) ) {
413 $request->set_headers( $args['headers'] );
414 }
415 if ( ! empty( $args['params'] ) ) {
416 $request->set_query_params( $args['params'] );
417 }
418
419 $response = rest_do_request( $request );
420 $server = rest_get_server();
421 return $server->response_to_data( $response, false );
422 }
423 }
424