| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * REST API: WP_REST_View_Config_Controller class 4 * 5 * @package WordPress 6 * @subpackage REST_API 7 * @since 7.1.0 8 */ 9 10 /** 11 * Controller which provides a REST endpoint for retrieving the default 12 * view configuration for a given entity type. 13 * 14 * @since 7.1.0 15 * 16 * @see WP_REST_Controller 17 */ 18 class WP_REST_View_Config_Controller extends WP_REST_Controller { 19 20 /** 21 * Constructor. 22 * 23 * @since 7.1.0 24 */ 25 public function __construct() { 26 $this->namespace = 'wp/v2'; 27 $this->rest_base = 'view-config'; 28 } 29 30 /** 31 * Registers the routes for the controller. 32 * 33 * @since 7.1.0 34 */ 35 public function register_routes() { 36 register_rest_route( 37 $this->namespace, 38 '/' . $this->rest_base, 39 array( 40 array( 41 'methods' => WP_REST_Server::READABLE, 42 'callback' => array( $this, 'get_items' ), 43 'permission_callback' => array( $this, 'get_items_permissions_check' ), 44 'args' => array( 45 'kind' => array( 46 'description' => __( 'Entity kind.' ), 47 'type' => 'string', 48 'required' => true, 49 ), 50 'name' => array( 51 'description' => __( 'Entity name.' ), 52 'type' => 'string', 53 'required' => true, 54 ), 55 ), 56 ), 57 'schema' => array( $this, 'get_public_item_schema' ), 58 ) 59 ); 60 } 61 62 /** 63 * Checks if a given request has access to read view config. 64 * 65 * @since 7.1.0 66 * 67 * @param WP_REST_Request $request Full details about the request. 68 * @return true|WP_Error True if the request has read access, WP_Error object otherwise. 69 */ 70 public function get_items_permissions_check( $request ) { 71 $kind = $request->get_param( 'kind' ); 72 $name = $request->get_param( 'name' ); 73 74 $capability = $this->get_required_capability( $kind, $name ); 75 76 if ( null === $capability ) { 77 return new WP_Error( 78 'rest_view_config_invalid_entity', 79 __( 'Invalid entity kind or name.' ), 80 array( 'status' => 404 ) 81 ); 82 } 83 84 if ( ! current_user_can( $capability ) ) { 85 return new WP_Error( 86 'rest_cannot_read', 87 __( 'Sorry, you are not allowed to read view config.' ), 88 array( 'status' => rest_authorization_required_code() ) 89 ); 90 } 91 92 return true; 93 } 94 95 /** 96 * Resolves the capability required to read the view config for an entity. 97 * 98 * Known kinds map to the capability that gates managing that entity's list: 99 * post types use their own `edit_posts` capability (which honors custom 100 * `capability_type` registrations), taxonomies use `manage_terms`, and 101 * root-level entities use `manage_options`. A post type or taxonomy that is 102 * not registered, or not exposed to the REST API, resolves to `null` so the 103 * request is treated as referencing an unknown entity. 104 * 105 * Any other kind falls back to `edit_posts`. This keeps entities registered 106 * through the `get_entity_view_config_{$kind}_{$name}` filter readable behind 107 * a baseline capability. 108 * 109 * @since 7.1.0 110 * 111 * @param string $kind The entity kind (e.g. `postType`). 112 * @param string $name The entity name (e.g. `page`). 113 * @return string|null Capability required to read the config, or null if the 114 * entity is not registered. 115 */ 116 protected function get_required_capability( $kind, $name ) { 117 switch ( $kind ) { 118 case 'postType': 119 $post_type = get_post_type_object( $name ); 120 if ( $post_type && $post_type->show_in_rest ) { 121 return $post_type->cap->edit_posts; 122 } 123 return null; 124 125 case 'taxonomy': 126 $taxonomy = get_taxonomy( $name ); 127 if ( $taxonomy && $taxonomy->show_in_rest ) { 128 return $taxonomy->cap->manage_terms; 129 } 130 return null; 131 132 case 'root': 133 return 'manage_options'; 134 } 135 136 return 'edit_posts'; 137 } 138 139 /** 140 * Returns the default view configuration for the given entity type. 141 * 142 * @since 7.1.0 143 * 144 * @param WP_REST_Request $request Full details about the request. 145 * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. 146 */ 147 public function get_items( $request ) { 148 $kind = $request->get_param( 'kind' ); 149 $name = $request->get_param( 'name' ); 150 151 $config = wp_get_entity_view_config( $kind, $name ); 152 $schema = $this->get_item_schema(); 153 154 $response = array( 155 'kind' => $kind, 156 'name' => $name, 157 'version' => WP_View_Config_Data::LATEST_VERSION, 158 'default_view' => $this->cast_empty_objects( $config['default_view'], $schema['properties']['default_view'] ), 159 'default_layouts' => $this->cast_empty_objects( $config['default_layouts'], $schema['properties']['default_layouts'] ), 160 'view_list' => $this->cast_empty_objects( $config['view_list'], $schema['properties']['view_list'] ), 161 'form' => $this->cast_empty_objects( $config['form'], $schema['properties']['form'] ), 162 ); 163 164 return rest_ensure_response( $response ); 165 } 166 167 /** 168 * Recursively casts empty arrays to objects where the schema types them as 169 * objects. 170 * 171 * PHP cannot distinguish an empty associative array from an empty list, so 172 * `json_encode()` always serializes `array()` as a JSON array (`[]`). The 173 * REST schema, however, types several values as objects, which must encode 174 * as `{}`. This walks the value against its schema and casts any empty, 175 * object-typed array to an object. Non-empty associative arrays already 176 * encode as objects, so they are left as arrays and only recursed into to 177 * fix any nested empty objects. 178 * 179 * Union schemas (`oneOf`/`anyOf`) are handled only for the empty-array case: 180 * an empty value is cast to an object when any branch allows an object. Such 181 * values are not recursed into, which is sufficient for the form schema 182 * where they never contain empty nested objects. 183 * 184 * @since 7.1.0 185 * 186 * @param mixed $value The value to normalize. 187 * @param array $schema The schema node describing the value. 188 * @return mixed The normalized value, with empty object-typed arrays cast to objects. 189 */ 190 protected function cast_empty_objects( $value, $schema ) { 191 if ( ! is_array( $value ) || ! is_array( $schema ) ) { 192 return $value; 193 } 194 195 if ( isset( $schema['oneOf'] ) || isset( $schema['anyOf'] ) ) { 196 $branches = isset( $schema['oneOf'] ) ? $schema['oneOf'] : $schema['anyOf']; 197 if ( array() === $value ) { 198 foreach ( $branches as $branch ) { 199 if ( is_array( $branch ) && in_array( 'object', (array) ( isset( $branch['type'] ) ? $branch['type'] : array() ), true ) ) { 200 return (object) array(); 201 } 202 } 203 } 204 return $value; 205 } 206 207 $types = (array) ( isset( $schema['type'] ) ? $schema['type'] : array() ); 208 209 if ( in_array( 'array', $types, true ) && isset( $schema['items'] ) ) { 210 foreach ( $value as $index => $item ) { 211 $value[ $index ] = $this->cast_empty_objects( $item, $schema['items'] ); 212 } 213 return $value; 214 } 215 216 if ( in_array( 'object', $types, true ) ) { 217 if ( isset( $schema['properties'] ) ) { 218 foreach ( $schema['properties'] as $property => $property_schema ) { 219 if ( array_key_exists( $property, $value ) ) { 220 $value[ $property ] = $this->cast_empty_objects( $value[ $property ], $property_schema ); 221 } 222 } 223 } 224 if ( isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] ) ) { 225 foreach ( $value as $key => $item ) { 226 if ( isset( $schema['properties'][ $key ] ) ) { 227 continue; 228 } 229 $value[ $key ] = $this->cast_empty_objects( $item, $schema['additionalProperties'] ); 230 } 231 } 232 233 // Empty object-typed arrays must serialize as {} to match the schema. 234 if ( array() === $value ) { 235 return (object) array(); 236 } 237 } 238 239 return $value; 240 } 241 242 /** 243 * Retrieves the item's schema, conforming to JSON Schema. 244 * 245 * @since 7.1.0 246 * 247 * @return array Item schema data. 248 */ 249 public function get_item_schema() { 250 if ( $this->schema ) { 251 return $this->add_additional_fields_schema( $this->schema ); 252 } 253 254 $view_base_properties = $this->get_view_base_schema(); 255 256 $this->schema = array( 257 '$schema' => 'http://json-schema.org/draft-04/schema#', 258 'title' => 'view-config', 259 'type' => 'object', 260 'properties' => array( 261 'kind' => array( 262 'description' => __( 'Entity kind.' ), 263 'type' => 'string', 264 'readonly' => true, 265 ), 266 'name' => array( 267 'description' => __( 'Entity name.' ), 268 'type' => 'string', 269 'readonly' => true, 270 ), 271 'version' => array( 272 'description' => __( 'The schema version of the configuration.' ), 273 'type' => 'integer', 274 'readonly' => true, 275 ), 276 'default_view' => array( 277 'description' => __( 'Default view configuration.' ), 278 'type' => 'object', 279 'readonly' => true, 280 'properties' => array_merge( 281 array( 282 'type' => array( 283 'type' => 'string', 284 ), 285 'layout' => $this->get_combined_layout_schema(), 286 ), 287 $view_base_properties 288 ), 289 ), 290 'default_layouts' => array( 291 'description' => __( 'Default layout configurations.' ), 292 'type' => 'object', 293 'readonly' => true, 294 'properties' => array( 295 'table' => array( 296 'type' => 'object', 297 'properties' => array_merge( 298 $view_base_properties, 299 array( 300 'layout' => $this->get_table_layout_schema(), 301 ) 302 ), 303 ), 304 'list' => array( 305 'type' => 'object', 306 'properties' => array_merge( 307 $view_base_properties, 308 array( 309 'layout' => $this->get_list_layout_schema(), 310 ) 311 ), 312 ), 313 'grid' => array( 314 'type' => 'object', 315 'properties' => array_merge( 316 $view_base_properties, 317 array( 318 'layout' => $this->get_grid_layout_schema(), 319 ) 320 ), 321 ), 322 'activity' => array( 323 'type' => 'object', 324 'properties' => array_merge( 325 $view_base_properties, 326 array( 327 'layout' => $this->get_list_layout_schema(), 328 ) 329 ), 330 ), 331 'pickerGrid' => array( 332 'type' => 'object', 333 'properties' => array_merge( 334 $view_base_properties, 335 array( 336 'layout' => $this->get_grid_layout_schema(), 337 ) 338 ), 339 ), 340 'pickerTable' => array( 341 'type' => 'object', 342 'properties' => array_merge( 343 $view_base_properties, 344 array( 345 'layout' => $this->get_table_layout_schema(), 346 ) 347 ), 348 ), 349 ), 350 ), 351 'view_list' => array( 352 'description' => __( 'List of default views.' ), 353 'type' => 'array', 354 'readonly' => true, 355 'items' => array( 356 'type' => 'object', 357 'properties' => array( 358 'title' => array( 359 'type' => 'string', 360 ), 361 'slug' => array( 362 'type' => 'string', 363 ), 364 'view' => array( 365 'type' => 'object', 366 'properties' => array_merge( 367 array( 368 'type' => array( 369 'type' => 'string', 370 ), 371 'layout' => $this->get_combined_layout_schema(), 372 ), 373 $view_base_properties 374 ), 375 ), 376 ), 377 ), 378 ), 379 'form' => array( 380 'description' => __( 'Default form configuration.' ), 381 'type' => 'object', 382 'readonly' => true, 383 'properties' => $this->get_form_schema(), 384 ), 385 ), 386 ); 387 388 return $this->add_additional_fields_schema( $this->schema ); 389 } 390 391 /** 392 * Returns the schema properties shared by all view types (ViewBase), excluding 'type'. 393 * 394 * @since 7.1.0 395 * 396 * @return array Schema properties for the base view configuration. 397 */ 398 protected function get_view_base_schema() { 399 return array( 400 'search' => array( 401 'type' => 'string', 402 ), 403 'filters' => array( 404 'type' => 'array', 405 'items' => array( 406 'type' => 'object', 407 'properties' => array( 408 'field' => array( 409 'type' => 'string', 410 ), 411 'operator' => array( 412 'type' => 'string', 413 'enum' => array( 414 'is', 415 'isNot', 416 'isAny', 417 'isNone', 418 'isAll', 419 'isNotAll', 420 'lessThan', 421 'greaterThan', 422 'lessThanOrEqual', 423 'greaterThanOrEqual', 424 'before', 425 'after', 426 ), 427 ), 428 'value' => array(), 429 'isLocked' => array( 430 'type' => 'boolean', 431 ), 432 ), 433 ), 434 ), 435 'sort' => array( 436 'type' => 'object', 437 'properties' => array( 438 'field' => array( 439 'type' => 'string', 440 ), 441 'direction' => array( 442 'type' => 'string', 443 'enum' => array( 'asc', 'desc' ), 444 ), 445 ), 446 ), 447 'page' => array( 448 'type' => 'integer', 449 ), 450 'perPage' => array( 451 'type' => 'integer', 452 ), 453 'fields' => array( 454 'type' => 'array', 455 'items' => array( 456 'type' => 'string', 457 ), 458 ), 459 'titleField' => array( 460 'type' => 'string', 461 ), 462 'mediaField' => array( 463 'type' => 'string', 464 ), 465 'descriptionField' => array( 466 'type' => 'string', 467 ), 468 'showTitle' => array( 469 'type' => 'boolean', 470 ), 471 'showMedia' => array( 472 'type' => 'boolean', 473 ), 474 'showDescription' => array( 475 'type' => 'boolean', 476 ), 477 'showLevels' => array( 478 'type' => 'boolean', 479 ), 480 'groupBy' => array( 481 'type' => 'object', 482 'properties' => array( 483 'field' => array( 484 'type' => 'string', 485 ), 486 'direction' => array( 487 'type' => 'string', 488 'enum' => array( 'asc', 'desc' ), 489 ), 490 'showLabel' => array( 491 'type' => 'boolean', 492 'default' => true, 493 ), 494 ), 495 ), 496 'infiniteScrollEnabled' => array( 497 'type' => 'boolean', 498 ), 499 ); 500 } 501 502 /** 503 * Returns the schema for the ColumnStyle type. 504 * 505 * @since 7.1.0 506 * 507 * @return array Schema for a column style object. 508 */ 509 protected function get_column_style_schema() { 510 return array( 511 'type' => 'object', 512 'properties' => array( 513 'width' => array( 514 'type' => array( 'string', 'number' ), 515 ), 516 'maxWidth' => array( 517 'type' => array( 'string', 'number' ), 518 ), 519 'minWidth' => array( 520 'type' => array( 'string', 'number' ), 521 ), 522 'align' => array( 523 'type' => 'string', 524 'enum' => array( 'start', 'center', 'end' ), 525 ), 526 ), 527 ); 528 } 529 530 /** 531 * Returns the layout schema for table-type views (ViewTable, ViewPickerTable). 532 * 533 * @since 7.1.0 534 * 535 * @return array Schema for a table layout object. 536 */ 537 protected function get_table_layout_schema() { 538 return array( 539 'type' => 'object', 540 'properties' => array( 541 'styles' => array( 542 'type' => 'object', 543 'additionalProperties' => $this->get_column_style_schema(), 544 ), 545 'density' => array( 546 'type' => 'string', 547 'enum' => array( 'compact', 'balanced', 'comfortable' ), 548 ), 549 'enableMoving' => array( 550 'type' => 'boolean', 551 ), 552 ), 553 ); 554 } 555 556 /** 557 * Returns the layout schema for list-type views (ViewList, ViewActivity). 558 * 559 * @since 7.1.0 560 * 561 * @return array Schema for a list layout object. 562 */ 563 protected function get_list_layout_schema() { 564 return array( 565 'type' => 'object', 566 'properties' => array( 567 'density' => array( 568 'type' => 'string', 569 'enum' => array( 'compact', 'balanced', 'comfortable' ), 570 ), 571 ), 572 ); 573 } 574 575 /** 576 * Returns a combined layout schema that accepts properties from all view types. 577 * 578 * This is useful for contexts where the view type is not known ahead of time 579 * (e.g. the `view` override in a view list item), so all possible layout 580 * properties must be accepted. 581 * 582 * @since 7.1.0 583 * 584 * @return array Schema for a combined layout object. 585 */ 586 protected function get_combined_layout_schema() { 587 return array( 588 'type' => 'object', 589 'properties' => array_merge( 590 $this->get_table_layout_schema()['properties'], 591 $this->get_grid_layout_schema()['properties'], 592 $this->get_list_layout_schema()['properties'] 593 ), 594 ); 595 } 596 597 /** 598 * Returns the layout schema for grid-type views (ViewGrid, ViewPickerGrid). 599 * 600 * @since 7.1.0 601 * 602 * @return array Schema for a grid layout object. 603 */ 604 protected function get_grid_layout_schema() { 605 return array( 606 'type' => 'object', 607 'properties' => array( 608 'badgeFields' => array( 609 'type' => 'array', 610 'items' => array( 611 'type' => 'string', 612 ), 613 ), 614 'previewSize' => array( 615 'type' => 'number', 616 ), 617 'density' => array( 618 'type' => 'string', 619 'enum' => array( 'compact', 'balanced', 'comfortable' ), 620 ), 621 ), 622 ); 623 } 624 625 /** 626 * Returns the schema for a form layout object as a discriminated union. 627 * 628 * Each variant is discriminated by a single-value enum on its `type` property, 629 * matching the TypeScript Layout union in dataviews/src/types/dataform.ts. 630 * 631 * @since 7.1.0 632 * 633 * @return array Schema for a form layout object. 634 */ 635 protected function get_form_layout_schema() { 636 return array( 637 'oneOf' => array( 638 // RegularLayout. 639 array( 640 'type' => 'object', 641 'properties' => array( 642 'type' => array( 643 'type' => 'string', 644 'enum' => array( 'regular' ), 645 ), 646 'labelPosition' => array( 647 'type' => 'string', 648 'enum' => array( 'top', 'side', 'none' ), 649 ), 650 ), 651 ), 652 // PanelLayout. 653 array( 654 'type' => 'object', 655 'properties' => array( 656 'type' => array( 657 'type' => 'string', 658 'enum' => array( 'panel' ), 659 ), 660 'labelPosition' => array( 661 'type' => 'string', 662 'enum' => array( 'top', 'side', 'none' ), 663 ), 664 'openAs' => array( 665 'oneOf' => array( 666 array( 667 'type' => 'string', 668 'enum' => array( 'dropdown', 'modal' ), 669 ), 670 array( 671 'type' => 'object', 672 'properties' => array( 673 'type' => array( 674 'type' => 'string', 675 'enum' => array( 'dropdown', 'modal' ), 676 ), 677 'applyLabel' => array( 678 'type' => 'string', 679 ), 680 'cancelLabel' => array( 681 'type' => 'string', 682 ), 683 ), 684 ), 685 ), 686 ), 687 'summary' => array( 688 'oneOf' => array( 689 array( 'type' => 'string' ), 690 array( 691 'type' => 'array', 692 'items' => array( 693 'type' => 'string', 694 ), 695 ), 696 ), 697 ), 698 'editVisibility' => array( 699 'type' => 'string', 700 'enum' => array( 'always', 'on-hover' ), 701 ), 702 ), 703 ), 704 // CardLayout. 705 array( 706 'type' => 'object', 707 'properties' => array( 708 'type' => array( 709 'type' => 'string', 710 'enum' => array( 'card' ), 711 ), 712 'withHeader' => array( 713 'type' => 'boolean', 714 ), 715 'isOpened' => array( 716 'type' => 'boolean', 717 ), 718 'isCollapsible' => array( 719 'type' => 'boolean', 720 ), 721 'summary' => array( 722 'oneOf' => array( 723 array( 'type' => 'string' ), 724 array( 725 'type' => 'array', 726 'items' => array( 727 'oneOf' => array( 728 array( 'type' => 'string' ), 729 array( 730 'type' => 'object', 731 'properties' => array( 732 'id' => array( 733 'type' => 'string', 734 ), 735 'visibility' => array( 736 'type' => 'string', 737 'enum' => array( 'always', 'when-collapsed' ), 738 ), 739 ), 740 ), 741 ), 742 ), 743 ), 744 ), 745 ), 746 ), 747 ), 748 // RowLayout. 749 array( 750 'type' => 'object', 751 'properties' => array( 752 'type' => array( 753 'type' => 'string', 754 'enum' => array( 'row' ), 755 ), 756 'alignment' => array( 757 'type' => 'string', 758 'enum' => array( 'start', 'center', 'end' ), 759 ), 760 'styles' => array( 761 'type' => 'object', 762 'additionalProperties' => array( 763 'type' => 'object', 764 'properties' => array( 765 'flex' => array( 766 'type' => array( 'string', 'number' ), 767 ), 768 ), 769 ), 770 ), 771 ), 772 ), 773 // DetailsLayout. 774 array( 775 'type' => 'object', 776 'properties' => array( 777 'type' => array( 778 'type' => 'string', 779 'enum' => array( 'details' ), 780 ), 781 'summary' => array( 782 'type' => 'string', 783 ), 784 ), 785 ), 786 ), 787 ); 788 } 789 790 /** 791 * Returns the schema for a form field item (string or object). 792 * 793 * @since 7.1.0 794 * 795 * @return array Schema for a form field. 796 */ 797 protected function get_form_field_schema() { 798 return array( 799 'oneOf' => array( 800 array( 'type' => 'string' ), 801 array( 802 'type' => 'object', 803 'properties' => array( 804 'id' => array( 805 'type' => 'string', 806 ), 807 'label' => array( 808 'type' => 'string', 809 ), 810 'description' => array( 811 'type' => 'string', 812 ), 813 'layout' => $this->get_form_layout_schema(), 814 'children' => array( 815 'type' => 'array', 816 'items' => array( 817 'oneOf' => array( 818 array( 'type' => 'string' ), 819 // This object can have the shape of a form field itself, 820 // allowing for recursive nesting of form fields. 821 // There's no easy way to codify this recursion via the JSON Schema draft-04 822 // supported by the REST API. 823 array( 'type' => 'object' ), 824 ), 825 ), 826 ), 827 ), 828 ), 829 ), 830 ); 831 } 832 833 /** 834 * Returns the schema for the form configuration object. 835 * 836 * @since 7.1.0 837 * 838 * @return array Schema properties for the form configuration. 839 */ 840 protected function get_form_schema() { 841 return array( 842 'layout' => $this->get_form_layout_schema(), 843 'fields' => array( 844 'type' => 'array', 845 'items' => $this->get_form_field_schema(), 846 ), 847 ); 848 } 849 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Jul 15 08:20:16 2026 | Cross-referenced by PHPXref |