| [ 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 = $schema['oneOf'] ?? $schema['anyOf']; 197 if ( array() === $value ) { 198 foreach ( $branches as $branch ) { 199 if ( is_array( $branch ) && in_array( 'object', (array) ( $branch['type'] ?? array() ), true ) ) { 200 return (object) array(); 201 } 202 } 203 } 204 return $value; 205 } 206 207 $types = (array) ( $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 * Note that `search` and `page` are not part of the schema: they are managed 395 * via the URL, which is their only source of truth. 396 * 397 * @since 7.1.0 398 * 399 * @return array Schema properties for the base view configuration. 400 */ 401 protected function get_view_base_schema() { 402 return array( 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 'perPage' => array( 448 'type' => 'integer', 449 ), 450 'fields' => array( 451 'type' => 'array', 452 'items' => array( 453 'type' => 'string', 454 ), 455 ), 456 'titleField' => array( 457 'type' => 'string', 458 ), 459 'mediaField' => array( 460 'type' => 'string', 461 ), 462 'descriptionField' => array( 463 'type' => 'string', 464 ), 465 'showTitle' => array( 466 'type' => 'boolean', 467 ), 468 'showMedia' => array( 469 'type' => 'boolean', 470 ), 471 'showDescription' => array( 472 'type' => 'boolean', 473 ), 474 'showLevels' => array( 475 'type' => 'boolean', 476 ), 477 'groupBy' => array( 478 'type' => 'object', 479 'properties' => array( 480 'field' => array( 481 'type' => 'string', 482 ), 483 'direction' => array( 484 'type' => 'string', 485 'enum' => array( 'asc', 'desc' ), 486 ), 487 'showLabel' => array( 488 'type' => 'boolean', 489 'default' => true, 490 ), 491 ), 492 ), 493 'infiniteScrollEnabled' => array( 494 'type' => 'boolean', 495 ), 496 ); 497 } 498 499 /** 500 * Returns the schema for the ColumnStyle type. 501 * 502 * @since 7.1.0 503 * 504 * @return array Schema for a column style object. 505 */ 506 protected function get_column_style_schema() { 507 return array( 508 'type' => 'object', 509 'properties' => array( 510 'width' => array( 511 'type' => array( 'string', 'number' ), 512 ), 513 'maxWidth' => array( 514 'type' => array( 'string', 'number' ), 515 ), 516 'minWidth' => array( 517 'type' => array( 'string', 'number' ), 518 ), 519 'align' => array( 520 'type' => 'string', 521 'enum' => array( 'start', 'center', 'end' ), 522 ), 523 ), 524 ); 525 } 526 527 /** 528 * Returns the layout schema for table-type views (ViewTable, ViewPickerTable). 529 * 530 * @since 7.1.0 531 * 532 * @return array Schema for a table layout object. 533 */ 534 protected function get_table_layout_schema() { 535 return array( 536 'type' => 'object', 537 'properties' => array( 538 'styles' => array( 539 'type' => 'object', 540 'additionalProperties' => $this->get_column_style_schema(), 541 ), 542 'density' => array( 543 'type' => 'string', 544 'enum' => array( 'compact', 'balanced', 'comfortable' ), 545 ), 546 'enableMoving' => array( 547 'type' => 'boolean', 548 ), 549 ), 550 ); 551 } 552 553 /** 554 * Returns the layout schema for list-type views (ViewList, ViewActivity). 555 * 556 * @since 7.1.0 557 * 558 * @return array Schema for a list layout object. 559 */ 560 protected function get_list_layout_schema() { 561 return array( 562 'type' => 'object', 563 'properties' => array( 564 'density' => array( 565 'type' => 'string', 566 'enum' => array( 'compact', 'balanced', 'comfortable' ), 567 ), 568 ), 569 ); 570 } 571 572 /** 573 * Returns a combined layout schema that accepts properties from all view types. 574 * 575 * This is useful for contexts where the view type is not known ahead of time 576 * (e.g. the `view` override in a view list item), so all possible layout 577 * properties must be accepted. 578 * 579 * @since 7.1.0 580 * 581 * @return array Schema for a combined layout object. 582 */ 583 protected function get_combined_layout_schema() { 584 return array( 585 'type' => 'object', 586 'properties' => array_merge( 587 $this->get_table_layout_schema()['properties'], 588 $this->get_grid_layout_schema()['properties'], 589 $this->get_list_layout_schema()['properties'] 590 ), 591 ); 592 } 593 594 /** 595 * Returns the layout schema for grid-type views (ViewGrid, ViewPickerGrid). 596 * 597 * @since 7.1.0 598 * 599 * @return array Schema for a grid layout object. 600 */ 601 protected function get_grid_layout_schema() { 602 return array( 603 'type' => 'object', 604 'properties' => array( 605 'badgeFields' => array( 606 'type' => 'array', 607 'items' => array( 608 'type' => 'string', 609 ), 610 ), 611 'previewSize' => array( 612 'type' => 'number', 613 ), 614 'density' => array( 615 'type' => 'string', 616 'enum' => array( 'compact', 'balanced', 'comfortable' ), 617 ), 618 ), 619 ); 620 } 621 622 /** 623 * Returns the schema for a form layout object as a discriminated union. 624 * 625 * Each variant is discriminated by a single-value enum on its `type` property, 626 * matching the TypeScript Layout union in dataviews/src/types/dataform.ts. 627 * 628 * @since 7.1.0 629 * 630 * @return array Schema for a form layout object. 631 */ 632 protected function get_form_layout_schema() { 633 return array( 634 'oneOf' => array( 635 // RegularLayout. 636 array( 637 'type' => 'object', 638 'properties' => array( 639 'type' => array( 640 'type' => 'string', 641 'enum' => array( 'regular' ), 642 ), 643 'labelPosition' => array( 644 'type' => 'string', 645 'enum' => array( 'top', 'side', 'none' ), 646 ), 647 ), 648 ), 649 // PanelLayout. 650 array( 651 'type' => 'object', 652 'properties' => array( 653 'type' => array( 654 'type' => 'string', 655 'enum' => array( 'panel' ), 656 ), 657 'labelPosition' => array( 658 'type' => 'string', 659 'enum' => array( 'top', 'side', 'none' ), 660 ), 661 'openAs' => array( 662 'oneOf' => array( 663 array( 664 'type' => 'string', 665 'enum' => array( 'dropdown', 'modal' ), 666 ), 667 array( 668 'type' => 'object', 669 'properties' => array( 670 'type' => array( 671 'type' => 'string', 672 'enum' => array( 'dropdown', 'modal' ), 673 ), 674 'applyLabel' => array( 675 'type' => 'string', 676 ), 677 'cancelLabel' => array( 678 'type' => 'string', 679 ), 680 ), 681 ), 682 ), 683 ), 684 'summary' => array( 685 'oneOf' => array( 686 array( 'type' => 'string' ), 687 array( 688 'type' => 'array', 689 'items' => array( 690 'type' => 'string', 691 ), 692 ), 693 ), 694 ), 695 'editVisibility' => array( 696 'type' => 'string', 697 'enum' => array( 'always', 'on-hover' ), 698 ), 699 ), 700 ), 701 // CardLayout. 702 array( 703 'type' => 'object', 704 'properties' => array( 705 'type' => array( 706 'type' => 'string', 707 'enum' => array( 'card' ), 708 ), 709 'withHeader' => array( 710 'type' => 'boolean', 711 ), 712 'isOpened' => array( 713 'type' => 'boolean', 714 ), 715 'isCollapsible' => array( 716 'type' => 'boolean', 717 ), 718 'summary' => array( 719 'oneOf' => array( 720 array( 'type' => 'string' ), 721 array( 722 'type' => 'array', 723 'items' => array( 724 'oneOf' => array( 725 array( 'type' => 'string' ), 726 array( 727 'type' => 'object', 728 'properties' => array( 729 'id' => array( 730 'type' => 'string', 731 ), 732 'visibility' => array( 733 'type' => 'string', 734 'enum' => array( 'always', 'when-collapsed' ), 735 ), 736 ), 737 ), 738 ), 739 ), 740 ), 741 ), 742 ), 743 ), 744 ), 745 // RowLayout. 746 array( 747 'type' => 'object', 748 'properties' => array( 749 'type' => array( 750 'type' => 'string', 751 'enum' => array( 'row' ), 752 ), 753 'alignment' => array( 754 'type' => 'string', 755 'enum' => array( 'start', 'center', 'end' ), 756 ), 757 'styles' => array( 758 'type' => 'object', 759 'additionalProperties' => array( 760 'type' => 'object', 761 'properties' => array( 762 'flex' => array( 763 'type' => array( 'string', 'number' ), 764 ), 765 ), 766 ), 767 ), 768 ), 769 ), 770 // DetailsLayout. 771 array( 772 'type' => 'object', 773 'properties' => array( 774 'type' => array( 775 'type' => 'string', 776 'enum' => array( 'details' ), 777 ), 778 'summary' => array( 779 'type' => 'string', 780 ), 781 ), 782 ), 783 ), 784 ); 785 } 786 787 /** 788 * Returns the schema for a form field item (string or object). 789 * 790 * @since 7.1.0 791 * 792 * @return array Schema for a form field. 793 */ 794 protected function get_form_field_schema() { 795 return array( 796 'oneOf' => array( 797 array( 'type' => 'string' ), 798 array( 799 'type' => 'object', 800 'properties' => array( 801 'id' => array( 802 'type' => 'string', 803 ), 804 'label' => array( 805 'type' => 'string', 806 ), 807 'description' => array( 808 'type' => 'string', 809 ), 810 'layout' => $this->get_form_layout_schema(), 811 'children' => array( 812 'type' => 'array', 813 'items' => array( 814 'oneOf' => array( 815 array( 'type' => 'string' ), 816 // This object can have the shape of a form field itself, 817 // allowing for recursive nesting of form fields. 818 // There's no easy way to codify this recursion via the JSON Schema draft-04 819 // supported by the REST API. 820 array( 'type' => 'object' ), 821 ), 822 ), 823 ), 824 ), 825 ), 826 ), 827 ); 828 } 829 830 /** 831 * Returns the schema for the form configuration object. 832 * 833 * @since 7.1.0 834 * 835 * @return array Schema properties for the form configuration. 836 */ 837 protected function get_form_schema() { 838 return array( 839 'layout' => $this->get_form_layout_schema(), 840 'fields' => array( 841 'type' => 'array', 842 'items' => $this->get_form_field_schema(), 843 ), 844 ); 845 } 846 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Tue Aug 4 08:20:20 2026 | Cross-referenced by PHPXref |