class-wp-rest-block-types-controller.php (20330B)
1 <?php 2 /** 3 * REST API: WP_REST_Block_Types_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 5.5.0 8 */ 9 10 /** 11 * Core class used to access block types via the REST API. 12 * 13 * @since 5.5.0 14 * 15 * @see WP_REST_Controller 16 */ 17 class WP_REST_Block_Types_Controller extends WP_REST_Controller { 18 19 /** 20 * Instance of WP_Block_Type_Registry. 21 * 22 * @since 5.5.0 23 * @var WP_Block_Type_Registry 24 */ 25 protected $block_registry; 26 27 /** 28 * Instance of WP_Block_Styles_Registry. 29 * 30 * @since 5.5.0 31 * @var WP_Block_Styles_Registry 32 */ 33 protected $style_registry; 34 35 /** 36 * Constructor. 37 * 38 * @since 5.5.0 39 */ 40 public function __construct() { 41 $this->namespace = 'wp/v2'; 42 $this->rest_base = 'block-types'; 43 $this->block_registry = WP_Block_Type_Registry::get_instance(); 44 $this->style_registry = WP_Block_Styles_Registry::get_instance(); 45 } 46 47 /** 48 * Registers the routes for block types. 49 * 50 * @since 5.5.0 51 * 52 * @see register_rest_route() 53 */ 54 public function register_routes() { 55 56 register_rest_route( 57 $this->namespace, 58 '/' . $this->rest_base, 59 array( 60 array( 61 'methods' => WP_REST_Server::READABLE, 62 'callback' => array( $this, 'get_items' ), 63 'permission_callback' => array( $this, 'get_items_permissions_check' ), 64 'args' => $this->get_collection_params(), 65 ), 66 'schema' => array( $this, 'get_public_item_schema' ), 67 ) 68 ); 69 70 register_rest_route( 71 $this->namespace, 72 '/' . $this->rest_base . '/(?P<namespace>[a-zA-Z0-9_-]+)', 73 array( 74 array( 75 'methods' => WP_REST_Server::READABLE, 76 'callback' => array( $this, 'get_items' ), 77 'permission_callback' => array( $this, 'get_items_permissions_check' ), 78 'args' => $this->get_collection_params(), 79 ), 80 'schema' => array( $this, 'get_public_item_schema' ), 81 ) 82 ); 83 84 register_rest_route( 85 $this->namespace, 86 '/' . $this->rest_base . '/(?P<namespace>[a-zA-Z0-9_-]+)/(?P<name>[a-zA-Z0-9_-]+)', 87 array( 88 'args' => array( 89 'name' => array( 90 'description' => __( 'Block name.' ), 91 'type' => 'string', 92 ), 93 'namespace' => array( 94 'description' => __( 'Block namespace.' ), 95 'type' => 'string', 96 ), 97 ), 98 array( 99 'methods' => WP_REST_Server::READABLE, 100 'callback' => array( $this, 'get_item' ), 101 'permission_callback' => array( $this, 'get_item_permissions_check' ), 102 'args' => array( 103 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 104 ), 105 ), 106 'schema' => array( $this, 'get_public_item_schema' ), 107 ) 108 ); 109 } 110 111 /** 112 * Checks whether a given request has permission to read post block types. 113 * 114 * @since 5.5.0 115 * 116 * @param WP_REST_Request $request Full details about the request. 117 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 118 */ 119 public function get_items_permissions_check( $request ) { 120 return $this->check_read_permission(); 121 } 122 123 /** 124 * Retrieves all post block types, depending on user context. 125 * 126 * @since 5.5.0 127 * 128 * @param WP_REST_Request $request Full details about the request. 129 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 130 */ 131 public function get_items( $request ) { 132 $data = array(); 133 $block_types = $this->block_registry->get_all_registered(); 134 135 // Retrieve the list of registered collection query parameters. 136 $registered = $this->get_collection_params(); 137 $namespace = ''; 138 if ( isset( $registered['namespace'] ) && ! empty( $request['namespace'] ) ) { 139 $namespace = $request['namespace']; 140 } 141 142 foreach ( $block_types as $slug => $obj ) { 143 if ( $namespace ) { 144 list ( $block_namespace ) = explode( '/', $obj->name ); 145 146 if ( $namespace !== $block_namespace ) { 147 continue; 148 } 149 } 150 $block_type = $this->prepare_item_for_response( $obj, $request ); 151 $data[] = $this->prepare_response_for_collection( $block_type ); 152 } 153 154 return rest_ensure_response( $data ); 155 } 156 157 /** 158 * Checks if a given request has access to read a block type. 159 * 160 * @since 5.5.0 161 * 162 * @param WP_REST_Request $request Full details about the request. 163 * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. 164 */ 165 public function get_item_permissions_check( $request ) { 166 $check = $this->check_read_permission(); 167 if ( is_wp_error( $check ) ) { 168 return $check; 169 } 170 $block_name = sprintf( '%s/%s', $request['namespace'], $request['name'] ); 171 $block_type = $this->get_block( $block_name ); 172 if ( is_wp_error( $block_type ) ) { 173 return $block_type; 174 } 175 176 return true; 177 } 178 179 /** 180 * Checks whether a given block type should be visible. 181 * 182 * @since 5.5.0 183 * 184 * @return true|WP_Error True if the block type is visible, WP_Error otherwise. 185 */ 186 protected function check_read_permission() { 187 if ( current_user_can( 'edit_posts' ) ) { 188 return true; 189 } 190 foreach ( get_post_types( array( 'show_in_rest' => true ), 'objects' ) as $post_type ) { 191 if ( current_user_can( $post_type->cap->edit_posts ) ) { 192 return true; 193 } 194 } 195 196 return new WP_Error( 'rest_block_type_cannot_view', __( 'Sorry, you are not allowed to manage block types.' ), array( 'status' => rest_authorization_required_code() ) ); 197 } 198 199 /** 200 * Get the block, if the name is valid. 201 * 202 * @since 5.5.0 203 * 204 * @param string $name Block name. 205 * @return WP_Block_Type|WP_Error Block type object if name is valid, WP_Error otherwise. 206 */ 207 protected function get_block( $name ) { 208 $block_type = $this->block_registry->get_registered( $name ); 209 if ( empty( $block_type ) ) { 210 return new WP_Error( 'rest_block_type_invalid', __( 'Invalid block type.' ), array( 'status' => 404 ) ); 211 } 212 213 return $block_type; 214 } 215 216 /** 217 * Retrieves a specific block type. 218 * 219 * @since 5.5.0 220 * 221 * @param WP_REST_Request $request Full details about the request. 222 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 223 */ 224 public function get_item( $request ) { 225 $block_name = sprintf( '%s/%s', $request['namespace'], $request['name'] ); 226 $block_type = $this->get_block( $block_name ); 227 if ( is_wp_error( $block_type ) ) { 228 return $block_type; 229 } 230 $data = $this->prepare_item_for_response( $block_type, $request ); 231 232 return rest_ensure_response( $data ); 233 } 234 235 /** 236 * Prepares a block type object for serialization. 237 * 238 * @since 5.5.0 239 * 240 * @param WP_Block_Type $block_type Block type data. 241 * @param WP_REST_Request $request Full details about the request. 242 * @return WP_REST_Response Block type data. 243 */ 244 public function prepare_item_for_response( $block_type, $request ) { 245 246 $fields = $this->get_fields_for_response( $request ); 247 $data = array(); 248 249 if ( rest_is_field_included( 'attributes', $fields ) ) { 250 $data['attributes'] = $block_type->get_attributes(); 251 } 252 253 if ( rest_is_field_included( 'is_dynamic', $fields ) ) { 254 $data['is_dynamic'] = $block_type->is_dynamic(); 255 } 256 257 $schema = $this->get_item_schema(); 258 $extra_fields = array( 259 'api_version', 260 'name', 261 'title', 262 'description', 263 'icon', 264 'category', 265 'keywords', 266 'parent', 267 'provides_context', 268 'uses_context', 269 'supports', 270 'styles', 271 'textdomain', 272 'example', 273 'editor_script', 274 'script', 275 'editor_style', 276 'style', 277 'variations', 278 ); 279 foreach ( $extra_fields as $extra_field ) { 280 if ( rest_is_field_included( $extra_field, $fields ) ) { 281 if ( isset( $block_type->$extra_field ) ) { 282 $field = $block_type->$extra_field; 283 } elseif ( array_key_exists( 'default', $schema['properties'][ $extra_field ] ) ) { 284 $field = $schema['properties'][ $extra_field ]['default']; 285 } else { 286 $field = ''; 287 } 288 $data[ $extra_field ] = rest_sanitize_value_from_schema( $field, $schema['properties'][ $extra_field ] ); 289 } 290 } 291 292 if ( rest_is_field_included( 'styles', $fields ) ) { 293 $styles = $this->style_registry->get_registered_styles_for_block( $block_type->name ); 294 $styles = array_values( $styles ); 295 $data['styles'] = wp_parse_args( $styles, $data['styles'] ); 296 $data['styles'] = array_filter( $data['styles'] ); 297 } 298 299 $context = ! empty( $request['context'] ) ? $request['context'] : 'view'; 300 $data = $this->add_additional_fields_to_object( $data, $request ); 301 $data = $this->filter_response_by_context( $data, $context ); 302 303 $response = rest_ensure_response( $data ); 304 305 $response->add_links( $this->prepare_links( $block_type ) ); 306 307 /** 308 * Filters a block type returned from the REST API. 309 * 310 * Allows modification of the block type data right before it is returned. 311 * 312 * @since 5.5.0 313 * 314 * @param WP_REST_Response $response The response object. 315 * @param WP_Block_Type $block_type The original block type object. 316 * @param WP_REST_Request $request Request used to generate the response. 317 */ 318 return apply_filters( 'rest_prepare_block_type', $response, $block_type, $request ); 319 } 320 321 /** 322 * Prepares links for the request. 323 * 324 * @since 5.5.0 325 * 326 * @param WP_Block_Type $block_type Block type data. 327 * @return array Links for the given block type. 328 */ 329 protected function prepare_links( $block_type ) { 330 list( $namespace ) = explode( '/', $block_type->name ); 331 332 $links = array( 333 'collection' => array( 334 'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ), 335 ), 336 'self' => array( 337 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $block_type->name ) ), 338 ), 339 'up' => array( 340 'href' => rest_url( sprintf( '%s/%s/%s', $this->namespace, $this->rest_base, $namespace ) ), 341 ), 342 ); 343 344 if ( $block_type->is_dynamic() ) { 345 $links['https://api.w.org/render-block'] = array( 346 'href' => add_query_arg( 'context', 'edit', rest_url( sprintf( '%s/%s/%s', 'wp/v2', 'block-renderer', $block_type->name ) ) ), 347 ); 348 } 349 350 return $links; 351 } 352 353 /** 354 * Retrieves the block type' schema, conforming to JSON Schema. 355 * 356 * @since 5.5.0 357 * 358 * @return array Item schema data. 359 */ 360 public function get_item_schema() { 361 if ( $this->schema ) { 362 return $this->add_additional_fields_schema( $this->schema ); 363 } 364 365 // rest_validate_value_from_schema doesn't understand $refs, pull out reused definitions for readability. 366 $inner_blocks_definition = array( 367 'description' => __( 'The list of inner blocks used in the example.' ), 368 'type' => 'array', 369 'items' => array( 370 'type' => 'object', 371 'properties' => array( 372 'name' => array( 373 'description' => __( 'The name of the inner block.' ), 374 'type' => 'string', 375 ), 376 'attributes' => array( 377 'description' => __( 'The attributes of the inner block.' ), 378 'type' => 'object', 379 ), 380 'innerBlocks' => array( 381 'description' => __( "A list of the inner block's own inner blocks. This is a recursive definition following the parent innerBlocks schema." ), 382 'type' => 'array', 383 ), 384 ), 385 ), 386 ); 387 388 $example_definition = array( 389 'description' => __( 'Block example.' ), 390 'type' => array( 'object', 'null' ), 391 'default' => null, 392 'properties' => array( 393 'attributes' => array( 394 'description' => __( 'The attributes used in the example.' ), 395 'type' => 'object', 396 ), 397 'innerBlocks' => $inner_blocks_definition, 398 ), 399 'context' => array( 'embed', 'view', 'edit' ), 400 'readonly' => true, 401 ); 402 403 $keywords_definition = array( 404 'description' => __( 'Block keywords.' ), 405 'type' => 'array', 406 'items' => array( 407 'type' => 'string', 408 ), 409 'default' => array(), 410 'context' => array( 'embed', 'view', 'edit' ), 411 'readonly' => true, 412 ); 413 414 $icon_definition = array( 415 'description' => __( 'Icon of block type.' ), 416 'type' => array( 'string', 'null' ), 417 'default' => null, 418 'context' => array( 'embed', 'view', 'edit' ), 419 'readonly' => true, 420 ); 421 422 $category_definition = array( 423 'description' => __( 'Block category.' ), 424 'type' => array( 'string', 'null' ), 425 'default' => null, 426 'context' => array( 'embed', 'view', 'edit' ), 427 'readonly' => true, 428 ); 429 430 $schema = array( 431 '$schema' => 'http://json-schema.org/draft-04/schema#', 432 'title' => 'block-type', 433 'type' => 'object', 434 'properties' => array( 435 'api_version' => array( 436 'description' => __( 'Version of block API.' ), 437 'type' => 'integer', 438 'default' => 1, 439 'context' => array( 'embed', 'view', 'edit' ), 440 'readonly' => true, 441 ), 442 'title' => array( 443 'description' => __( 'Title of block type.' ), 444 'type' => 'string', 445 'default' => '', 446 'context' => array( 'embed', 'view', 'edit' ), 447 'readonly' => true, 448 ), 449 'name' => array( 450 'description' => __( 'Unique name identifying the block type.' ), 451 'type' => 'string', 452 'default' => '', 453 'context' => array( 'embed', 'view', 'edit' ), 454 'readonly' => true, 455 ), 456 'description' => array( 457 'description' => __( 'Description of block type.' ), 458 'type' => 'string', 459 'default' => '', 460 'context' => array( 'embed', 'view', 'edit' ), 461 'readonly' => true, 462 ), 463 'icon' => $icon_definition, 464 'attributes' => array( 465 'description' => __( 'Block attributes.' ), 466 'type' => array( 'object', 'null' ), 467 'properties' => array(), 468 'default' => null, 469 'additionalProperties' => array( 470 'type' => 'object', 471 ), 472 'context' => array( 'embed', 'view', 'edit' ), 473 'readonly' => true, 474 ), 475 'provides_context' => array( 476 'description' => __( 'Context provided by blocks of this type.' ), 477 'type' => 'object', 478 'properties' => array(), 479 'additionalProperties' => array( 480 'type' => 'string', 481 ), 482 'default' => array(), 483 'context' => array( 'embed', 'view', 'edit' ), 484 'readonly' => true, 485 ), 486 'uses_context' => array( 487 'description' => __( 'Context values inherited by blocks of this type.' ), 488 'type' => 'array', 489 'default' => array(), 490 'items' => array( 491 'type' => 'string', 492 ), 493 'context' => array( 'embed', 'view', 'edit' ), 494 'readonly' => true, 495 ), 496 'supports' => array( 497 'description' => __( 'Block supports.' ), 498 'type' => 'object', 499 'default' => array(), 500 'properties' => array(), 501 'context' => array( 'embed', 'view', 'edit' ), 502 'readonly' => true, 503 ), 504 'category' => $category_definition, 505 'is_dynamic' => array( 506 'description' => __( 'Is the block dynamically rendered.' ), 507 'type' => 'boolean', 508 'default' => false, 509 'context' => array( 'embed', 'view', 'edit' ), 510 'readonly' => true, 511 ), 512 'editor_script' => array( 513 'description' => __( 'Editor script handle.' ), 514 'type' => array( 'string', 'null' ), 515 'default' => null, 516 'context' => array( 'embed', 'view', 'edit' ), 517 'readonly' => true, 518 ), 519 'script' => array( 520 'description' => __( 'Public facing script handle.' ), 521 'type' => array( 'string', 'null' ), 522 'default' => null, 523 'context' => array( 'embed', 'view', 'edit' ), 524 'readonly' => true, 525 ), 526 'editor_style' => array( 527 'description' => __( 'Editor style handle.' ), 528 'type' => array( 'string', 'null' ), 529 'default' => null, 530 'context' => array( 'embed', 'view', 'edit' ), 531 'readonly' => true, 532 ), 533 'style' => array( 534 'description' => __( 'Public facing style handle.' ), 535 'type' => array( 'string', 'null' ), 536 'default' => null, 537 'context' => array( 'embed', 'view', 'edit' ), 538 'readonly' => true, 539 ), 540 'styles' => array( 541 'description' => __( 'Block style variations.' ), 542 'type' => 'array', 543 'items' => array( 544 'type' => 'object', 545 'properties' => array( 546 'name' => array( 547 'description' => __( 'Unique name identifying the style.' ), 548 'type' => 'string', 549 'required' => true, 550 ), 551 'label' => array( 552 'description' => __( 'The human-readable label for the style.' ), 553 'type' => 'string', 554 ), 555 'inline_style' => array( 556 'description' => __( 'Inline CSS code that registers the CSS class required for the style.' ), 557 'type' => 'string', 558 ), 559 'style_handle' => array( 560 'description' => __( 'Contains the handle that defines the block style.' ), 561 'type' => 'string', 562 ), 563 ), 564 ), 565 'default' => array(), 566 'context' => array( 'embed', 'view', 'edit' ), 567 'readonly' => true, 568 ), 569 'variations' => array( 570 'description' => __( 'Block variations.' ), 571 'type' => 'array', 572 'items' => array( 573 'type' => 'object', 574 'properties' => array( 575 'name' => array( 576 'description' => __( 'The unique and machine-readable name.' ), 577 'type' => 'string', 578 'required' => true, 579 ), 580 'title' => array( 581 'description' => __( 'A human-readable variation title.' ), 582 'type' => 'string', 583 'required' => true, 584 ), 585 'description' => array( 586 'description' => __( 'A detailed variation description.' ), 587 'type' => 'string', 588 'required' => false, 589 ), 590 'category' => $category_definition, 591 'icon' => $icon_definition, 592 'isDefault' => array( 593 'description' => __( 'Indicates whether the current variation is the default one.' ), 594 'type' => 'boolean', 595 'required' => false, 596 'default' => false, 597 ), 598 'attributes' => array( 599 'description' => __( 'The initial values for attributes.' ), 600 'type' => 'object', 601 ), 602 'innerBlocks' => $inner_blocks_definition, 603 'example' => $example_definition, 604 'scope' => array( 605 'description' => __( 'The list of scopes where the variation is applicable. When not provided, it assumes all available scopes.' ), 606 'type' => array( 'array', 'null' ), 607 'default' => null, 608 'items' => array( 609 'type' => 'string', 610 'enum' => array( 'block', 'inserter', 'transform' ), 611 ), 612 'readonly' => true, 613 ), 614 'keywords' => $keywords_definition, 615 ), 616 ), 617 'readonly' => true, 618 'context' => array( 'embed', 'view', 'edit' ), 619 'default' => null, 620 ), 621 'textdomain' => array( 622 'description' => __( 'Public text domain.' ), 623 'type' => array( 'string', 'null' ), 624 'default' => null, 625 'context' => array( 'embed', 'view', 'edit' ), 626 'readonly' => true, 627 ), 628 'parent' => array( 629 'description' => __( 'Parent blocks.' ), 630 'type' => array( 'array', 'null' ), 631 'items' => array( 632 'type' => 'string', 633 ), 634 'default' => null, 635 'context' => array( 'embed', 'view', 'edit' ), 636 'readonly' => true, 637 ), 638 'keywords' => $keywords_definition, 639 'example' => $example_definition, 640 ), 641 ); 642 643 $this->schema = $schema; 644 645 return $this->add_additional_fields_schema( $this->schema ); 646 } 647 648 /** 649 * Retrieves the query params for collections. 650 * 651 * @since 5.5.0 652 * 653 * @return array Collection parameters. 654 */ 655 public function get_collection_params() { 656 return array( 657 'context' => $this->get_context_param( array( 'default' => 'view' ) ), 658 'namespace' => array( 659 'description' => __( 'Block namespace.' ), 660 'type' => 'string', 661 ), 662 ); 663 } 664 665 }