PluginProbe ʕ •ᴥ•ʔ
GenerateBlocks / trunk
GenerateBlocks vtrunk
trunk 1.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.3.0
generateblocks / includes / pattern-library / class-pattern-library-rest.php
generateblocks / includes / pattern-library Last commit date
templates 2 years ago class-libraries.php 3 weeks ago class-library-dto.php 2 years ago class-pattern-library-rest.php 3 weeks ago
class-pattern-library-rest.php
509 lines
1 <?php
2 /**
3 * The Pattern library class file.
4 *
5 * @package GenerateBlocks\Pattern_Library
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly.
10 }
11
12 /**
13 * Main class for the Pattern Library Rest functions.
14 *
15 * @since 1.9
16 */
17 class GenerateBlocks_Pattern_Library_Rest extends GenerateBlocks_Singleton {
18
19 /**
20 * Initialize all hooks.
21 *
22 * @return void
23 */
24 public function init() {
25 add_action( 'rest_api_init', array( $this, 'register_routes' ) );
26 }
27
28 /**
29 * Register the REST routes.
30 *
31 * @return void
32 */
33 public function register_routes(): void {
34 $namespace = 'generateblocks/v1';
35
36 register_rest_route(
37 $namespace,
38 '/pattern-library/libraries',
39 array(
40 'methods' => WP_REST_Server::READABLE,
41 'callback' => array( $this, 'list_libraries' ),
42 'permission_callback' => function() {
43 return apply_filters(
44 'generateblocks_can_view_pattern_library',
45 $this->edit_posts_permission()
46 );
47 },
48 )
49 );
50
51 register_rest_route(
52 $namespace,
53 '/pattern-library/libraries/save',
54 array(
55 'methods' => WP_REST_Server::CREATABLE,
56 'callback' => array( $this, 'save_libraries' ),
57 'permission_callback' => array( $this, 'manage_options_permission' ),
58 )
59 );
60
61 register_rest_route(
62 $namespace,
63 '/pattern-library/categories',
64 array(
65 'methods' => WP_REST_Server::READABLE,
66 'callback' => array( $this, 'list_categories' ),
67 'permission_callback' => function() {
68 return apply_filters(
69 'generateblocks_can_view_pattern_library',
70 $this->edit_posts_permission()
71 );
72 },
73 )
74 );
75
76 register_rest_route(
77 $namespace,
78 '/pattern-library/patterns',
79 array(
80 'methods' => WP_REST_Server::READABLE,
81 'callback' => array( $this, 'list_patterns' ),
82 'permission_callback' => function() {
83 return apply_filters(
84 'generateblocks_can_view_pattern_library',
85 $this->edit_posts_permission()
86 );
87 },
88 )
89 );
90
91 register_rest_route(
92 $namespace,
93 '/pattern-library/get-cache-data',
94 array(
95 'methods' => WP_REST_Server::READABLE,
96 'callback' => array( $this, 'get_cache_data' ),
97 'permission_callback' => function() {
98 return apply_filters(
99 'generateblocks_can_view_pattern_library',
100 $this->edit_posts_permission()
101 );
102 },
103 )
104 );
105
106 register_rest_route(
107 $namespace,
108 '/pattern-library/clear-cache',
109 array(
110 'methods' => WP_REST_Server::EDITABLE,
111 'callback' => array( $this, 'clear_cache' ),
112 'permission_callback' => array( $this, 'edit_posts_permission' ),
113 )
114 );
115 }
116
117 /**
118 * Manage options permission callback.
119 *
120 * @return bool
121 */
122 public function manage_options_permission(): bool {
123 return current_user_can( 'manage_options' );
124 }
125
126 /**
127 * Manage options permission callback.
128 *
129 * @return bool
130 */
131 public function edit_posts_permission(): bool {
132 return current_user_can( 'edit_posts' );
133 }
134
135 /**
136 * Returns a list of registered libraries.
137 *
138 * @param WP_REST_Request $request The request.
139 *
140 * @return WP_REST_Response
141 */
142 public function list_libraries( WP_REST_Request $request ): WP_REST_Response {
143 $is_enabled = $request->get_param( 'is_enabled' );
144 $libraries = GenerateBlocks_Libraries::get_instance();
145 $data = $libraries->get_all( rest_sanitize_boolean( $is_enabled ) );
146 $data = array_values( $data ); // Fix indexes.
147
148 return new WP_REST_Response(
149 array(
150 'error' => false,
151 'data' => $data,
152 ),
153 200
154 );
155 }
156
157 /**
158 * Saves the list of libraries.
159 *
160 * @param WP_REST_Request $request The request.
161 *
162 * @return WP_REST_Response
163 */
164 public function save_libraries( WP_REST_Request $request ): WP_REST_Response {
165 $data = $request->get_param( 'data' );
166
167 if ( ! is_array( $data ) ) {
168 return $this->error( 400, __( 'Invalid library data.', 'generateblocks' ) );
169 }
170
171 $libraries = array_values(
172 array_filter(
173 array_map(
174 [ $this, 'sanitize_library_data' ],
175 $data
176 )
177 )
178 );
179
180 update_option( 'generateblocks_pattern_libraries', $libraries );
181 return $this->success( $libraries );
182 }
183
184 /**
185 * Returns a list of categories.
186 *
187 * @param WP_REST_Request $request The request.
188 *
189 * @return WP_REST_Response
190 */
191 public function list_categories( WP_REST_Request $request ): WP_REST_Response {
192 $library_id = $this->sanitize_request_string( $request->get_param( 'libraryId' ) );
193
194 if ( ! $library_id ) {
195 return $this->error( 404, "Library of id \"$library_id\" was not found." );
196 }
197
198 $libraries = GenerateBlocks_Libraries::get_instance();
199 $library = $libraries->get_one( $library_id );
200
201 if ( ! is_null( $library ) ) {
202 return self::remote_fetch( $library, 'categories' );
203 }
204
205 return $this->error( 404, "Library of id \"$library_id\" was not found." );
206 }
207
208 /**
209 * Returns a list of patterns.
210 *
211 * @param WP_REST_Request $request The request.
212 *
213 * @return WP_REST_Response
214 */
215 public function list_patterns( WP_REST_Request $request ): WP_REST_Response {
216 $library_id = $this->sanitize_request_string( $request->get_param( 'libraryId' ) );
217 $search = $this->sanitize_request_string( $request->get_param( 'search' ) );
218 $category_id = $this->sanitize_request_string( $request->get_param( 'categoryId' ) );
219
220 if ( ! $library_id ) {
221 return $this->error( 404, "Library of id \"$library_id\" was not found." );
222 }
223
224 $libraries = GenerateBlocks_Libraries::get_instance();
225 $library = $libraries->get_one( $library_id );
226
227 if ( ! is_null( $library ) ) {
228 return self::remote_fetch(
229 $library,
230 'patterns',
231 array(
232 'search' => $search,
233 'categoryId' => $category_id,
234 )
235 );
236 }
237
238 return $this->error( 404, "Library of id \"$library_id\" was not found." );
239 }
240
241 /**
242 * Fetch pattern library remotely.
243 *
244 * @param GenerateBlocks_Library_DTO $library The library to fetch data.
245 * @param string $collection The collection type. Either 'categories' or 'patterns'.
246 * @param array $query_args The extra query arguments.
247 *
248 * @return WP_REST_Response
249 */
250 private function remote_fetch(
251 GenerateBlocks_Library_DTO $library,
252 string $collection,
253 array $query_args = array()
254 ): WP_REST_Response {
255 if ( ! in_array( $collection, array( 'categories', 'patterns' ), true ) ) {
256 return $this->error( 400, __( 'Invalid library collection.', 'generateblocks' ) );
257 }
258
259 $domain = esc_url_raw( $library->domain );
260
261 if ( ! $domain ) {
262 return $this->error( 400, __( 'Invalid library domain.', 'generateblocks' ) );
263 }
264
265 $endpoint = trailingslashit( $domain ) . "wp-json/generateblocks-pro/v1/pattern-library/$collection";
266 $url = add_query_arg( $query_args, $endpoint );
267 $cache_key = $library->id . '-' . $collection;
268 $cache = GenerateBlocks_Libraries::get_cached_data( $cache_key, $query_args, $collection );
269
270 if ( false !== $cache ) {
271 return $this->success( $cache );
272 }
273
274 $request = wp_remote_get(
275 $url,
276 array(
277 'headers' => array(
278 'X-GB-Public-Key' => $library->public_key,
279 ),
280 'timeout' => 15,
281 )
282 );
283
284 if ( is_wp_error( $request ) ) {
285 return $this->error( 500, "Unable to request from $endpoint" );
286 }
287
288 $response_code = (int) wp_remote_retrieve_response_code( $request );
289
290 if ( 200 !== $response_code ) {
291 return $this->error( $response_code ? $response_code : 500, "Unable to request from $endpoint" );
292 }
293
294 $body = wp_remote_retrieve_body( $request );
295 $body = json_decode( $body, true );
296
297 if ( ! is_array( $body ) ) {
298 return $this->error( 500, __( 'Invalid library response.', 'generateblocks' ) );
299 }
300
301 $data = $body['response']['data'] ?? [];
302
303 if ( ! is_array( $data ) ) {
304 return $this->error( 500, __( 'Invalid library response data.', 'generateblocks' ) );
305 }
306
307 // Cache our data.
308 GenerateBlocks_Libraries::set_cached_data( $data, $cache_key, $collection );
309
310 return $this->success( $data );
311 }
312
313 /**
314 * Get the expiry time of a cache.
315 *
316 * @param WP_REST_Request $request The request.
317 *
318 * @return WP_REST_Response
319 */
320 public function get_cache_data( WP_REST_Request $request ): WP_REST_Response {
321 $id = $this->sanitize_request_string( $request->get_param( 'id' ) );
322
323 if ( ! $id ) {
324 return $this->error( 400, __( 'Invalid library id.', 'generateblocks' ) );
325 }
326
327 if ( is_null( GenerateBlocks_Libraries::get_instance()->get_one( $id ) ) ) {
328 return $this->error( 404, __( 'Library not found.', 'generateblocks' ) );
329 }
330
331 $expiration_time = get_option( '_transient_timeout_' . $id . '-patterns_0' );
332
333 if ( ! $expiration_time ) {
334 return $this->failed( 'no_cache' );
335 }
336
337 $current_time = time();
338 $cache_made_time = $expiration_time - GenerateBlocks_Libraries::get_cache_expiry();
339 $can_clear_cache = $current_time > ( $cache_made_time + 300 );
340
341 return $this->success(
342 [
343 'expiry_time_raw' => $expiration_time,
344 'expiry_time' => gmdate( 'Y-m-d H:i:s', $expiration_time ),
345 'can_clear' => $can_clear_cache,
346 ]
347 );
348 }
349
350 /**
351 * Clear caches for a specific collection.
352 *
353 * @param WP_REST_Request $request The request.
354 *
355 * @return WP_REST_Response
356 */
357 public function clear_cache( WP_REST_Request $request ): WP_REST_Response {
358 $id = $this->sanitize_request_string( $request->get_param( 'id' ) );
359
360 if ( ! $id ) {
361 return $this->error( 400, __( 'Invalid library id.', 'generateblocks' ) );
362 }
363
364 if ( is_null( GenerateBlocks_Libraries::get_instance()->get_one( $id ) ) ) {
365 return $this->error( 404, __( 'Library not found.', 'generateblocks' ) );
366 }
367
368 GenerateBlocks_Libraries::delete_cached_data( $id . '-categories' );
369 GenerateBlocks_Libraries::delete_cached_data( $id . '-patterns', 'patterns' );
370 GenerateBlocks_Libraries::delete_cached_data( $id . '_global-style-data' );
371
372 return $this->success( [] );
373 }
374
375 /**
376 * Sanitize scalar REST request values.
377 *
378 * @param mixed $value The value to sanitize.
379 * @return string
380 */
381 private function sanitize_request_string( $value ): string {
382 if ( ! is_scalar( $value ) ) {
383 return '';
384 }
385
386 return sanitize_text_field( (string) $value );
387 }
388
389 /**
390 * Sanitize scalar REST request URLs.
391 *
392 * @param mixed $value The value to sanitize.
393 * @return string
394 */
395 private function sanitize_request_url( $value ): string {
396 if ( ! is_string( $value ) ) {
397 return '';
398 }
399
400 return esc_url_raw( $value, array( 'http', 'https' ) );
401 }
402
403 /**
404 * Sanitize a saved library row.
405 *
406 * @param mixed $library The library data.
407 * @return array
408 */
409 private function sanitize_library_data( $library ): array {
410 if ( ! is_array( $library ) ) {
411 return [];
412 }
413
414 $id = $this->sanitize_request_string( $library['id'] ?? '' );
415
416 if ( ! $id ) {
417 return [];
418 }
419
420 $is_local = rest_sanitize_boolean( $library['isLocal'] ?? false );
421 $is_default = rest_sanitize_boolean( $library['isDefault'] ?? false );
422 $is_enabled = rest_sanitize_boolean( $library['isEnabled'] ?? false );
423
424 if ( ! $is_local && ! $is_default ) {
425 $domain = $this->sanitize_request_url( $library['domain'] ?? '' );
426
427 if ( ! $domain ) {
428 return [];
429 }
430
431 // Save all data if this is a remote library.
432 return [
433 'id' => $id,
434 'name' => $this->sanitize_request_string( $library['name'] ?? '' ),
435 'domain' => $domain,
436 'publicKey' => $this->sanitize_request_string( $library['publicKey'] ?? '' ),
437 'isEnabled' => $is_enabled,
438 'isDefault' => $is_default,
439 'isLocal' => $is_local,
440 ];
441 }
442
443 // Only save the ID and status for local and default libraries.
444 // The rest of the data will be supplied via the PHP filter.
445 return [
446 'id' => $id,
447 'isEnabled' => $is_enabled,
448 ];
449 }
450
451 /**
452 * Returns a success response.
453 *
454 * @param array $data The data.
455 *
456 * @return WP_REST_Response
457 */
458 private function success( array $data ): WP_REST_Response {
459 return new WP_REST_Response(
460 array(
461 'success' => true,
462 'response' => array(
463 'data' => $data,
464 ),
465 ),
466 200
467 );
468 }
469
470 /**
471 * Returns a success response.
472 *
473 * @param string $message The error message.
474 *
475 * @return WP_REST_Response
476 */
477 private function failed( string $message ): WP_REST_Response {
478 return new WP_REST_Response(
479 array(
480 'success' => false,
481 'response' => $message,
482 ),
483 200
484 );
485 }
486
487 /**
488 * Returns a error response.
489 *
490 * @param int $code Error code.
491 * @param string $message Error message.
492 *
493 * @return WP_REST_Response
494 */
495 private function error( int $code, string $message ): WP_REST_Response {
496 return new WP_REST_Response(
497 array(
498 'error' => true,
499 'success' => false,
500 'error_code' => $code,
501 'response' => $message,
502 ),
503 $code
504 );
505 }
506 }
507
508 GenerateBlocks_Pattern_Library_Rest::get_instance()->init();
509