| [ 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 'default_view' => $this->cast_empty_objects( $config['default_view'], $schema['properties']['default_view'] ), 158 'default_layouts' => $this->cast_empty_objects( $config['default_layouts'], $schema['properties']['default_layouts'] ), 159 'view_list' => $this->cast_empty_objects( $config['view_list'], $schema['properties']['view_list'] ), 160 'form' => $this->cast_empty_objects( $config['form'], $schema['properties']['form'] ), 161 ); 162 163 return rest_ensure_response( $response ); 164 } 165 166 /** 167 * Recursively casts empty arrays to objects where the schema types them as 168 * objects. 169 * 170 * PHP cannot distinguish an empty associative array from an empty list, so 171 * `json_encode()` always serializes `array()` as a JSON array (`[]`). The 172 * REST schema, however, types several values as objects, which must encode 173 * as `{}`. This walks the value against its schema and casts any empty, 174 * object-typed array to an object. Non-empty associative arrays already 175 * encode as objects, so they are left as arrays and only recursed into to 176 * fix any nested empty objects. 177 * 178 * Union schemas (`oneOf`/`anyOf`) are handled only for the empty-array case: 179 * an empty value is cast to an object when any branch allows an object. Such 180 * values are not recursed into, which is sufficient for the form schema 181 * where they never contain empty nested objects. 182 * 183 * @since 7.1.0 184 * 185 * @param mixed $value The value to normalize. 186 * @param array $schema The schema node describing the value. 187 * @return mixed The normalized value, with empty object-typed arrays cast to objects. 188 */ 189 protected function cast_empty_objects( $value, $schema ) { 190 if ( ! is_array( $value ) || ! is_array( $schema ) ) { 191 return $value; 192 } 193 194 if ( isset( $schema['oneOf'] ) || isset( $schema['anyOf'] ) ) { 195 $branches = isset( $schema['oneOf'] ) ? $schema['oneOf'] : $schema['anyOf']; 196 if ( array() === $value ) { 197 foreach ( $branches as $branch ) { 198 if ( is_array( $branch ) && in_array( 'object', (array) ( isset( $branch['type'] ) ? $branch['type'] : array() ), true ) ) { 199 return (object) array(); 200 } 201 } 202 } 203 return $value; 204 } 205 206 $types = (array) ( isset( $schema['type'] ) ? $schema['type'] : array() ); 207 208 if ( in_array( 'array', $types, true ) && isset( $schema['items'] ) ) { 209 foreach ( $value as $index => $item ) { 210 $value[ $index ] = $this->cast_empty_objects( $item, $schema['items'] ); 211 } 212 return $value; 213 } 214 215 if ( in_array( 'object', $types, true ) ) { 216 if ( isset( $schema['properties'] ) ) { 217 foreach ( $schema['properties'] as $property => $property_schema ) { 218 if ( array_key_exists( $property, $value ) ) { 219 $value[ $property ] = $this->cast_empty_objects( $value[ $property ], $property_schema ); 220 } 221 } 222 } 223 if ( isset( $schema['additionalProperties'] ) && is_array( $schema['additionalProperties'] ) ) { 224 foreach ( $value as $key => $item ) { 225 if ( isset( $schema['properties'][ $key ] ) ) { 226 continue; 227 } 228 $value[ $key ] = $this->cast_empty_objects( $item, $schema['additionalProperties'] ); 229 } 230 } 231 232 // Empty object-typed arrays must serialize as {} to match the schema. 233 if ( array() === $value ) { 234 return (object) array(); 235 } 236 } 237 238 return $value; 239 } 240 241 /** 242 * Retrieves the item's schema, conforming to JSON Schema. 243 * 244 * @since 7.1.0 245 * 246 * @return array Item schema data. 247 */ 248 public function get_item_schema() { 249 if ( $this->schema ) { 250 return $this->add_additional_fields_schema( $this->schema ); 251 } 252 253 $view_base_properties = $this->get_view_base_schema(); 254 255 $this->schema = array( 256 '$schema' => 'http://json-schema.org/draft-04/schema#', 257 'title' => 'view-config', 258 'type' => 'object', 259 'properties' => array( 260 'kind' => array( 261 'description' => __( 'Entity kind.' ), 262 'type' => 'string', 263 'readonly' => true, 264 ), 265 'name' => array( 266 'description' => __( 'Entity name.' ), 267 'type' => 'string', 268 'readonly' => true, 269 ), 270 'default_view' => array( 271 'description' => __( 'Default view configuration.' ), 272 'type' => 'object', 273 'readonly' => true, 274 'properties' => array_merge( 275 array( 276 'type' => array( 277 'type' => 'string', 278 ), 279 'layout' => $this->get_combined_layout_schema(), 280 ), 281 $view_base_properties 282 ), 283 ), 284 'default_layouts' => array( 285 'description' => __( 'Default layout configurations.' ), 286 'type' => 'object', 287 'readonly' => true, 288 'properties' => array( 289 'table' => array( 290 'type' => 'object', 291 'properties' => array_merge( 292 $view_base_properties, 293 array( 294 'layout' => $this->get_table_layout_schema(), 295 ) 296 ), 297 ), 298 'list' => array( 299 'type' => 'object', 300 'properties' => array_merge( 301 $view_base_properties, 302 array( 303 'layout' => $this->get_list_layout_schema(), 304 ) 305 ), 306 ), 307 'grid' => array( 308 'type' => 'object', 309 'properties' => array_merge( 310 $view_base_properties, 311 array( 312 'layout' => $this->get_grid_layout_schema(), 313 ) 314 ), 315 ), 316 'activity' => array( 317 'type' => 'object', 318 'properties' => array_merge( 319 $view_base_properties, 320 array( 321 'layout' => $this->get_list_layout_schema(), 322 ) 323 ), 324 ), 325 'pickerGrid' => array( 326 'type' => 'object', 327 'properties' => array_merge( 328 $view_base_properties, 329 array( 330 'layout' => $this->get_grid_layout_schema(), 331 ) 332 ), 333 ), 334 'pickerTable' => array( 335 'type' => 'object', 336 'properties' => array_merge( 337 $view_base_properties, 338 array( 339 'layout' => $this->get_table_layout_schema(), 340 ) 341 ), 342 ), 343 ), 344 ), 345 'view_list' => array( 346 'description' => __( 'List of default views.' ), 347 'type' => 'array', 348 'readonly' => true, 349 'items' => array( 350 'type' => 'object', 351 'properties' => array( 352 'title' => array( 353 'type' => 'string', 354 ), 355 'slug' => array( 356 'type' => 'string', 357 ), 358 'view' => array( 359 'type' => 'object', 360 'properties' => array_merge( 361 array( 362 'type' => array( 363 'type' => 'string', 364 ), 365 'layout' => $this->get_combined_layout_schema(), 366 ), 367 $view_base_properties 368 ), 369 ), 370 ), 371 ), 372 ), 373 'form' => array( 374 'description' => __( 'Default form configuration.' ), 375 'type' => 'object', 376 'readonly' => true, 377 'properties' => $this->get_form_schema(), 378 ), 379 ), 380 ); 381 382 return $this->add_additional_fields_schema( $this->schema ); 383 } 384 385 /** 386 * Returns the schema properties shared by all view types (ViewBase), excluding 'type'. 387 * 388 * @since 7.1.0 389 * 390 * @return array Schema properties for the base view configuration. 391 */ 392 protected function get_view_base_schema() { 393 return array( 394 'search' => array( 395 'type' => 'string', 396 ), 397 'filters' => array( 398 'type' => 'array', 399 'items' => array( 400 'type' => 'object', 401 'properties' => array( 402 'field' => array( 403 'type' => 'string', 404 ), 405 'operator' => array( 406 'type' => 'string', 407 'enum' => array( 408 'is', 409 'isNot', 410 'isAny', 411 'isNone', 412 'isAll', 413 'isNotAll', 414 'lessThan', 415 'greaterThan', 416 'lessThanOrEqual', 417 'greaterThanOrEqual', 418 'before', 419 'after', 420 ), 421 ), 422 'value' => array(), 423 'isLocked' => array( 424 'type' => 'boolean', 425 ), 426 ), 427 ), 428 ), 429 'sort' => array( 430 'type' => 'object', 431 'properties' => array( 432 'field' => array( 433 'type' => 'string', 434 ), 435 'direction' => array( 436 'type' => 'string', 437 'enum' => array( 'asc', 'desc' ), 438 ), 439 ), 440 ), 441 'page' => array( 442 'type' => 'integer', 443 ), 444 'perPage' => array( 445 'type' => 'integer', 446 ), 447 'fields' => array( 448 'type' => 'array', 449 'items' => array( 450 'type' => 'string', 451 ), 452 ), 453 'titleField' => array( 454 'type' => 'string', 455 ), 456 'mediaField' => array( 457 'type' => 'string', 458 ), 459 'descriptionField' => array( 460 'type' => 'string', 461 ), 462 'showTitle' => array( 463 'type' => 'boolean', 464 ), 465 'showMedia' => array( 466 'type' => 'boolean', 467 ), 468 'showDescription' => array( 469 'type' => 'boolean', 470 ), 471 'showLevels' => array( 472 'type' => 'boolean', 473 ), 474 'groupBy' => array( 475 'type' => 'object', 476 'properties' => array( 477 'field' => array( 478 'type' => 'string', 479 ), 480 'direction' => array( 481 'type' => 'string', 482 'enum' => array( 'asc', 'desc' ), 483 ), 484 'showLabel' => array( 485 'type' => 'boolean', 486 'default' => true, 487 ), 488 ), 489 ), 490 'infiniteScrollEnabled' => array( 491 'type' => 'boolean', 492 ), 493 ); 494 } 495 496 /** 497 * Returns the schema for the ColumnStyle type. 498 * 499 * @since 7.1.0 500 * 501 * @return array Schema for a column style object. 502 */ 503 protected function get_column_style_schema() { 504 return array( 505 'type' => 'object', 506 'properties' => array( 507 'width' => array( 508 'type' => array( 'string', 'number' ), 509 ), 510 'maxWidth' => array( 511 'type' => array( 'string', 'number' ), 512 ), 513 'minWidth' => array( 514 'type' => array( 'string', 'number' ), 515 ), 516 'align' => array( 517 'type' => 'string', 518 'enum' => array( 'start', 'center', 'end' ), 519 ), 520 ), 521 ); 522 } 523 524 /** 525 * Returns the layout schema for table-type views (ViewTable, ViewPickerTable). 526 * 527 * @since 7.1.0 528 * 529 * @return array Schema for a table layout object. 530 */ 531 protected function get_table_layout_schema() { 532 return array( 533 'type' => 'object', 534 'properties' => array( 535 'styles' => array( 536 'type' => 'object', 537 'additionalProperties' => $this->get_column_style_schema(), 538 ), 539 'density' => array( 540 'type' => 'string', 541 'enum' => array( 'compact', 'balanced', 'comfortable' ), 542 ), 543 'enableMoving' => array( 544 'type' => 'boolean', 545 ), 546 ), 547 ); 548 } 549 550 /** 551 * Returns the layout schema for list-type views (ViewList, ViewActivity). 552 * 553 * @since 7.1.0 554 * 555 * @return array Schema for a list layout object. 556 */ 557 protected function get_list_layout_schema() { 558 return array( 559 'type' => 'object', 560 'properties' => array( 561 'density' => array( 562 'type' => 'string', 563 'enum' => array( 'compact', 'balanced', 'comfortable' ), 564 ), 565 ), 566 ); 567 } 568 569 /** 570 * Returns a combined layout schema that accepts properties from all view types. 571 * 572 * This is useful for contexts where the view type is not known ahead of time 573 * (e.g. the `view` override in a view list item), so all possible layout 574 * properties must be accepted. 575 * 576 * @since 7.1.0 577 * 578 * @return array Schema for a combined layout object. 579 */ 580 protected function get_combined_layout_schema() { 581 return array( 582 'type' => 'object', 583 'properties' => array_merge( 584 $this->get_table_layout_schema()['properties'], 585 $this->get_grid_layout_schema()['properties'], 586 $this->get_list_layout_schema()['properties'] 587 ), 588 ); 589 } 590 591 /** 592 * Returns the layout schema for grid-type views (ViewGrid, ViewPickerGrid). 593 * 594 * @since 7.1.0 595 * 596 * @return array Schema for a grid layout object. 597 */ 598 protected function get_grid_layout_schema() { 599 return array( 600 'type' => 'object', 601 'properties' => array( 602 'badgeFields' => array( 603 'type' => 'array', 604 'items' => array( 605 'type' => 'string', 606 ), 607 ), 608 'previewSize' => array( 609 'type' => 'number', 610 ), 611 'density' => array( 612 'type' => 'string', 613 'enum' => array( 'compact', 'balanced', 'comfortable' ), 614 ), 615 ), 616 ); 617 } 618 619 /** 620 * Returns the schema for a form layout object as a discriminated union. 621 * 622 * Each variant is discriminated by a single-value enum on its `type` property, 623 * matching the TypeScript Layout union in dataviews/src/types/dataform.ts. 624 * 625 * @since 7.1.0 626 * 627 * @return array Schema for a form layout object. 628 */ 629 protected function get_form_layout_schema() { 630 return array( 631 'oneOf' => array( 632 // RegularLayout. 633 array( 634 'type' => 'object', 635 'properties' => array( 636 'type' => array( 637 'type' => 'string', 638 'enum' => array( 'regular' ), 639 ), 640 'labelPosition' => array( 641 'type' => 'string', 642 'enum' => array( 'top', 'side', 'none' ), 643 ), 644 ), 645 ), 646 // PanelLayout. 647 array( 648 'type' => 'object', 649 'properties' => array( 650 'type' => array( 651 'type' => 'string', 652 'enum' => array( 'panel' ), 653 ), 654 'labelPosition' => array( 655 'type' => 'string', 656 'enum' => array( 'top', 'side', 'none' ), 657 ), 658 'openAs' => array( 659 'oneOf' => array( 660 array( 661 'type' => 'string', 662 'enum' => array( 'dropdown', 'modal' ), 663 ), 664 array( 665 'type' => 'object', 666 'properties' => array( 667 'type' => array( 668 'type' => 'string', 669 'enum' => array( 'dropdown', 'modal' ), 670 ), 671 'applyLabel' => array( 672 'type' => 'string', 673 ), 674 'cancelLabel' => array( 675 'type' => 'string', 676 ), 677 ), 678 ), 679 ), 680 ), 681 'summary' => array( 682 'oneOf' => array( 683 array( 'type' => 'string' ), 684 array( 685 'type' => 'array', 686 'items' => array( 687 'type' => 'string', 688 ), 689 ), 690 ), 691 ), 692 'editVisibility' => array( 693 'type' => 'string', 694 'enum' => array( 'always', 'on-hover' ), 695 ), 696 ), 697 ), 698 // CardLayout. 699 array( 700 'type' => 'object', 701 'properties' => array( 702 'type' => array( 703 'type' => 'string', 704 'enum' => array( 'card' ), 705 ), 706 'withHeader' => array( 707 'type' => 'boolean', 708 ), 709 'isOpened' => array( 710 'type' => 'boolean', 711 ), 712 'isCollapsible' => array( 713 'type' => 'boolean', 714 ), 715 'summary' => array( 716 'oneOf' => array( 717 array( 'type' => 'string' ), 718 array( 719 'type' => 'array', 720 'items' => array( 721 'oneOf' => array( 722 array( 'type' => 'string' ), 723 array( 724 'type' => 'object', 725 'properties' => array( 726 'id' => array( 727 'type' => 'string', 728 ), 729 'visibility' => array( 730 'type' => 'string', 731 'enum' => array( 'always', 'when-collapsed' ), 732 ), 733 ), 734 ), 735 ), 736 ), 737 ), 738 ), 739 ), 740 ), 741 ), 742 // RowLayout. 743 array( 744 'type' => 'object', 745 'properties' => array( 746 'type' => array( 747 'type' => 'string', 748 'enum' => array( 'row' ), 749 ), 750 'alignment' => array( 751 'type' => 'string', 752 'enum' => array( 'start', 'center', 'end' ), 753 ), 754 'styles' => array( 755 'type' => 'object', 756 'additionalProperties' => array( 757 'type' => 'object', 758 'properties' => array( 759 'flex' => array( 760 'type' => array( 'string', 'number' ), 761 ), 762 ), 763 ), 764 ), 765 ), 766 ), 767 // DetailsLayout. 768 array( 769 'type' => 'object', 770 'properties' => array( 771 'type' => array( 772 'type' => 'string', 773 'enum' => array( 'details' ), 774 ), 775 'summary' => array( 776 'type' => 'string', 777 ), 778 ), 779 ), 780 ), 781 ); 782 } 783 784 /** 785 * Returns the schema for a form field item (string or object). 786 * 787 * @since 7.1.0 788 * 789 * @return array Schema for a form field. 790 */ 791 protected function get_form_field_schema() { 792 return array( 793 'oneOf' => array( 794 array( 'type' => 'string' ), 795 array( 796 'type' => 'object', 797 'properties' => array( 798 'id' => array( 799 'type' => 'string', 800 ), 801 'label' => array( 802 'type' => 'string', 803 ), 804 'description' => array( 805 'type' => 'string', 806 ), 807 'layout' => $this->get_form_layout_schema(), 808 'children' => array( 809 'type' => 'array', 810 'items' => array( 811 'oneOf' => array( 812 array( 'type' => 'string' ), 813 // This object can have the shape of a form field itself, 814 // allowing for recursive nesting of form fields. 815 // There's no easy way to codify this recursion via the JSON Schema draft-04 816 // supported by the REST API. 817 array( 'type' => 'object' ), 818 ), 819 ), 820 ), 821 ), 822 ), 823 ), 824 ); 825 } 826 827 /** 828 * Returns the schema for the form configuration object. 829 * 830 * @since 7.1.0 831 * 832 * @return array Schema properties for the form configuration. 833 */ 834 protected function get_form_schema() { 835 return array( 836 'layout' => $this->get_form_layout_schema(), 837 'fields' => array( 838 'type' => 'array', 839 'items' => $this->get_form_field_schema(), 840 ), 841 ); 842 } 843 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Wed Jun 24 08:20:11 2026 | Cross-referenced by PHPXref |