[ 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 $response = array(); 165 166 if ( $raw_patterns ) { 167 foreach ( $raw_patterns as $pattern ) { 168 $response[] = $this->prepare_response_for_collection( 169 $this->prepare_item_for_response( $pattern, $request ) 170 ); 171 } 172 } 173 174 return new WP_REST_Response( $response ); 175 } 176 177 /** 178 * Prepare a raw block pattern before it gets output in a REST API response. 179 * 180 * @since 5.8.0 181 * @since 5.9.0 Renamed `$raw_pattern` to `$item` to match parent class for PHP 8 named parameter support. 182 * 183 * @param object $item Raw pattern from api.wordpress.org, before any changes. 184 * @param WP_REST_Request $request Request object. 185 * @return WP_REST_Response 186 */ 187 public function prepare_item_for_response( $item, $request ) { 188 // Restores the more descriptive, specific name for use within this method. 189 $raw_pattern = $item; 190 191 $prepared_pattern = array( 192 'id' => absint( $raw_pattern->id ), 193 'title' => sanitize_text_field( $raw_pattern->title->rendered ), 194 'content' => wp_kses_post( $raw_pattern->pattern_content ), 195 'categories' => array_map( 'sanitize_title', $raw_pattern->category_slugs ), 196 'keywords' => array_map( 'sanitize_text_field', explode( ',', $raw_pattern->meta->wpop_keywords ) ), 197 'description' => sanitize_text_field( $raw_pattern->meta->wpop_description ), 198 'viewport_width' => absint( $raw_pattern->meta->wpop_viewport_width ), 199 'block_types' => array_map( 'sanitize_text_field', $raw_pattern->meta->wpop_block_types ), 200 ); 201 202 $prepared_pattern = $this->add_additional_fields_to_object( $prepared_pattern, $request ); 203 204 $response = new WP_REST_Response( $prepared_pattern ); 205 206 /** 207 * Filters the REST API response for a block pattern. 208 * 209 * @since 5.8.0 210 * 211 * @param WP_REST_Response $response The response object. 212 * @param object $raw_pattern The unprepared block pattern. 213 * @param WP_REST_Request $request The request object. 214 */ 215 return apply_filters( 'rest_prepare_block_pattern', $response, $raw_pattern, $request ); 216 } 217 218 /** 219 * Retrieves the block pattern's schema, conforming to JSON Schema. 220 * 221 * @since 5.8.0 222 * @since 6.2.0 Added `'block_types'` to schema. 223 * 224 * @return array Item schema data. 225 */ 226 public function get_item_schema() { 227 if ( $this->schema ) { 228 return $this->add_additional_fields_schema( $this->schema ); 229 } 230 231 $this->schema = array( 232 '$schema' => 'http://json-schema.org/draft-04/schema#', 233 'title' => 'pattern-directory-item', 234 'type' => 'object', 235 'properties' => array( 236 'id' => array( 237 'description' => __( 'The pattern ID.' ), 238 'type' => 'integer', 239 'minimum' => 1, 240 'context' => array( 'view', 'edit', 'embed' ), 241 ), 242 243 'title' => array( 244 'description' => __( 'The pattern title, in human readable format.' ), 245 'type' => 'string', 246 'minLength' => 1, 247 'context' => array( 'view', 'edit', 'embed' ), 248 ), 249 250 'content' => array( 251 'description' => __( 'The pattern content.' ), 252 'type' => 'string', 253 'minLength' => 1, 254 'context' => array( 'view', 'edit', 'embed' ), 255 ), 256 257 'categories' => array( 258 'description' => __( "The pattern's category slugs." ), 259 'type' => 'array', 260 'uniqueItems' => true, 261 'items' => array( 'type' => 'string' ), 262 'context' => array( 'view', 'edit', 'embed' ), 263 ), 264 265 'keywords' => array( 266 'description' => __( "The pattern's keywords." ), 267 'type' => 'array', 268 'uniqueItems' => true, 269 'items' => array( 'type' => 'string' ), 270 'context' => array( 'view', 'edit', 'embed' ), 271 ), 272 273 'description' => array( 274 'description' => __( 'A description of the pattern.' ), 275 'type' => 'string', 276 'minLength' => 1, 277 'context' => array( 'view', 'edit', 'embed' ), 278 ), 279 280 'viewport_width' => array( 281 'description' => __( 'The preferred width of the viewport when previewing a pattern, in pixels.' ), 282 'type' => 'integer', 283 'context' => array( 'view', 'edit', 'embed' ), 284 ), 285 286 'block_types' => array( 287 'description' => __( 'The block types which can use this pattern.' ), 288 'type' => 'array', 289 'uniqueItems' => true, 290 'items' => array( 'type' => 'string' ), 291 'context' => array( 'view', 'embed' ), 292 ), 293 ), 294 ); 295 296 return $this->add_additional_fields_schema( $this->schema ); 297 } 298 299 /** 300 * Retrieves the search parameters for the block pattern's collection. 301 * 302 * @since 5.8.0 303 * @since 6.2.0 Added 'per_page', 'page', 'offset', 'order', and 'orderby' to request. 304 * 305 * @return array Collection parameters. 306 */ 307 public function get_collection_params() { 308 $query_params = parent::get_collection_params(); 309 310 $query_params['per_page']['default'] = 100; 311 $query_params['search']['minLength'] = 1; 312 $query_params['context']['default'] = 'view'; 313 314 $query_params['category'] = array( 315 'description' => __( 'Limit results to those matching a category ID.' ), 316 'type' => 'integer', 317 'minimum' => 1, 318 ); 319 320 $query_params['keyword'] = array( 321 'description' => __( 'Limit results to those matching a keyword ID.' ), 322 'type' => 'integer', 323 'minimum' => 1, 324 ); 325 326 $query_params['slug'] = array( 327 'description' => __( 'Limit results to those matching a pattern (slug).' ), 328 'type' => 'array', 329 ); 330 331 $query_params['offset'] = array( 332 'description' => __( 'Offset the result set by a specific number of items.' ), 333 'type' => 'integer', 334 ); 335 336 $query_params['order'] = array( 337 'description' => __( 'Order sort attribute ascending or descending.' ), 338 'type' => 'string', 339 'default' => 'desc', 340 'enum' => array( 'asc', 'desc' ), 341 ); 342 343 $query_params['orderby'] = array( 344 'description' => __( 'Sort collection by post attribute.' ), 345 'type' => 'string', 346 'default' => 'date', 347 'enum' => array( 348 'author', 349 'date', 350 'id', 351 'include', 352 'modified', 353 'parent', 354 'relevance', 355 'slug', 356 'include_slugs', 357 'title', 358 'favorite_count', 359 ), 360 ); 361 362 /** 363 * Filter collection parameters for the block pattern directory controller. 364 * 365 * @since 5.8.0 366 * 367 * @param array $query_params JSON Schema-formatted collection parameters. 368 */ 369 return apply_filters( 'rest_pattern_directory_collection_params', $query_params ); 370 } 371 372 /** 373 * Include a hash of the query args, so that different requests are stored in 374 * separate caches. 375 * 376 * MD5 is chosen for its speed, low-collision rate, universal availability, and to stay 377 * under the character limit for `_site_transient_timeout_{...}` keys. 378 * 379 * @link https://stackoverflow.com/questions/3665247/fastest-hash-for-non-cryptographic-uses 380 * 381 * @since 6.0.0 382 * 383 * @param array $query_args Query arguments to generate a transient key from. 384 * @return string Transient key. 385 */ 386 protected function get_transient_key( $query_args ) { 387 388 if ( isset( $query_args['slug'] ) ) { 389 // This is an additional precaution because the "sort" function expects an array. 390 $query_args['slug'] = wp_parse_list( $query_args['slug'] ); 391 392 // Empty arrays should not affect the transient key. 393 if ( empty( $query_args['slug'] ) ) { 394 unset( $query_args['slug'] ); 395 } else { 396 // Sort the array so that the transient key doesn't depend on the order of slugs. 397 sort( $query_args['slug'] ); 398 } 399 } 400 401 return 'wp_remote_block_patterns_' . md5( serialize( $query_args ) ); 402 } 403 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Nov 21 08:20:01 2024 | Cross-referenced by PHPXref |