| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Entity view configuration API. 4 * 5 * Builds the default view configuration for an entity and exposes it through 6 * the dynamic `get_entity_view_config_{$kind}_{$name}` filter so core and third 7 * parties can provide the configuration for a specific entity. The dynamic 8 * portions of the hook name are lowercased, e.g. 9 * `get_entity_view_config_posttype_page` for the `page` post type. 10 * 11 * @package WordPress 12 * @since 7.1.0 13 */ 14 15 /** 16 * Builds the name of the dynamic filter that provides the view configuration 17 * for an entity. 18 * 19 * The entity kind and name are embedded in the hook name lowercased, so the 20 * hook follows the WordPress convention of lowercase hook names regardless of 21 * how the entity identifiers are spelled: the `postType`/`page` entity maps to 22 * the `get_entity_view_config_posttype_page` hook. 23 * 24 * @since 7.1.0 25 * 26 * @param string $kind The entity kind (e.g. `postType`). 27 * @param string $name The entity name (e.g. `page`). 28 * @return string The filter name. 29 */ 30 function wp_get_entity_view_config_hook_name( $kind, $name ) { 31 return strtolower( "get_entity_view_config_{$kind}_{$name}" ); 32 } 33 34 /** 35 * Builds the default `form` configuration for post types that don't provide their own. 36 * 37 * It is a sensible default for `post`, `page`, and custom post types alike rather 38 * than being tailored per type. Post types that need a different shape can replace 39 * it entirely with a dedicated `form` through their own filter callback. 40 * 41 * It is intentionally NOT gated by `supports`. The registered fields are the 42 * single source of truth for what applies: each field is registered for a post 43 * type based on its `supports` (and related flags such as `theme_supports`), and 44 * the editor drops any form field whose definition is absent or whose `isVisible` 45 * returns `false`. 46 * 47 * @since 7.1.0 48 * @since 7.2.0 Shows a placeholder for an empty excerpt and a missing author, 49 * and summarizes the `date` panel with the `publish_date` field. 50 * 51 * @return array The default form configuration. 52 */ 53 function _wp_get_default_posttype_form() { 54 return array( 55 'layout' => array( 'type' => 'panel' ), 56 'fields' => array( 57 array( 58 'id' => 'featured_media', 59 'layout' => array( 60 'type' => 'regular', 61 'labelPosition' => 'none', 62 ), 63 ), 64 array( 65 'id' => 'post-content-info', 66 'layout' => array( 67 'type' => 'regular', 68 'labelPosition' => 'none', 69 ), 70 ), 71 array( 72 'id' => 'excerpt', 73 'layout' => array( 74 'type' => 'panel', 75 'labelPosition' => 'top', 76 'showPlaceholderIfEmpty' => true, 77 ), 78 ), 79 array( 80 'id' => 'status', 81 'label' => __( 'Status' ), 82 'layout' => array( 83 'type' => 'panel', 84 'summary' => 'status', 85 ), 86 'children' => array( 87 array( 88 'id' => 'status', 89 'layout' => array( 90 'type' => 'regular', 91 'labelPosition' => 'none', 92 ), 93 ), 94 'scheduled_date', 95 'password', 96 'sticky', 97 ), 98 ), 99 array( 100 'id' => 'date', 101 'layout' => array( 102 'type' => 'panel', 103 'summary' => 'publish_date', 104 ), 105 ), 106 'slug', 107 array( 108 'id' => 'author', 109 'layout' => array( 110 'type' => 'panel', 111 'showPlaceholderIfEmpty' => true, 112 ), 113 ), 114 'template', 115 array( 116 'id' => 'discussion', 117 'label' => __( 'Discussion' ), 118 'layout' => array( 119 'type' => 'panel', 120 'summary' => 'discussion', 121 ), 122 'children' => array( 123 array( 124 'id' => 'comment_status', 125 'layout' => array( 126 'type' => 'regular', 127 'labelPosition' => 'none', 128 ), 129 ), 130 'ping_status', 131 ), 132 ), 133 'parent', 134 'format', 135 'revisions', 136 ), 137 ); 138 } 139 140 /** 141 * Returns the view configuration for the given entity. 142 * 143 * Builds the default configuration shared by all entities and then exposes it 144 * through the dynamic `get_entity_view_config_{$kind}_{$name}` filter — with the 145 * dynamic portions lowercased, see wp_get_entity_view_config_hook_name() 146 * — so that core and third parties can provide the configuration for a 147 * specific entity. 148 * 149 * @since 7.1.0 150 * 151 * @param string $kind The entity kind (e.g. `postType`). 152 * @param string $name The entity name (e.g. `page`). 153 * @return array { 154 * The view configuration for the entity. 155 * 156 * @type array $default_view Default view configuration. 157 * @type array $default_layouts Default layouts configuration. 158 * @type array $view_list List of available views. 159 * @type array $form Form configuration. 160 * } 161 */ 162 function wp_get_entity_view_config( $kind, $name ) { 163 $default_view = array( 164 'type' => 'table', 165 'filters' => array(), 166 'sort' => array( 167 'field' => 'title', 168 'direction' => 'asc', 169 ), 170 'perPage' => 20, 171 'fields' => array( 'author', 'status' ), 172 'titleField' => 'title', 173 ); 174 $default_layouts = array( 175 'table' => array(), 176 'grid' => array(), 177 'list' => array(), 178 ); 179 $all_items_title = __( 'All items' ); 180 if ( 'postType' === $kind ) { 181 $post_type_object = get_post_type_object( $name ); 182 if ( $post_type_object && ! empty( $post_type_object->labels->all_items ) ) { 183 $all_items_title = $post_type_object->labels->all_items; 184 } 185 } 186 $view_list = array( 187 array( 188 'title' => $all_items_title, 189 'slug' => 'all', 190 ), 191 ); 192 193 $config = array( 194 'default_view' => $default_view, 195 'default_layouts' => $default_layouts, 196 'view_list' => $view_list, 197 'form' => 'postType' === $kind ? _wp_get_default_posttype_form() : array(), 198 ); 199 200 $data = new WP_View_Config_Data( $config ); 201 202 return $data->apply_filters( $kind, $name ); 203 } 204 205 /** 206 * Provides the view configuration for the `page` post type. 207 * 208 * @since 7.1.0 209 * 210 * @param WP_View_Config_Data $data The view configuration container for the entity. 211 * @return WP_View_Config_Data The updated view configuration container. 212 */ 213 function _wp_get_entity_view_config_posttype_page( $data ) { 214 $default_layouts = array( 215 'table' => array( 216 'layout' => array( 217 'styles' => array( 218 'author' => array( 219 'align' => 'start', 220 ), 221 ), 222 ), 223 ), 224 'grid' => array(), 225 'list' => array(), 226 ); 227 228 $default_view = array( 229 'type' => 'list', 230 'filters' => array(), 231 'perPage' => 20, 232 'sort' => array( 233 'field' => 'title', 234 'direction' => 'asc', 235 ), 236 'showLevels' => true, 237 'titleField' => 'title', 238 'mediaField' => 'featured_media', 239 'fields' => array( 'author', 'status' ), 240 ); 241 242 $view_list = array( 243 array( 244 'title' => __( 'Published' ), 245 'slug' => 'published', 246 'view' => array( 247 'filters' => array( 248 array( 249 'field' => 'status', 250 'operator' => 'isAny', 251 'value' => 'publish', 252 'isLocked' => true, 253 ), 254 ), 255 ), 256 ), 257 array( 258 'title' => __( 'Scheduled' ), 259 'slug' => 'future', 260 'view' => array( 261 'filters' => array( 262 array( 263 'field' => 'status', 264 'operator' => 'isAny', 265 'value' => 'future', 266 'isLocked' => true, 267 ), 268 ), 269 ), 270 ), 271 array( 272 'title' => __( 'Drafts' ), 273 'slug' => 'drafts', 274 'view' => array( 275 'filters' => array( 276 array( 277 'field' => 'status', 278 'operator' => 'isAny', 279 'value' => 'draft', 280 'isLocked' => true, 281 ), 282 ), 283 ), 284 ), 285 array( 286 'title' => __( 'Pending' ), 287 'slug' => 'pending', 288 'view' => array( 289 'filters' => array( 290 array( 291 'field' => 'status', 292 'operator' => 'isAny', 293 'value' => 'pending', 294 'isLocked' => true, 295 ), 296 ), 297 ), 298 ), 299 array( 300 'title' => __( 'Private' ), 301 'slug' => 'private', 302 'view' => array( 303 'filters' => array( 304 array( 305 'field' => 'status', 306 'operator' => 'isAny', 307 'value' => 'private', 308 'isLocked' => true, 309 ), 310 ), 311 ), 312 ), 313 array( 314 'title' => __( 'Trash' ), 315 'slug' => 'trash', 316 'view' => array( 317 'type' => 'table', 318 'layout' => $default_layouts['table']['layout'], 319 'filters' => array( 320 array( 321 'field' => 'status', 322 'operator' => 'isAny', 323 'value' => 'trash', 324 'isLocked' => true, 325 ), 326 ), 327 ), 328 ), 329 ); 330 331 $data->set( 332 array( 333 'default_view' => $default_view, 334 'default_layouts' => $default_layouts, 335 ), 336 1 337 ); 338 // Append the status views, thereby preserving the base "all items" view, 339 // so its post-type-specific title is kept. 340 $data->merge( array( 'view_list' => $view_list ), 1 ); 341 342 return $data; 343 } 344 345 /** 346 * Provides the view configuration for the `wp_block` post type. 347 * 348 * @since 7.1.0 349 * @since 7.2.0 Shows a placeholder for an empty excerpt. 350 * 351 * @param WP_View_Config_Data $data The view configuration container for the entity. 352 * @return WP_View_Config_Data The updated view configuration container. 353 */ 354 function _wp_get_entity_view_config_posttype_wp_block( $data ) { 355 $default_layouts = array( 356 'table' => array( 357 'layout' => array( 358 'styles' => array( 359 'author' => array( 360 'width' => '1%', 361 'align' => 'start', 362 ), 363 ), 364 ), 365 ), 366 'grid' => array( 367 'layout' => array( 368 'badgeFields' => array( 'sync-status' ), 369 ), 370 ), 371 ); 372 373 $default_view = array( 374 'type' => 'grid', 375 'perPage' => 20, 376 'titleField' => 'title', 377 'mediaField' => 'preview', 378 'fields' => array( 'sync-status' ), 379 'filters' => array(), 380 'layout' => $default_layouts['grid']['layout'], 381 ); 382 383 $view_list = array( 384 array( 385 'title' => __( 'All patterns' ), 386 'slug' => 'all-patterns', 387 ), 388 array( 389 'title' => __( 'Site patterns' ), 390 'slug' => 'my-patterns', 391 ), 392 ); 393 394 // Gather categories from the block pattern categories registry. 395 $registry = WP_Block_Pattern_Categories_Registry::get_instance(); 396 $categories = array(); 397 398 foreach ( $registry->get_all_registered() as $category ) { 399 $categories[ $category['name'] ] = $category['label']; 400 } 401 402 // Ensure "Uncategorized" is always included for patterns 403 // that have no category assigned. 404 $categories['uncategorized'] ??= __( 'Uncategorized' ); 405 406 // Also gather user-created pattern categories (wp_pattern_category taxonomy). 407 $user_terms = get_terms( 408 array( 409 'taxonomy' => 'wp_pattern_category', 410 'hide_empty' => false, 411 ) 412 ); 413 414 if ( ! is_wp_error( $user_terms ) ) { 415 foreach ( $user_terms as $term ) { 416 $categories[ $term->slug ] = $term->name; 417 } 418 } 419 420 // Sort categories alphabetically by label. 421 asort( $categories, SORT_NATURAL | SORT_FLAG_CASE ); 422 423 foreach ( $categories as $category_name => $label ) { 424 $view_list[] = array( 425 'title' => $label, 426 'slug' => $category_name, 427 ); 428 } 429 430 $form = array( 431 'layout' => array( 'type' => 'panel' ), 432 'fields' => array( 433 array( 434 'id' => 'excerpt', 435 'layout' => array( 436 'type' => 'panel', 437 'labelPosition' => 'top', 438 'showPlaceholderIfEmpty' => true, 439 ), 440 ), 441 array( 442 'id' => 'post-content-info', 443 'layout' => array( 444 'type' => 'regular', 445 'labelPosition' => 'none', 446 ), 447 ), 448 'sync-status', 449 'revisions', 450 ), 451 ); 452 453 $data->set( 454 array( 455 'default_view' => $default_view, 456 'default_layouts' => $default_layouts, 457 'view_list' => $view_list, 458 'form' => $form, 459 ), 460 1 461 ); 462 463 return $data; 464 } 465 466 /** 467 * Provides the view configuration for the `wp_template_part` post type. 468 * 469 * @since 7.1.0 470 * 471 * @param WP_View_Config_Data $data The view configuration container for the entity. 472 * @return WP_View_Config_Data The updated view configuration container. 473 */ 474 function _wp_get_entity_view_config_posttype_wp_template_part( $data ) { 475 $default_layouts = array( 476 'table' => array( 477 'layout' => array( 478 'styles' => array( 479 'author' => array( 480 'width' => '1%', 481 ), 482 ), 483 ), 484 ), 485 'grid' => array( 486 'layout' => array(), 487 ), 488 ); 489 490 $default_view = array( 491 'type' => 'grid', 492 'perPage' => 20, 493 'titleField' => 'title', 494 'mediaField' => 'preview', 495 'fields' => array( 'author' ), 496 'filters' => array(), 497 'layout' => $default_layouts['grid']['layout'], 498 ); 499 500 $view_list = array( 501 array( 502 'title' => __( 'All template parts' ), 503 'slug' => 'all-parts', 504 ), 505 ); 506 507 $areas = get_allowed_block_template_part_areas(); 508 509 // Ensure default areas appear in a consistent order. 510 $preferred_order = array( 'header', 'footer', 'sidebar', 'navigation-overlay', 'uncategorized' ); 511 $ordered_areas = array(); 512 $remaining_areas = array(); 513 foreach ( $areas as $area ) { 514 $position = array_search( $area['area'], $preferred_order, true ); 515 if ( false !== $position ) { 516 $ordered_areas[ $position ] = $area; 517 } else { 518 $remaining_areas[] = $area; 519 } 520 } 521 ksort( $ordered_areas ); 522 $areas = array_merge( array_values( $ordered_areas ), $remaining_areas ); 523 524 foreach ( $areas as $area ) { 525 $view_list[] = array( 526 'title' => $area['label'], 527 'slug' => $area['area'], 528 'view' => array( 529 'filters' => array( 530 array( 531 'field' => 'area', 532 'operator' => 'is', 533 'value' => $area['area'], 534 'isLocked' => true, 535 ), 536 ), 537 ), 538 ); 539 } 540 541 $form = array( 542 'layout' => array( 'type' => 'panel' ), 543 'fields' => array( 544 array( 545 'id' => 'last_edited_date', 546 'layout' => array( 547 'type' => 'panel', 548 'labelPosition' => 'none', 549 ), 550 ), 551 'revisions', 552 ), 553 ); 554 555 $data->set( 556 array( 557 'default_view' => $default_view, 558 'default_layouts' => $default_layouts, 559 'view_list' => $view_list, 560 'form' => $form, 561 ), 562 1 563 ); 564 565 return $data; 566 } 567 568 /** 569 * Provides the view configuration for the `wp_template` post type. 570 * 571 * @since 7.1.0 572 * @since 7.2.0 Shows a placeholder for an empty description. 573 * 574 * @param WP_View_Config_Data $data The view configuration container for the entity. 575 * @return WP_View_Config_Data The updated view configuration container. 576 */ 577 function _wp_get_entity_view_config_posttype_wp_template( $data ) { 578 $default_view = array( 579 'type' => 'grid', 580 'perPage' => 20, 581 'sort' => array( 582 'field' => 'title', 583 'direction' => 'asc', 584 ), 585 'titleField' => 'title', 586 'descriptionField' => 'description', 587 'mediaField' => 'preview', 588 'fields' => array( 'author' ), 589 'filters' => array(), 590 'showMedia' => true, 591 ); 592 593 $default_layouts = array( 594 'table' => array( 'showMedia' => false ), 595 'grid' => array( 'showMedia' => true ), 596 'list' => array( 'showMedia' => false ), 597 ); 598 599 $view_list = array( 600 array( 601 'title' => __( 'All templates' ), 602 'slug' => 'all', 603 ), 604 ); 605 606 $templates = get_block_templates( array(), 'wp_template' ); 607 608 // Collect unique authors, tracking whether they come from a registered 609 // source (theme, plugin, site) so we can sort those before user ones. 610 $seen_authors = array(); 611 $registered_authors = array(); 612 $user_authors = array(); 613 foreach ( $templates as $template ) { 614 /* 615 * Determine the original source of the template ('theme', 'plugin', 616 * 'site', or 'user'). 617 */ 618 $original_source = 'user'; 619 if ( 'wp_template' === $template->type || 'wp_template_part' === $template->type ) { 620 if ( $template->has_theme_file && 621 ( 'theme' === $template->origin || ( 622 empty( $template->origin ) && in_array( 623 $template->source, 624 array( 625 'theme', 626 'custom', 627 ), 628 true 629 ) ) 630 ) 631 ) { 632 /* 633 * Added by theme. 634 * Template originally provided by a theme, but customized by a user. 635 * Templates originally didn't have the 'origin' field so identify 636 * older customized templates by checking for no origin and a 'theme' 637 * or 'custom' source. 638 */ 639 $original_source = 'theme'; 640 } elseif ( 'plugin' === $template->origin ) { 641 // Added by plugin. 642 $original_source = 'plugin'; 643 } elseif ( empty( $template->has_theme_file ) && 'custom' === $template->source && empty( $template->author ) ) { 644 /* 645 * Added by site. 646 * Template was created from scratch, but has no author. Author support 647 * was only added to templates in WordPress 5.9. Fallback to showing the 648 * site logo and title. 649 */ 650 $original_source = 'site'; 651 } 652 } 653 654 // Determine a human readable text for the author of the template. 655 $author_text = ''; 656 switch ( $original_source ) { 657 case 'theme': 658 $theme_name = wp_get_theme( $template->theme )->get( 'Name' ); 659 $author_text = empty( $theme_name ) ? $template->theme : $theme_name; 660 break; 661 case 'plugin': 662 if ( ! function_exists( 'get_plugins' ) ) { 663 require_once ABSPATH . 'wp-admin/includes/plugin.php'; 664 } 665 $plugin_name = ''; 666 if ( isset( $template->plugin ) ) { 667 $plugins = wp_get_active_and_valid_plugins(); 668 669 foreach ( $plugins as $plugin_file ) { 670 $plugin_basename = plugin_basename( $plugin_file ); 671 list( $plugin_slug, ) = explode( '/', $plugin_basename ); 672 673 if ( $plugin_slug === $template->plugin ) { 674 $plugin_data = get_plugin_data( $plugin_file ); 675 676 if ( ! empty( $plugin_data['Name'] ) ) { 677 $plugin_name = $plugin_data['Name']; 678 } 679 680 break; 681 } 682 } 683 } 684 685 /* 686 * Fall back to the theme name if the plugin is not defined. That's needed to keep backwards 687 * compatibility with templates that were registered before the plugin attribute was added. 688 */ 689 if ( '' === $plugin_name ) { 690 $plugins = get_plugins(); 691 $plugin_basename = plugin_basename( sanitize_text_field( $template->theme . '.php' ) ); 692 if ( isset( $plugins[ $plugin_basename ] ) && isset( $plugins[ $plugin_basename ]['Name'] ) ) { 693 $plugin_name = $plugins[ $plugin_basename ]['Name']; 694 } else { 695 $plugin_name = $template->plugin ?? $template->theme; 696 } 697 } 698 $author_text = $plugin_name; 699 break; 700 case 'site': 701 $author_text = get_bloginfo( 'name' ); 702 break; 703 case 'user': 704 $author = get_user_by( 'id', $template->author ); 705 if ( ! $author ) { 706 $author_text = __( 'Unknown author' ); 707 } else { 708 $author_text = $author->get( 'display_name' ); 709 } 710 break; 711 } 712 713 if ( ! empty( $author_text ) && ! isset( $seen_authors[ $author_text ] ) ) { 714 $seen_authors[ $author_text ] = true; 715 $entry = array( 716 'title' => $author_text, 717 'slug' => $author_text, 718 'view' => array( 719 'filters' => array( 720 array( 721 'field' => 'author', 722 'operator' => 'is', 723 'value' => $author_text, 724 'isLocked' => true, 725 ), 726 ), 727 ), 728 ); 729 if ( 'user' === $original_source ) { 730 $user_authors[] = $entry; 731 } else { 732 $registered_authors[] = $entry; 733 } 734 } 735 } 736 737 $form = array( 738 'layout' => array( 'type' => 'panel' ), 739 'fields' => array( 740 array( 741 'id' => 'description', 742 'layout' => array( 743 'type' => 'panel', 744 'labelPosition' => 'top', 745 'showPlaceholderIfEmpty' => true, 746 ), 747 ), 748 array( 749 'id' => 'description_readonly', 750 'layout' => array( 751 'type' => 'regular', 752 'labelPosition' => 'none', 753 ), 754 ), 755 array( 756 'id' => 'reading_settings', 757 'layout' => array( 758 'type' => 'regular', 759 'labelPosition' => 'none', 760 ), 761 ), 762 array( 763 'id' => 'last_edited_date', 764 'layout' => array( 765 'type' => 'panel', 766 'labelPosition' => 'none', 767 ), 768 ), 769 'revisions', 770 // The following fields are only meaningful in the `home`/`index` 771 // template summary. They edit other entities (`root/site` and the 772 // posts page); the editor merges those records into the form data 773 // under a namespace and controls when the fields are shown. 774 'posts_page_title', 775 'posts_per_page', 776 'default_comment_status', 777 ), 778 ); 779 780 $data->set( 781 array( 782 'default_view' => $default_view, 783 'default_layouts' => $default_layouts, 784 'view_list' => array_merge( $view_list, $registered_authors, $user_authors ), 785 'form' => $form, 786 ), 787 1 788 ); 789 790 return $data; 791 } 792 793 /** 794 * Provides the view configuration for the `attachment` post type. 795 * 796 * An attachment is edited through a form (the media editor) that shares 797 * nothing with the default post type form, so the `form` is replaced: the 798 * editable fields first, in a regular layout, then the metadata and the file 799 * information, in panels. The generic `default_view`, `default_layouts`, and 800 * `view_list` built by wp_get_entity_view_config() are left untouched. 801 * 802 * @since 7.2.0 803 * 804 * @param WP_View_Config_Data $data The view configuration container for the entity. 805 * @return WP_View_Config_Data The updated view configuration container. 806 */ 807 function _wp_get_entity_view_config_posttype_attachment( $data ) { 808 $regular_layout = array( 809 'type' => 'regular', 810 'labelPosition' => 'top', 811 ); 812 813 return $data->set( 814 array( 815 'form' => array( 816 'layout' => array( 'type' => 'panel' ), 817 'fields' => array( 818 array( 819 'id' => 'title', 820 'layout' => $regular_layout, 821 ), 822 array( 823 'id' => 'alt_text', 824 'layout' => $regular_layout, 825 ), 826 array( 827 'id' => 'caption', 828 'layout' => $regular_layout, 829 ), 830 array( 831 'id' => 'description', 832 'layout' => $regular_layout, 833 ), 834 'date', 835 array( 836 'id' => 'author', 837 'layout' => array( 838 'type' => 'panel', 839 'showPlaceholderIfEmpty' => true, 840 ), 841 ), 842 'filename', 843 'mime_type', 844 'filesize', 845 'media_dimensions', 846 'attached_to', 847 ), 848 ), 849 ), 850 1 851 ); 852 } 853 854 /** 855 * Provides the view configuration for the `root`/`site` entity. 856 * 857 * The site settings are a singleton record edited through a form (the site 858 * editor's Identity screen) rather than listed in a view, so only the `form` 859 * is defined here. The generic `default_view`, `default_layouts`, and 860 * `view_list` built by wp_get_entity_view_config() are left untouched. 861 * 862 * @since 7.2.0 863 * 864 * @param WP_View_Config_Data $data The view configuration container for the entity. 865 * @return WP_View_Config_Data The updated view configuration container. 866 */ 867 function _wp_get_entity_view_config_root_site( $data ) { 868 return $data->set( 869 array( 870 'form' => array( 871 'layout' => array( 872 'type' => 'regular', 873 'labelPosition' => 'top', 874 ), 875 'fields' => array( 876 'title', 877 'description', 878 'site_logo', 879 'site_icon', 880 ), 881 ), 882 ), 883 1 884 ); 885 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Fri Nov 13 08:20:01 2026 | Cross-referenced by PHPXref |