[ 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 $valid_query_args = array( 91 'offset' => true, 92 'order' => true, 93 'orderby' => true, 94 'page' => true, 95 'per_page' => true, 96 'search' => true, 97 'slug' => true, 98 ); 99 $query_args = array_intersect_key( $request->get_params(), $valid_query_args ); 100 101 $query_args['locale'] = get_user_locale(); 102 $query_args['wp-version'] = wp_get_wp_version(); 103 $query_args['pattern-categories'] = isset( $request['category'] ) ? $request['category'] : false; 104 $query_args['pattern-keywords'] = isset( $request['keyword'] ) ? $request['keyword'] : false; 105 106 $query_args = array_filter( $query_args ); 107 108 $transient_key = $this->get_transient_key( $query_args ); 109 110 /* 111 * Use network-wide transient to improve performance. The locale is the only site 112 * configuration that affects the response, and it's included in the transient key. 113 */ 114 $raw_patterns = get_site_transient( $transient_key ); 115 116 if ( ! $raw_patterns ) { 117 $api_url = 'http://api.wordpress.org/patterns/1.0/?' . build_query( $query_args ); 118 if ( wp_http_supports( array( 'ssl' ) ) ) { 119 $api_url = set_url_scheme( $api_url, 'https' ); 120 } 121 122 /* 123 * Default to a short TTL, to mitigate cache stampedes on high-traffic sites. 124 * This assumes that most errors will be short-lived, e.g., packet loss that causes the 125 * first request to fail, but a follow-up one will succeed. The value should be high 126 * enough to avoid stampedes, but low enough to not interfere with users manually 127 * re-trying a failed request. 128 */ 129 $cache_ttl = 5; 130 $wporg_response = wp_remote_get( $api_url ); 131 $raw_patterns = json_decode( wp_remote_retrieve_body( $wporg_response ) ); 132 133 if ( is_wp_error( $wporg_response ) ) { 134 $raw_patterns = $wporg_response; 135 136 } elseif ( ! is_array( $raw_patterns ) ) { 137 // HTTP request succeeded, but response data is invalid. 138 $raw_patterns = new WP_Error( 139 'pattern_api_failed', 140 sprintf( 141 /* translators: %s: Support forums URL. */ 142 __( '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>.' ), 143 __( 'https://wordpress.org/support/forums/' ) 144 ), 145 array( 146 'response' => wp_remote_retrieve_body( $wporg_response ), 147 ) 148 ); 149 150 } else { 151 // Response has valid data. 152 $cache_ttl = HOUR_IN_SECONDS; 153 } 154 155 set_site_transient( $transient_key, $raw_patterns, $cache_ttl ); 156 } 157 158 if ( is_wp_error( $raw_patterns ) ) { 159 $raw_patterns->add_data( array( 'status' => 500 ) ); 160 161 return $raw_patterns; 162 } 163 164 if ( $request->is_method( 'HEAD' ) ) { 165 // Return early as this handler doesn't add any response headers. 166 return new WP_REST_Response( array() ); 167 } 168 169 $response = array(); 170 171 if ( $raw_patterns ) { 172 foreach ( $raw_patterns as $pattern ) { 173 $response[] = $this->prepare_response_for_collection( 174 $this->prepare_item_for_response( $pattern, $request ) 175 ); 176 } 177 } 178 179 return new WP_REST_Response( $response ); 180 } 181 182 /** 183 * Prepare a raw block pattern before it gets output in a REST API response. 184 * 185 * @since 5.8.0 186 * @since 5.9.0 Renamed `$raw_pattern` to `$item` to match parent class for PHP 8 named parameter support. 187 * 188 * @param object $item Raw pattern from api.wordpress.org, before any changes. 189 * @param WP_REST_Request $request Request object. 190 * @return WP_REST_Response 191 */ 192 public function prepare_item_for_response( $item, $request ) { 193 // Restores the more descriptive, specific name for use within this method. 194 $raw_pattern = $item; 195 196 $prepared_pattern = array( 197 'id' => absint( $raw_pattern->id ), 198 'title' => sanitize_text_field( $raw_pattern->title->rendered ), 199 'content' => wp_kses_post( $raw_pattern->pattern_content ), 200 'categories' => array_map( 'sanitize_title', $raw_pattern->category_slugs ), 201 'keywords' => array_map( 'sanitize_text_field', explode( ',', $raw_pattern->meta->wpop_keywords ) ), 202 'description' => sanitize_text_field( $raw_pattern->meta->wpop_description ), 203 'viewport_width' => absint( $raw_pattern->meta->wpop_viewport_width ), 204 'block_types' => array_map( 'sanitize_text_field', $raw_pattern->meta->wpop_block_types ), 205 ); 206 207 $prepared_pattern = $this->add_additional_fields_to_object( $prepared_pattern, $request ); 208 209 $response = new WP_REST_Response( $prepared_pattern ); 210 211 /** 212 * Filters the REST API response for a block pattern. 213 * 214 * @since 5.8.0 215 * 216 * @param WP_REST_Response $response The response object. 217 * @param object $raw_pattern The unprepared block pattern. 218 * @param WP_REST_Request $request The request object. 219 */ 220 return apply_filters( 'rest_prepare_block_pattern', $response, $raw_pattern, $request ); 221 } 222 223 /** 224 * Retrieves the block pattern's schema, conforming to JSON Schema. 225 * 226 * @since 5.8.0 227 * @since 6.2.0 Added `'block_types'` to schema. 228 * 229 * @return array Item schema data. 230 */ 231 public function get_item_schema() { 232 if ( $this->schema ) { 233 return $this->add_additional_fields_schema( $this->schema ); 234 } 235 236 $this->schema = array( 237 '$schema' => 'http://json-schema.org/draft-04/schema#', 238 'title' => 'pattern-directory-item', 239 'type' => 'object', 240 'properties' => array( 241 'id' => array( 242 'description' => __( 'The pattern ID.' ), 243 'type' => 'integer', 244 'minimum' => 1, 245 'context' => array( 'view', 'edit', 'embed' ), 246 ), 247 248 'title' => array( 249 'description' => __( 'The pattern title, in human readable format.' ), 250 'type' => 'string', 251 'minLength' => 1, 252 'context' => array( 'view', 'edit', 'embed' ), 253 ), 254 255 'content' => array( 256 'description' => __( 'The pattern content.' ), 257 'type' => 'string', 258 'minLength' => 1, 259 'context' => array( 'view', 'edit', 'embed' ), 260 ), 261 262 'categories' => array( 263 'description' => __( "The pattern's category slugs." ), 264 'type' => 'array', 265 'uniqueItems' => true, 266 'items' => array( 'type' => 'string' ), 267 'context' => array( 'view', 'edit', 'embed' ), 268 ), 269 270 'keywords' => array( 271 'description' => __( "The pattern's keywords." ), 272 'type' => 'array', 273 'uniqueItems' => true, 274 'items' => array( 'type' => 'string' ), 275 'context' => array( 'view', 'edit', 'embed' ), 276 ), 277 278 'description' => array( 279 'description' => __( 'A description of the pattern.' ), 280 'type' => 'string', 281 'minLength' => 1, 282 'context' => array( 'view', 'edit', 'embed' ), 283 ), 284 285 'viewport_width' => array( 286 'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.' ), 287 'type' => 'integer', 288 'context' => array( 'view', 'edit', 'embed' ), 289 ), 290 291 'block_types' => array( 292 'description' => __( 'The block types which can use this pattern.' ), 293 'type' => 'array', 294 'uniqueItems' => true, 295 'items' => array( 'type' => 'string' ), 296 'context' => array( 'view', 'embed' ), 297 ), 298 ), 299 ); 300 301 return $this->add_additional_fields_schema( $this->schema ); 302 } 303 304 /** 305 * Retrieves the search parameters for the block pattern's collection. 306 * 307 * @since 5.8.0 308 * @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request. 309 * 310 * @return array Collection parameters. 311 */ 312 public function get_collection_params() { 313 $query_params = parent::get_collection_params(); 314 315 $query_params['per_page']['default'] = 100; 316 $query_params['search']['minLength'] = 1; 317 $query_params['context']['default'] = 'view'; 318 319 $query_params['category'] = array( 320 'description' => __( 'Limit results to those matching a category ID.' ), 321 'type' => 'integer', 322 'minimum' => 1, 323 ); 324 325 $query_params['keyword'] = array( 326 'description' => __( 'Limit results to those matching a keyword ID.' ), 327 'type' => 'integer', 328 'minimum' => 1, 329 ); 330 331 $query_params['slug'] = array( 332 'description' => __( 'Limit results to those matching a pattern (slug).' ), 333 'type' => 'array', 334 ); 335 336 $query_params['offset'] = array( 337 'description' => __( 'Offset the result set by a specific number of items.' ), 338 'type' => 'integer', 339 ); 340 341 $query_params['order'] = array( 342 'description' => __( 'Order sort attribute ascending or descending.' ), 343 'type' => 'string', 344 'default' => 'desc', 345 'enum' => array( 'asc', 'desc' ), 346 ); 347 348 $query_params['orderby'] = array( 349 'description' => __( 'Sort collection by post attribute.' ), 350 'type' => 'string', 351 'default' => 'date', 352 'enum' => array( 353 'author', 354 'date', 355 'id', 356 'include', 357 'modified', 358 'parent', 359 'relevance', 360 'slug', 361 'include_slugs', 362 'title', 363 'favorite_count', 364 ), 365 ); 366 367 /** 368 * Filter collection parameters for the block pattern directory controller. 369 * 370 * @since 5.8.0 371 * 372 * @param array $query_params JSON Schema-formatted collection parameters. 373 */ 374 return apply_filters( 'rest_pattern_directory_collection_params', $query_params ); 375 } 376 377 /** 378 * Include a hash of the query args, so that different requests are stored in 379 * separate caches. 380 * 381 * MD5 is chosen for its speed, low-collision rate, universal availability, and to stay 382 * under the character limit for `_site_transient_timeout_{...}` keys. 383 * 384 * @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses 385 * 386 * @since 6.0.0 387 * 388 * @param array $query_args Query arguments to generate a transient key from. 389 * @return string Transient key. 390 */ 391 protected function get_transient_key( $query_args ) { 392 393 if ( isset( $query_args['slug'] ) ) { 394 // This is an additional precaution because the "sort" function expects an array. 395 $query_args['slug'] = wp_parse_list( $query_args['slug'] ); 396 397 // Empty arrays should not affect the transient key. 398 if ( empty( $query_args['slug'] ) ) { 399 unset( $query_args['slug'] ); 400 } else { 401 // Sort the array so that the transient key doesn't depend on the order of slugs. 402 sort( $query_args['slug'] ); 403 } 404 } 405 406 return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) ); 407 } 408 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Mon Apr 28 08:20:01 2025 | Cross-referenced by PHPXref |