PluginProbe
Image Optimizer – Compress Images and Convert to WebP or AVIF / trunk
Image Optimizer – Compress Images and Convert to WebP or AVIF vtrunk
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 / route.php

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

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