PluginProbe
Image Optimization – Compress Images and Convert to WebP or AVIF / 1.6.8
Image Optimization – Compress Images and Convert to WebP or AVIF v1.6.8
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 1.6.6 All 32 releases
image-optimization / classes / route.php

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

370 lines 11.1 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 * @param string $method The REST method name
119 *
120 * @return callable If a method called (rest-method)_permission_callback exists, returns a reference to it, otherwise
121 * returns a reference to the default member method /permission_callback/.
122 */
123 public function get_permission_callback_method( string $method ): callable {
124 $method_name = strtolower( $method );
125 $permission_callback_method = $method_name . '_permission_callback';
126 $permission_callback = $this->method_exists_in_current_class( $permission_callback_method ) ? $permission_callback_method : 'permission_callback';
127 return [ $this, $permission_callback ];
128 }
129
130 /**
131 * maybe_add_args_to_config
132 *
133 * Checks if the class has a method call (rest-method)_args.
134 * If it does, the function calls it and adds its response to the config object passed to the function, under the /args/ key.
135 * @param string $method The REST method name being configured
136 * @param array $config The configuration object for the method
137 *
138 * @return array The configuration object for the method, possibly after being amended
139 */
140 public function maybe_add_args_to_config( string $method, array $config ): array {
141 $method_name = strtolower( $method );
142 $method_args = $method_name . '_args';
143 if ( $this->method_exists_in_current_class( $method_args ) ) {
144 $config['args'] = $this->{$method_args}();
145 }
146 return $config;
147 }
148
149 /**
150 * maybe_add_response_to_swagger
151 *
152 * If the function method /(rest-method)_response_callback/ exists, adds the filter
153 * /swagger_api_response_(namespace with slashes replaced with underscores)_(endpoint with slashes replaced with underscores)/
154 * with the aforementioned function method.
155 * This filter is used with the WP API Swagger UI plugin to create documentation for the API.
156 * The value being passed is an array: [
157 '200' => ['description' => 'OK'],
158 '404' => ['description' => 'Not Found'],
159 '400' => ['description' => 'Bad Request']
160 ]
161 * @param string $method REST method name
162 */
163 public function maybe_add_response_to_swagger( string $method ): void {
164 $method_name = strtolower( $method );
165 $method_response_callback = $method_name . '_response_callback';
166 if ( $this->method_exists_in_current_class( $method_response_callback ) ) {
167 $response_filter = $method_name . '_' . str_replace(
168 '/',
169 '_',
170 $this->namespace . '/' . $this->get_endpoint()
171 );
172 add_filter( 'swagger_api_responses_' . $response_filter, [ $this, $method_response_callback ] );
173 }
174 }
175
176 /**
177 * build_endpoint_method_config
178 *
179 * Builds a configuration array for the endpoint based on the presence of the callback, permission, additional parameters,
180 * and response to Swagger member functions.
181 * @param string $method The REST method for the endpoint
182 *
183 * @return array The endpoint configuration for the method specified by the parameter
184 */
185 private function build_endpoint_method_config( string $method ): array {
186 $config = [
187 'methods' => $method,
188 'callback' => $this->get_callback_method( $method ),
189 'permission_callback' => $this->get_permission_callback_method( $method ),
190 ];
191 $config = $this->maybe_add_args_to_config( $method, $config );
192 return $config;
193 }
194
195 /**
196 * method_exists_in_current_class
197 *
198 * Uses reflection to check if this class has the /method/ method.
199 * @param string $method The name of the method being checked.
200 *
201 * @return bool TRUE if the class has the /method/ method, FALSE otherwise.
202 */
203 private function method_exists_in_current_class( string $method ): bool {
204 $class_name = get_class( $this );
205 try {
206 $reflection = new ReflectionClass( $class_name );
207 } catch ( \ReflectionException $e ) {
208 return false;
209 }
210 if ( ! $reflection->hasMethod( $method ) ) {
211 return false;
212 }
213 $method_ref = $reflection->getMethod( $method );
214
215 return ( $method_ref && $class_name === $method_ref->class );
216 }
217
218 /**
219 * permission_callback
220 * Permissions callback fallback for the endpoint
221 * Gets the current user ID and sets the /current_user_id/ property.
222 * If the /auth/ property is set to /true/ will make sure that the user is logged in (has an id greater than 0)
223 *
224 * @param WP_REST_Request $request unused
225 *
226 * @return bool TRUE, if permission granted, FALSE otherwise
227 */
228 public function permission_callback( WP_REST_Request $request ): bool {
229 // try to get current user
230 $this->current_user_id = get_current_user_id();
231 if ( $this->auth ) {
232 return $this->current_user_id > 0;
233 }
234
235 return true;
236 }
237
238 /**
239 * callback
240 * Fallback callback function, returns a response consisting of the string /ok/.
241 *
242 * @param WP_REST_Request $request unused
243 *
244 * @return WP_REST_Response Default Response of the string 'ok'.
245 */
246 public function callback( WP_REST_Request $request ): WP_REST_Response {
247 return rest_ensure_response( [ 'OK' ] );
248 }
249
250 /**
251 * respond_wrong_method
252 *
253 * Creates a WordPress error object with the /rest_no_route/ code and the message and code supplied or the defaults.
254 * @param null $message The error message for the wrong method.
255 * Optional.
256 * Defaults to null, which makes sets the message to /No route was found matching the URL and request method/
257 * @param int $code The HTTP status code.
258 * Optional.
259 * Defaults to 404 (Not found).
260 *
261 * @return WP_Error The WordPress error object with the error message and status code supplied
262 */
263 public function respond_wrong_method( $message = null, int $code = 404 ): WP_Error {
264 if ( null === $message ) {
265 $message = 'No route was found matching the URL and request method';
266 }
267
268 return new WP_Error( 'rest_no_route', $message, [ 'status' => $code ] );
269 }
270
271 /**
272 * respond_with_code
273 * Create a new /WP_REST_Response/ object with the specified data and HTTP response code.
274 *
275 * @param array|null $data The data to return in this response
276 * @param int $code The HTTP response code.
277 * Optional.
278 * Defaults to 200 (OK).
279 *
280 * @return WP_REST_Response The WordPress response object loaded with the data and the response code.
281 */
282 public function respond_with_code( ?array $data = null, int $code = 200 ): WP_REST_Response {
283 return new WP_REST_Response( $data, $code );
284 }
285
286 /**
287 * get_user_from_request
288 *
289 * Returns the current user object.
290 * Depends on the property /current_user_id/ to be set.
291 * @return WP_User|false The user object or false if not found or on error.
292 */
293 public function get_user_from_request() {
294 return get_user_by( 'id', $this->current_user_id );
295 }
296
297 /**
298 * get_arguments
299 * Rest Endpoint extra arguments
300 * @return array Additional arguments for the route configuration
301 */
302 public function get_arguments(): array {
303 return [];
304 }
305
306 /**
307 * get_endpoint
308 * Rest route Endpoint
309 * @return string Endpoint uri component (comes after the route namespace)
310 */
311 abstract public function get_endpoint(): string;
312
313 /**
314 * get_name
315 * @return string The name of the route
316 */
317 abstract public function get_name(): string;
318
319 public function get_self_url( $endpoint = '' ): string {
320 return rest_url( $this->namespace . '/' . $endpoint );
321 }
322
323 public function respond_success_json( $data = [] ): WP_REST_Response {
324 return new WP_REST_Response([
325 'success' => true,
326 'data' => $data,
327 ]);
328 }
329
330 /**
331 * @param array{message: string, code: string} $data
332 *
333 * @return WP_Error
334 */
335 public function respond_error_json( array $data ): WP_Error {
336 if ( ! isset( $data['message'] ) || ! isset( $data['code'] ) ) {
337 _doing_it_wrong(
338 __FUNCTION__,
339 esc_html__( 'Both `message` and `code` keys must be provided', 'image-optimization' ),
340 '1.0.0'
341 ); // @codeCoverageIgnore
342 }
343
344 return new WP_Error(
345 $data['code'] ?? 'internal_server_error',
346 $data['message'] ?? esc_html__( 'Internal server error', 'image-optimization' ),
347 );
348 }
349
350 public function verify_nonce( $nonce = '', $name = '' ) {
351 if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $nonce ) ), $name ) ) {
352 return $this->respond_error_json([
353 'message' => esc_html__( 'Invalid nonce', 'image-optimization' ),
354 'code' => 'bad_request',
355 ]);
356 }
357 }
358
359 public function verify_nonce_and_capability( $nonce = '', $name = '', $capability = 'manage_options' ) {
360 $this->verify_nonce( $nonce, $name );
361
362 if ( ! current_user_can( $capability ) ) {
363 return $this->respond_error_json([
364 'message' => esc_html__( 'You do not have sufficient permissions to access this data.', 'image-optimization' ),
365 'code' => 'bad_request',
366 ]);
367 }
368 }
369 }
370