[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Block Pattern Directory REST API: WP_REST_Pattern_Directory_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 5.8.0 8 */ 9 10 /** 11 * Controller which provides REST endpoint for block patterns. 12 * 13 * This simply proxies the endpoint at http://api.wordpress.org/patterns/1.0/. That isn't necessary for 14 * functionality, but is desired for privacy. It prevents api.wordpress.org from knowing the user's IP address. 15 * 16 * @since 5.8.0 17 * 18 * @see WP_REST_Controller 19 */ 20 class WP_REST_Pattern_Directory_Controller extends WP_REST_Controller { 21 22 /** 23 * Constructs the controller. 24 * 25 * @since 5.8.0 26 */ 27 public function __construct() { 28 $this->namespace = 'wp/v2'; 29 $this->rest_base = 'pattern-directory'; 30 } 31 32 /** 33 * Registers the necessary REST API routes. 34 * 35 * @since 5.8.0 36 */ 37 public function register_routes() { 38 register_rest_route( 39 $this->namespace, 40 '/' . $this->rest_base . '/patterns', 41 array( 42 array( 43 'methods' => WP_REST_Server::READABLE, 44 'callback' => array( $this, 'get_items' ), 45 'permission_callback' => array( $this, 'get_items_permissions_check' ), 46 'args' => $this->get_collection_params(), 47 ), 48 'schema' => array( $this, 'get_public_item_schema' ), 49 ) 50 ); 51 } 52 53 /** 54 * Checks whether a given request has permission to view the local block pattern directory. 55 * 56 * @since 5.8.0 57 * 58 * @param WP_REST_Request $request Full details about the request. 59 * @return true|WP_Error True if the request has permission, WP_Error object otherwise. 60 */ 61 public function get_items_permissions_check( $request ) { 62 if ( current_user_can( 'edit_posts' ) ) { 63 return true; 64 } 65 66 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { 67 if ( current_user_can( $post_type->cap->edit_posts ) ) { 68 return true; 69 } 70 } 71 72 return new WP_Error( 73 'rest_pattern_directory_cannot_view', 74 __( 'Sorry, you are not allowed to browse the local block pattern directory.' ), 75 array( 'status' => rest_authorization_required_code() ) 76 ); 77 } 78 79 /** 80 * Search and retrieve block patterns metadata 81 * 82 * @since 5.8.0 83 * @since 6.0.0 Added 'slug' to request. 84 * @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request. 85 * 86 * @param WP_REST_Request $request Full details about the request. 87 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 88 */ 89 public function get_items( $request ) { 90 /* 91 * Include an unmodified `$wp_version`, so the API can craft a response that's tailored to 92 * it. Some plugins modify the version in a misguided attempt to improve security by 93 * obscuring the version, which can cause invalid requests. 94 */ 95 require ABSPATH . WPINC . '/version.php'; 96 97 $valid_query_args = array( 98 'offset' => true, 99 'order' => true, 100 'orderby' => true, 101 'page' => true, 102 'per_page' => true, 103 'search' => true, 104 'slug' => true, 105 ); 106 $query_args = array_intersect_key( $request->get_params(), $valid_query_args ); 107 108 $query_args['locale'] = get_user_locale(); 109 $query_args['wp-version'] = $wp_version; 110 $query_args['pattern-categories'] = isset( $request['category'] ) ? $request['category'] : false; 111 $query_args['pattern-keywords'] = isset( $request['keyword'] ) ? $request['keyword'] : false; 112 113 $query_args = array_filter( $query_args ); 114 115 $transient_key = $this->get_transient_key( $query_args ); 116 117 /* 118 * Use network-wide transient to improve performance. The locale is the only site 119 * configuration that affects the response, and it's included in the transient key. 120 */ 121 $raw_patterns = get_site_transient( $transient_key ); 122 123 if ( ! $raw_patterns ) { 124 $api_url = 'http://api.wordpress.org/patterns/1.0/?' . build_query( $query_args ); 125 if ( wp_http_supports( array( 'ssl' ) ) ) { 126 $api_url = set_url_scheme( $api_url, 'https' ); 127 } 128 129 /* 130 * Default to a short TTL, to mitigate cache stampedes on high-traffic sites. 131 * This assumes that most errors will be short-lived, e.g., packet loss that causes the 132 * first request to fail, but a follow-up one will succeed. The value should be high 133 * enough to avoid stampedes, but low enough to not interfere with users manually 134 * re-trying a failed request. 135 */ 136 $cache_ttl = 5; 137 $wporg_response = wp_remote_get( $api_url ); 138 $raw_patterns = json_decode( wp_remote_retrieve_body( $wporg_response ) ); 139 140 if ( is_wp_error( $wporg_response ) ) { 141 $raw_patterns = $wporg_response; 142 143 } elseif ( ! is_array( $raw_patterns ) ) { 144 // HTTP request succeeded, but response data is invalid. 145 $raw_patterns = new WP_Error( 146 'pattern_api_failed', 147 sprintf( 148 /* translators: %s: Support forums URL. */ 149 __( 'An unexpected error occurred. Something may be wrong with WordPress.org or this server’s configuration. If you continue to have problems, please try the <a href="%s">support forums</a>.' ), 150 __( 'https://wordpress.org/support/forums/' ) 151 ), 152 array( 153 'response' => wp_remote_retrieve_body( $wporg_response ), 154 ) 155 ); 156 157 } else { 158 // Response has valid data. 159 $cache_ttl = HOUR_IN_SECONDS; 160 } 161 162 set_site_transient( $transient_key, $raw_patterns, $cache_ttl ); 163 } 164 165 if ( is_wp_error( $raw_patterns ) ) { 166 $raw_patterns->add_data( array( 'status' => 500 ) ); 167 168 return $raw_patterns; 169 } 170 171 $response = array(); 172 173 if ( $raw_patterns ) { 174 foreach ( $raw_patterns as $pattern ) { 175 $response[] = $this->prepare_response_for_collection( 176 $this->prepare_item_for_response( $pattern, $request ) 177 ); 178 } 179 } 180 181 return new WP_REST_Response( $response ); 182 } 183 184 /** 185 * Prepare a raw block pattern before it gets output in a REST API response. 186 * 187 * @since 5.8.0 188 * @since 5.9.0 Renamed `$raw_pattern` to `$item` to match parent class for PHP 8 named parameter support. 189 * 190 * @param object $item Raw pattern from api.wordpress.org, before any changes. 191 * @param WP_REST_Request $request Request object. 192 * @return WP_REST_Response 193 */ 194 public function prepare_item_for_response( $item, $request ) { 195 // Restores the more descriptive, specific name for use within this method. 196 $raw_pattern = $item; 197 198 $prepared_pattern = array( 199 'id' => absint( $raw_pattern->id ), 200 'title' => sanitize_text_field( $raw_pattern->title->rendered ), 201 'content' => wp_kses_post( $raw_pattern->pattern_content ), 202 'categories' => array_map( 'sanitize_title', $raw_pattern->category_slugs ), 203 'keywords' => array_map( 'sanitize_text_field', explode( ',', $raw_pattern->meta->wpop_keywords ) ), 204 'description' => sanitize_text_field( $raw_pattern->meta->wpop_description ), 205 'viewport_width' => absint( $raw_pattern->meta->wpop_viewport_width ), 206 'block_types' => array_map( 'sanitize_text_field', $raw_pattern->meta->wpop_block_types ), 207 ); 208 209 $prepared_pattern = $this->add_additional_fields_to_object( $prepared_pattern, $request ); 210 211 $response = new WP_REST_Response( $prepared_pattern ); 212 213 /** 214 * Filters the REST API response for a block pattern. 215 * 216 * @since 5.8.0 217 * 218 * @param WP_REST_Response $response The response object. 219 * @param object $raw_pattern The unprepared block pattern. 220 * @param WP_REST_Request $request The request object. 221 */ 222 return apply_filters( 'rest_prepare_block_pattern', $response, $raw_pattern, $request ); 223 } 224 225 /** 226 * Retrieves the block pattern's schema, conforming to JSON Schema. 227 * 228 * @since 5.8.0 229 * @since 6.2.0 Added `'block_types'` to schema. 230 * 231 * @return array Item schema data. 232 */ 233 public function get_item_schema() { 234 if ( $this->schema ) { 235 return $this->add_additional_fields_schema( $this->schema ); 236 } 237 238 $this->schema = array( 239 '$schema' => 'http://json-schema.org/draft-04/schema#', 240 'title' => 'pattern-directory-item', 241 'type' => 'object', 242 'properties' => array( 243 'id' => array( 244 'description' => __( 'The pattern ID.' ), 245 'type' => 'integer', 246 'minimum' => 1, 247 'context' => array( 'view', 'edit', 'embed' ), 248 ), 249 250 'title' => array( 251 'description' => __( 'The pattern title, in human readable format.' ), 252 'type' => 'string', 253 'minLength' => 1, 254 'context' => array( 'view', 'edit', 'embed' ), 255 ), 256 257 'content' => array( 258 'description' => __( 'The pattern content.' ), 259 'type' => 'string', 260 'minLength' => 1, 261 'context' => array( 'view', 'edit', 'embed' ), 262 ), 263 264 'categories' => array( 265 'description' => __( "The pattern's category slugs." ), 266 'type' => 'array', 267 'uniqueItems' => true, 268 'items' => array( 'type' => 'string' ), 269 'context' => array( 'view', 'edit', 'embed' ), 270 ), 271 272 'keywords' => array( 273 'description' => __( "The pattern's keywords." ), 274 'type' => 'array', 275 'uniqueItems' => true, 276 'items' => array( 'type' => 'string' ), 277 'context' => array( 'view', 'edit', 'embed' ), 278 ), 279 280 'description' => array( 281 'description' => __( 'A description of the pattern.' ), 282 'type' => 'string', 283 'minLength' => 1, 284 'context' => array( 'view', 'edit', 'embed' ), 285 ), 286 287 'viewport_width' => array( 288 'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.' ), 289 'type' => 'integer', 290 'context' => array( 'view', 'edit', 'embed' ), 291 ), 292 293 'block_types' => array( 294 'description' => __( 'The block types which can use this pattern.' ), 295 'type' => 'array', 296 'uniqueItems' => true, 297 'items' => array( 'type' => 'string' ), 298 'context' => array( 'view', 'embed' ), 299 ), 300 ), 301 ); 302 303 return $this->add_additional_fields_schema( $this->schema ); 304 } 305 306 /** 307 * Retrieves the search parameters for the block pattern's collection. 308 * 309 * @since 5.8.0 310 * @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request. 311 * 312 * @return array Collection parameters. 313 */ 314 public function get_collection_params() { 315 $query_params = parent::get_collection_params(); 316 317 $query_params['per_page']['default'] = 100; 318 $query_params['search']['minLength'] = 1; 319 $query_params['context']['default'] = 'view'; 320 321 $query_params['category'] = array( 322 'description' => __( 'Limit results to those matching a category ID.' ), 323 'type' => 'integer', 324 'minimum' => 1, 325 ); 326 327 $query_params['keyword'] = array( 328 'description' => __( 'Limit results to those matching a keyword ID.' ), 329 'type' => 'integer', 330 'minimum' => 1, 331 ); 332 333 $query_params['slug'] = array( 334 'description' => __( 'Limit results to those matching a pattern (slug).' ), 335 'type' => 'array', 336 ); 337 338 $query_params['offset'] = array( 339 'description' => __( 'Offset the result set by a specific number of items.' ), 340 'type' => 'integer', 341 ); 342 343 $query_params['order'] = array( 344 'description' => __( 'Order sort attribute ascending or descending.' ), 345 'type' => 'string', 346 'default' => 'desc', 347 'enum' => array( 'asc', 'desc' ), 348 ); 349 350 $query_params['orderby'] = array( 351 'description' => __( 'Sort collection by post attribute.' ), 352 'type' => 'string', 353 'default' => 'date', 354 'enum' => array( 355 'author', 356 'date', 357 'id', 358 'include', 359 'modified', 360 'parent', 361 'relevance', 362 'slug', 363 'include_slugs', 364 'title', 365 'favorite_count', 366 ), 367 ); 368 369 /** 370 * Filter collection parameters for the block pattern directory controller. 371 * 372 * @since 5.8.0 373 * 374 * @param array $query_params JSON Schema-formatted collection parameters. 375 */ 376 return apply_filters( 'rest_pattern_directory_collection_params', $query_params ); 377 } 378 379 /* 380 * Include a hash of the query args, so that different requests are stored in 381 * separate caches. 382 * 383 * MD5 is chosen for its speed, low-collision rate, universal availability, and to stay 384 * under the character limit for `_site_transient_timeout_{...}` keys. 385 * 386 * @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses 387 * 388 * @since 6.0.0 389 * 390 * @param array $query_args Query arguments to generate a transient key from. 391 * @return string Transient key. 392 */ 393 protected function get_transient_key( $query_args ) { 394 395 if ( isset( $query_args['slug'] ) ) { 396 // This is an additional precaution because the "sort" function expects an array. 397 $query_args['slug'] = wp_parse_list( $query_args['slug'] ); 398 399 // Empty arrays should not affect the transient key. 400 if ( empty( $query_args['slug'] ) ) { 401 unset( $query_args['slug'] ); 402 } else { 403 // Sort the array so that the transient key doesn't depend on the order of slugs. 404 sort( $query_args['slug'] ); 405 } 406 } 407 408 return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) ); 409 } 410 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Fri Apr 19 08:20:01 2024 | Cross-referenced by PHPXref |