[ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * Script Modules API: WP_Script_Modules class. 4 * 5 * Native support for ES Modules and Import Maps. 6 * 7 * @package WordPress 8 * @subpackage Script Modules 9 */ 10 11 /** 12 * Core class used to register script modules. 13 * 14 * @since 6.5.0 15 */ 16 class WP_Script_Modules { 17 /** 18 * Holds the registered script modules, keyed by script module identifier. 19 * 20 * @since 6.5.0 21 * @var array<string, array<string, mixed>> 22 */ 23 private $registered = array(); 24 25 /** 26 * An array of IDs for queued script modules. 27 * 28 * @since 6.9.0 29 * @var string[] 30 */ 31 private $queue = array(); 32 33 /** 34 * Holds the script module identifiers that have been printed. 35 * 36 * @since 6.9.0 37 * @var string[] 38 */ 39 private $done = array(); 40 41 /** 42 * Tracks whether the @wordpress/a11y script module is available. 43 * 44 * Some additional HTML is required on the page for the module to work. Track 45 * whether it's available to print at the appropriate time. 46 * 47 * @since 6.7.0 48 * @var bool 49 */ 50 private $a11y_available = false; 51 52 /** 53 * Holds a mapping of dependents (as IDs) for a given script ID. 54 * Used to optimize recursive dependency tree checks. 55 * 56 * @since 6.9.0 57 * @var array<string, string[]> 58 */ 59 private $dependents_map = array(); 60 61 /** 62 * Holds the valid values for fetchpriority. 63 * 64 * @since 6.9.0 65 * @var string[] 66 */ 67 private $priorities = array( 68 'low', 69 'auto', 70 'high', 71 ); 72 73 /** 74 * Registers the script module if no script module with that script module 75 * identifier has already been registered. 76 * 77 * @since 6.5.0 78 * @since 6.9.0 Added the $args parameter. 79 * 80 * @param string $id The identifier of the script module. Should be unique. It will be used in the 81 * final import map. 82 * @param string $src Optional. Full URL of the script module, or path of the script module relative 83 * to the WordPress root directory. If it is provided and the script module has 84 * not been registered yet, it will be registered. 85 * @param array $deps { 86 * Optional. List of dependencies. 87 * 88 * @type string|array ...$0 { 89 * An array of script module identifiers of the dependencies of this script 90 * module. The dependencies can be strings or arrays. If they are arrays, 91 * they need an `id` key with the script module identifier, and can contain 92 * an `import` key with either `static` or `dynamic`. By default, 93 * dependencies that don't contain an `import` key are considered static. 94 * 95 * @type string $id The script module identifier. 96 * @type string $import Optional. Import type. May be either `static` or 97 * `dynamic`. Defaults to `static`. 98 * } 99 * } 100 * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false. 101 * It is added to the URL as a query string for cache busting purposes. If $version 102 * is set to false, the version number is the currently installed WordPress version. 103 * If $version is set to null, no version is added. 104 * @param array $args { 105 * Optional. An array of additional args. Default empty array. 106 * 107 * @type bool $in_footer Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional. 108 * @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional. 109 * } 110 */ 111 public function register( string $id, string $src, array $deps = array(), $version = false, array $args = array() ) { 112 if ( '' === $id ) { 113 _doing_it_wrong( __METHOD__, __( 'Non-empty string required for id.' ), '6.9.0' ); 114 return; 115 } 116 117 if ( ! isset( $this->registered[ $id ] ) ) { 118 $dependencies = array(); 119 foreach ( $deps as $dependency ) { 120 if ( is_array( $dependency ) ) { 121 if ( ! isset( $dependency['id'] ) || ! is_string( $dependency['id'] ) ) { 122 _doing_it_wrong( __METHOD__, __( 'Missing required id key in entry among dependencies array.' ), '6.5.0' ); 123 continue; 124 } 125 $dependencies[] = array( 126 'id' => $dependency['id'], 127 'import' => isset( $dependency['import'] ) && 'dynamic' === $dependency['import'] ? 'dynamic' : 'static', 128 ); 129 } elseif ( is_string( $dependency ) ) { 130 $dependencies[] = array( 131 'id' => $dependency, 132 'import' => 'static', 133 ); 134 } else { 135 _doing_it_wrong( __METHOD__, __( 'Entries in dependencies array must be either strings or arrays with an id key.' ), '6.5.0' ); 136 } 137 } 138 139 $in_footer = isset( $args['in_footer'] ) && (bool) $args['in_footer']; 140 141 $fetchpriority = 'auto'; 142 if ( isset( $args['fetchpriority'] ) ) { 143 if ( $this->is_valid_fetchpriority( $args['fetchpriority'] ) ) { 144 $fetchpriority = $args['fetchpriority']; 145 } else { 146 _doing_it_wrong( 147 __METHOD__, 148 sprintf( 149 /* translators: 1: $fetchpriority, 2: $id */ 150 __( 'Invalid fetchpriority `%1$s` defined for `%2$s` during script registration.' ), 151 is_string( $args['fetchpriority'] ) ? $args['fetchpriority'] : gettype( $args['fetchpriority'] ), 152 $id 153 ), 154 '6.9.0' 155 ); 156 } 157 } 158 159 $this->registered[ $id ] = array( 160 'src' => $src, 161 'version' => $version, 162 'dependencies' => $dependencies, 163 'in_footer' => $in_footer, 164 'fetchpriority' => $fetchpriority, 165 ); 166 } 167 } 168 169 /** 170 * Gets IDs for queued script modules. 171 * 172 * @since 6.9.0 173 * 174 * @return string[] Script module IDs. 175 */ 176 public function get_queue(): array { 177 return $this->queue; 178 } 179 180 /** 181 * Checks if the provided fetchpriority is valid. 182 * 183 * @since 6.9.0 184 * 185 * @param string|mixed $priority Fetch priority. 186 * @return bool Whether valid fetchpriority. 187 */ 188 private function is_valid_fetchpriority( $priority ): bool { 189 return in_array( $priority, $this->priorities, true ); 190 } 191 192 /** 193 * Sets the fetch priority for a script module. 194 * 195 * @since 6.9.0 196 * 197 * @param string $id Script module identifier. 198 * @param 'auto'|'low'|'high' $priority Fetch priority for the script module. 199 * @return bool Whether setting the fetchpriority was successful. 200 */ 201 public function set_fetchpriority( string $id, string $priority ): bool { 202 if ( ! isset( $this->registered[ $id ] ) ) { 203 return false; 204 } 205 206 if ( '' === $priority ) { 207 $priority = 'auto'; 208 } 209 210 if ( ! $this->is_valid_fetchpriority( $priority ) ) { 211 _doing_it_wrong( 212 __METHOD__, 213 /* translators: %s: Invalid fetchpriority. */ 214 sprintf( __( 'Invalid fetchpriority: %s' ), $priority ), 215 '6.9.0' 216 ); 217 return false; 218 } 219 220 $this->registered[ $id ]['fetchpriority'] = $priority; 221 return true; 222 } 223 224 /** 225 * Sets whether a script module should be printed in the footer. 226 * 227 * This is only relevant in block themes. 228 * 229 * @since 6.9.0 230 * 231 * @param string $id Script module identifier. 232 * @param bool $in_footer Whether to print in the footer. 233 * @return bool Whether setting the printing location was successful. 234 */ 235 public function set_in_footer( string $id, bool $in_footer ): bool { 236 if ( ! isset( $this->registered[ $id ] ) ) { 237 return false; 238 } 239 $this->registered[ $id ]['in_footer'] = $in_footer; 240 return true; 241 } 242 243 /** 244 * Marks the script module to be enqueued in the page. 245 * 246 * If a src is provided and the script module has not been registered yet, it 247 * will be registered. 248 * 249 * @since 6.5.0 250 * @since 6.9.0 Added the $args parameter. 251 * 252 * @param string $id The identifier of the script module. Should be unique. It will be used in the 253 * final import map. 254 * @param string $src Optional. Full URL of the script module, or path of the script module relative 255 * to the WordPress root directory. If it is provided and the script module has 256 * not been registered yet, it will be registered. 257 * @param array $deps { 258 * Optional. List of dependencies. 259 * 260 * @type string|array ...$0 { 261 * An array of script module identifiers of the dependencies of this script 262 * module. The dependencies can be strings or arrays. If they are arrays, 263 * they need an `id` key with the script module identifier, and can contain 264 * an `import` key with either `static` or `dynamic`. By default, 265 * dependencies that don't contain an `import` key are considered static. 266 * 267 * @type string $id The script module identifier. 268 * @type string $import Optional. Import type. May be either `static` or 269 * `dynamic`. Defaults to `static`. 270 * } 271 * } 272 * @param string|false|null $version Optional. String specifying the script module version number. Defaults to false. 273 * It is added to the URL as a query string for cache busting purposes. If $version 274 * is set to false, the version number is the currently installed WordPress version. 275 * If $version is set to null, no version is added. 276 * @param array $args { 277 * Optional. An array of additional args. Default empty array. 278 * 279 * @type bool $in_footer Whether to print the script module in the footer. Only relevant to block themes. Default 'false'. Optional. 280 * @type 'auto'|'low'|'high' $fetchpriority Fetch priority. Default 'auto'. Optional. 281 * } 282 */ 283 public function enqueue( string $id, string $src = '', array $deps = array(), $version = false, array $args = array() ) { 284 if ( '' === $id ) { 285 _doing_it_wrong( __METHOD__, __( 'Non-empty string required for id.' ), '6.9.0' ); 286 return; 287 } 288 289 if ( ! in_array( $id, $this->queue, true ) ) { 290 $this->queue[] = $id; 291 } 292 if ( ! isset( $this->registered[ $id ] ) && $src ) { 293 $this->register( $id, $src, $deps, $version, $args ); 294 } 295 } 296 297 /** 298 * Unmarks the script module so it will no longer be enqueued in the page. 299 * 300 * @since 6.5.0 301 * 302 * @param string $id The identifier of the script module. 303 */ 304 public function dequeue( string $id ) { 305 $this->queue = array_values( array_diff( $this->queue, array( $id ) ) ); 306 } 307 308 /** 309 * Removes a registered script module. 310 * 311 * @since 6.5.0 312 * 313 * @param string $id The identifier of the script module. 314 */ 315 public function deregister( string $id ) { 316 $this->dequeue( $id ); 317 unset( $this->registered[ $id ] ); 318 } 319 320 /** 321 * Adds the hooks to print the import map, enqueued script modules and script 322 * module preloads. 323 * 324 * In classic themes, the script modules used by the blocks are not yet known 325 * when the `wp_head` actions is fired, so it needs to print everything in the 326 * footer. 327 * 328 * @since 6.5.0 329 */ 330 public function add_hooks() { 331 $is_block_theme = wp_is_block_theme(); 332 $position = $is_block_theme ? 'wp_head' : 'wp_footer'; 333 add_action( $position, array( $this, 'print_import_map' ) ); 334 if ( $is_block_theme ) { 335 /* 336 * Modules can only be printed in the head for block themes because only with 337 * block themes will import map be fully populated by modules discovered by 338 * rendering the block template. In classic themes, modules are enqueued during 339 * template rendering, thus the import map must be printed in the footer, 340 * followed by all enqueued modules. 341 */ 342 add_action( 'wp_head', array( $this, 'print_head_enqueued_script_modules' ) ); 343 } 344 add_action( 'wp_footer', array( $this, 'print_enqueued_script_modules' ) ); 345 add_action( $position, array( $this, 'print_script_module_preloads' ) ); 346 347 add_action( 'admin_print_footer_scripts', array( $this, 'print_import_map' ) ); 348 add_action( 'admin_print_footer_scripts', array( $this, 'print_enqueued_script_modules' ) ); 349 add_action( 'admin_print_footer_scripts', array( $this, 'print_script_module_preloads' ) ); 350 351 add_action( 'wp_footer', array( $this, 'print_script_module_data' ) ); 352 add_action( 'admin_print_footer_scripts', array( $this, 'print_script_module_data' ) ); 353 add_action( 'wp_footer', array( $this, 'print_a11y_script_module_html' ), 20 ); 354 add_action( 'admin_print_footer_scripts', array( $this, 'print_a11y_script_module_html' ), 20 ); 355 } 356 357 /** 358 * Gets the highest fetch priority for the provided script IDs. 359 * 360 * @since 6.9.0 361 * 362 * @param string[] $ids Script module IDs. 363 * @return 'auto'|'low'|'high' Highest fetch priority for the provided script module IDs. 364 */ 365 private function get_highest_fetchpriority( array $ids ): string { 366 static $high_priority_index = null; 367 if ( null === $high_priority_index ) { 368 $high_priority_index = count( $this->priorities ) - 1; 369 } 370 371 $highest_priority_index = 0; 372 foreach ( $ids as $id ) { 373 if ( isset( $this->registered[ $id ] ) ) { 374 $highest_priority_index = (int) max( 375 $highest_priority_index, 376 (int) array_search( $this->registered[ $id ]['fetchpriority'], $this->priorities, true ) 377 ); 378 if ( $high_priority_index === $highest_priority_index ) { 379 break; 380 } 381 } 382 } 383 384 return $this->priorities[ $highest_priority_index ]; 385 } 386 387 /** 388 * Prints the enqueued script modules in head. 389 * 390 * This is only used in block themes. 391 * 392 * @since 6.9.0 393 */ 394 public function print_head_enqueued_script_modules() { 395 foreach ( $this->get_sorted_dependencies( $this->queue ) as $id ) { 396 if ( 397 isset( $this->registered[ $id ] ) && 398 ! $this->registered[ $id ]['in_footer'] 399 ) { 400 // If any dependency is set to be printed in footer, skip printing this module in head. 401 $dependencies = $this->get_dependencies( array( $id ) ); 402 foreach ( $dependencies as $dependency_id ) { 403 if ( 404 in_array( $dependency_id, $this->queue, true ) && 405 isset( $this->registered[ $dependency_id ] ) && 406 $this->registered[ $dependency_id ]['in_footer'] 407 ) { 408 continue 2; 409 } 410 } 411 $this->print_script_module( $id ); 412 } 413 } 414 } 415 416 /** 417 * Prints the enqueued script modules in footer. 418 * 419 * @since 6.5.0 420 */ 421 public function print_enqueued_script_modules() { 422 foreach ( $this->get_sorted_dependencies( $this->queue ) as $id ) { 423 $this->print_script_module( $id ); 424 } 425 } 426 427 /** 428 * Prints the enqueued script module using script tags with type="module" 429 * attributes. 430 * 431 * @since 6.9.0 432 * 433 * @param string $id The script module identifier. 434 */ 435 private function print_script_module( string $id ) { 436 if ( in_array( $id, $this->done, true ) || ! in_array( $id, $this->queue, true ) ) { 437 return; 438 } 439 440 $this->done[] = $id; 441 442 $src = $this->get_src( $id ); 443 if ( '' === $src ) { 444 return; 445 } 446 447 $attributes = array( 448 'type' => 'module', 449 'src' => $src, 450 'id' => $id . '-js-module', 451 ); 452 453 $script_module = $this->registered[ $id ]; 454 $dependents = $this->get_recursive_dependents( $id ); 455 $fetchpriority = $this->get_highest_fetchpriority( array_merge( array( $id ), $dependents ) ); 456 if ( 'auto' !== $fetchpriority ) { 457 $attributes['fetchpriority'] = $fetchpriority; 458 } 459 if ( $fetchpriority !== $script_module['fetchpriority'] ) { 460 $attributes['data-wp-fetchpriority'] = $script_module['fetchpriority']; 461 } 462 wp_print_script_tag( $attributes ); 463 } 464 465 /** 466 * Prints the static dependencies of the enqueued script modules using 467 * link tags with rel="modulepreload" attributes. 468 * 469 * If a script module is marked for enqueue, it will not be preloaded. 470 * 471 * @since 6.5.0 472 */ 473 public function print_script_module_preloads() { 474 $dependency_ids = $this->get_sorted_dependencies( $this->queue, array( 'static' ) ); 475 foreach ( $dependency_ids as $id ) { 476 // Don't preload if it's marked for enqueue. 477 if ( in_array( $id, $this->queue, true ) ) { 478 continue; 479 } 480 481 $src = $this->get_src( $id ); 482 if ( '' === $src ) { 483 continue; 484 } 485 486 $enqueued_dependents = array_intersect( $this->get_recursive_dependents( $id ), $this->queue ); 487 $highest_fetchpriority = $this->get_highest_fetchpriority( $enqueued_dependents ); 488 printf( 489 '<link rel="modulepreload" href="%s" id="%s"', 490 esc_url( $src ), 491 esc_attr( $id . '-js-modulepreload' ) 492 ); 493 if ( 'auto' !== $highest_fetchpriority ) { 494 printf( ' fetchpriority="%s"', esc_attr( $highest_fetchpriority ) ); 495 } 496 if ( $highest_fetchpriority !== $this->registered[ $id ]['fetchpriority'] && 'auto' !== $this->registered[ $id ]['fetchpriority'] ) { 497 printf( ' data-wp-fetchpriority="%s"', esc_attr( $this->registered[ $id ]['fetchpriority'] ) ); 498 } 499 echo ">\n"; 500 } 501 } 502 503 /** 504 * Prints the import map using a script tag with a type="importmap" attribute. 505 * 506 * @since 6.5.0 507 */ 508 public function print_import_map() { 509 $import_map = $this->get_import_map(); 510 if ( ! empty( $import_map['imports'] ) ) { 511 wp_print_inline_script_tag( 512 (string) wp_json_encode( $import_map, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ), 513 array( 514 'type' => 'importmap', 515 'id' => 'wp-importmap', 516 ) 517 ); 518 } 519 } 520 521 /** 522 * Returns the import map array. 523 * 524 * @since 6.5.0 525 * 526 * @return array Array with an `imports` key mapping to an array of script module identifiers and their respective 527 * URLs, including the version query. 528 */ 529 private function get_import_map(): array { 530 $imports = array(); 531 foreach ( $this->get_dependencies( $this->queue ) as $id ) { 532 $src = $this->get_src( $id ); 533 if ( '' !== $src ) { 534 $imports[ $id ] = $src; 535 } 536 } 537 return array( 'imports' => $imports ); 538 } 539 540 /** 541 * Retrieves the list of script modules marked for enqueue. 542 * 543 * Even though this is a private method and is unused in core, there are ecosystem plugins accessing it via the 544 * Reflection API. The ecosystem should rather use {@see self::get_queue()}. 545 * 546 * @since 6.5.0 547 * 548 * @return array<string, array> Script modules marked for enqueue, keyed by script module identifier. 549 */ 550 private function get_marked_for_enqueue(): array { 551 return wp_array_slice_assoc( 552 $this->registered, 553 $this->queue 554 ); 555 } 556 557 /** 558 * Retrieves all the dependencies for the given script module identifiers, filtered by import types. 559 * 560 * It will consolidate an array containing a set of unique dependencies based 561 * on the requested import types: 'static', 'dynamic', or both. This method is 562 * recursive and also retrieves dependencies of the dependencies. 563 * 564 * @since 6.5.0 565 * 566 * @param string[] $ids The identifiers of the script modules for which to gather dependencies. 567 * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both. 568 * Default is both. 569 * @return string[] List of IDs for script module dependencies. 570 */ 571 private function get_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ): array { 572 $all_dependencies = array(); 573 $id_queue = $ids; 574 575 while ( ! empty( $id_queue ) ) { 576 $id = array_shift( $id_queue ); 577 if ( ! isset( $this->registered[ $id ] ) ) { 578 continue; 579 } 580 581 foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) { 582 if ( 583 ! isset( $all_dependencies[ $dependency['id'] ] ) && 584 in_array( $dependency['import'], $import_types, true ) && 585 isset( $this->registered[ $dependency['id'] ] ) 586 ) { 587 $all_dependencies[ $dependency['id'] ] = true; 588 589 // Add this dependency to the list to get dependencies for. 590 $id_queue[] = $dependency['id']; 591 } 592 } 593 } 594 595 return array_keys( $all_dependencies ); 596 } 597 598 /** 599 * Gets all dependents of a script module. 600 * 601 * This is not recursive. 602 * 603 * @since 6.9.0 604 * 605 * @see WP_Scripts::get_dependents() 606 * 607 * @param string $id The script ID. 608 * @return string[] Script module IDs. 609 */ 610 private function get_dependents( string $id ): array { 611 // Check if dependents map for the handle in question is present. If so, use it. 612 if ( isset( $this->dependents_map[ $id ] ) ) { 613 return $this->dependents_map[ $id ]; 614 } 615 616 $dependents = array(); 617 618 // Iterate over all registered scripts, finding dependents of the script passed to this method. 619 foreach ( $this->registered as $registered_id => $args ) { 620 if ( in_array( $id, wp_list_pluck( $args['dependencies'], 'id' ), true ) ) { 621 $dependents[] = $registered_id; 622 } 623 } 624 625 // Add the module's dependents to the map to ease future lookups. 626 $this->dependents_map[ $id ] = $dependents; 627 628 return $dependents; 629 } 630 631 /** 632 * Gets all recursive dependents of a script module. 633 * 634 * @since 6.9.0 635 * 636 * @see WP_Scripts::get_dependents() 637 * 638 * @param string $id The script ID. 639 * @return string[] Script module IDs. 640 */ 641 private function get_recursive_dependents( string $id ): array { 642 $dependents = array(); 643 $id_queue = array( $id ); 644 $processed = array(); 645 646 while ( ! empty( $id_queue ) ) { 647 $current_id = array_shift( $id_queue ); 648 649 // Skip unregistered or already-processed script modules. 650 if ( ! isset( $this->registered[ $current_id ] ) || isset( $processed[ $current_id ] ) ) { 651 continue; 652 } 653 654 // Mark as processed to guard against infinite loops from circular dependencies. 655 $processed[ $current_id ] = true; 656 657 // Find the direct dependents of the current script. 658 foreach ( $this->get_dependents( $current_id ) as $dependent_id ) { 659 // Only add the dependent if we haven't found it before. 660 if ( ! isset( $dependents[ $dependent_id ] ) ) { 661 $dependents[ $dependent_id ] = true; 662 663 // Add dependency to the queue. 664 $id_queue[] = $dependent_id; 665 } 666 } 667 } 668 669 return array_keys( $dependents ); 670 } 671 672 /** 673 * Sorts the given script module identifiers based on their dependencies. 674 * 675 * It will return a list of script module identifiers sorted in the order 676 * they should be printed, so that dependencies are printed before the script 677 * modules that depend on them. 678 * 679 * @since 6.9.0 680 * 681 * @param string[] $ids The identifiers of the script modules to sort. 682 * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both. 683 * Default is both. 684 * @return string[] Sorted list of script module identifiers. 685 */ 686 private function get_sorted_dependencies( array $ids, array $import_types = array( 'static', 'dynamic' ) ): array { 687 $sorted = array(); 688 689 foreach ( $ids as $id ) { 690 $this->sort_item_dependencies( $id, $import_types, $sorted ); 691 } 692 693 return array_unique( $sorted ); 694 } 695 696 /** 697 * Recursively sorts the dependencies for a single script module identifier. 698 * 699 * @since 6.9.0 700 * 701 * @param string $id The identifier of the script module to sort. 702 * @param string[] $import_types Optional. Import types of dependencies to retrieve: 'static', 'dynamic', or both. 703 * @param string[] &$sorted The array of sorted identifiers, passed by reference. 704 * @return bool True on success, false on failure (e.g., missing dependency). 705 */ 706 private function sort_item_dependencies( string $id, array $import_types, array &$sorted ): bool { 707 // If already processed, don't do it again. 708 if ( in_array( $id, $sorted, true ) ) { 709 return true; 710 } 711 712 // If the item doesn't exist, fail. 713 if ( ! isset( $this->registered[ $id ] ) ) { 714 return false; 715 } 716 717 $dependency_ids = array(); 718 foreach ( $this->registered[ $id ]['dependencies'] as $dependency ) { 719 if ( in_array( $dependency['import'], $import_types, true ) ) { 720 $dependency_ids[] = $dependency['id']; 721 } 722 } 723 724 // If the item requires dependencies that do not exist, fail. 725 if ( count( array_diff( $dependency_ids, array_keys( $this->registered ) ) ) > 0 ) { 726 return false; 727 } 728 729 // Recursively process dependencies. 730 foreach ( $dependency_ids as $dependency_id ) { 731 if ( ! $this->sort_item_dependencies( $dependency_id, $import_types, $sorted ) ) { 732 // A dependency failed to resolve, so this branch fails. 733 return false; 734 } 735 } 736 737 // All dependencies are sorted, so we can now add the current item. 738 $sorted[] = $id; 739 740 return true; 741 } 742 743 /** 744 * Gets the versioned URL for a script module src. 745 * 746 * If $version is set to false, the version number is the currently installed 747 * WordPress version. If $version is set to null, no version is added. 748 * Otherwise, the string passed in $version is used. 749 * 750 * @since 6.5.0 751 * 752 * @param string $id The script module identifier. 753 * @return string The script module src with a version if relevant. 754 */ 755 private function get_src( string $id ): string { 756 if ( ! isset( $this->registered[ $id ] ) ) { 757 return ''; 758 } 759 760 $script_module = $this->registered[ $id ]; 761 $src = $script_module['src']; 762 763 if ( '' !== $src ) { 764 if ( false === $script_module['version'] ) { 765 $src = add_query_arg( 'ver', get_bloginfo( 'version' ), $src ); 766 } elseif ( null !== $script_module['version'] ) { 767 $src = add_query_arg( 'ver', $script_module['version'], $src ); 768 } 769 } 770 771 /** 772 * Filters the script module source. 773 * 774 * @since 6.5.0 775 * 776 * @param string $src Module source URL. 777 * @param string $id Module identifier. 778 */ 779 $src = apply_filters( 'script_module_loader_src', $src, $id ); 780 if ( ! is_string( $src ) ) { 781 $src = ''; 782 } 783 784 return $src; 785 } 786 787 /** 788 * Print data associated with Script Modules. 789 * 790 * The data will be embedded in the page HTML and can be read by Script Modules on page load. 791 * 792 * @since 6.7.0 793 * 794 * Data can be associated with a Script Module via the 795 * {@see "script_module_data_{$module_id}"} filter. 796 * 797 * The data for a Script Module will be serialized as JSON in a script tag with an ID of the 798 * form `wp-script-module-data-{$module_id}`. 799 */ 800 public function print_script_module_data(): void { 801 $modules = array(); 802 foreach ( array_unique( $this->queue ) as $id ) { 803 if ( '@wordpress/a11y' === $id ) { 804 $this->a11y_available = true; 805 } 806 $modules[ $id ] = true; 807 } 808 foreach ( array_keys( $this->get_import_map()['imports'] ) as $id ) { 809 if ( '@wordpress/a11y' === $id ) { 810 $this->a11y_available = true; 811 } 812 $modules[ $id ] = true; 813 } 814 815 foreach ( array_keys( $modules ) as $module_id ) { 816 /** 817 * Filters data associated with a given Script Module. 818 * 819 * Script Modules may require data that is required for initialization or is essential 820 * to have immediately available on page load. These are suitable use cases for 821 * this data. 822 * 823 * The dynamic portion of the hook name, `$module_id`, refers to the Script Module ID 824 * that the data is associated with. 825 * 826 * This is best suited to pass essential data that must be available to the module for 827 * initialization or immediately on page load. It does not replace the REST API or 828 * fetching data from the client. 829 * 830 * Example: 831 * 832 * add_filter( 833 * 'script_module_data_MyScriptModuleID', 834 * function ( array $data ): array { 835 * $data['dataForClient'] = 'ok'; 836 * return $data; 837 * } 838 * ); 839 * 840 * If the filter returns no data (an empty array), nothing will be embedded in the page. 841 * 842 * The data for a given Script Module, if provided, will be JSON serialized in a script 843 * tag with an ID of the form `wp-script-module-data-{$module_id}`. 844 * 845 * The data can be read on the client with a pattern like this: 846 * 847 * Example: 848 * 849 * const dataContainer = document.getElementById( 'wp-script-module-data-MyScriptModuleID' ); 850 * let data = {}; 851 * if ( dataContainer ) { 852 * try { 853 * data = JSON.parse( dataContainer.textContent ); 854 * } catch {} 855 * } 856 * // data.dataForClient === 'ok'; 857 * initMyScriptModuleWithData( data ); 858 * 859 * @since 6.7.0 860 * 861 * @param array $data The data associated with the Script Module. 862 */ 863 $data = apply_filters( "script_module_data_{$module_id}", array() ); 864 865 if ( is_array( $data ) && array() !== $data ) { 866 /* 867 * This data will be printed as JSON inside a script tag like this: 868 * <script type="application/json"></script> 869 * 870 * A script tag must be closed by a sequence beginning with `</`. It's impossible to 871 * close a script tag without using `<`. We ensure that `<` is escaped and `/` can 872 * remain unescaped, so `</script>` will be printed as `\u003C/script\u00E3`. 873 * 874 * - JSON_HEX_TAG: All < and > are converted to \u003C and \u003E. 875 * - JSON_UNESCAPED_SLASHES: Don't escape /. 876 * 877 * If the page will use UTF-8 encoding, it's safe to print unescaped unicode: 878 * 879 * - JSON_UNESCAPED_UNICODE: Encode multibyte Unicode characters literally (instead of as `\uXXXX`). 880 * - JSON_UNESCAPED_LINE_TERMINATORS: The line terminators are kept unescaped when 881 * JSON_UNESCAPED_UNICODE is supplied. It uses the same behaviour as it was 882 * before PHP 7.1 without this constant. Available as of PHP 7.1.0. 883 * 884 * The JSON specification requires encoding in UTF-8, so if the generated HTML page 885 * is not encoded in UTF-8 then it's not safe to include those literals. They must 886 * be escaped to avoid encoding issues. 887 * 888 * @see https://www.rfc-editor.org/rfc/rfc8259.html for details on encoding requirements. 889 * @see https://www.php.net/manual/en/json.constants.php for details on these constants. 890 * @see https://html.spec.whatwg.org/#script-data-state for details on script tag parsing. 891 */ 892 $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_LINE_TERMINATORS; 893 if ( ! is_utf8_charset() ) { 894 $json_encode_flags = JSON_HEX_TAG | JSON_UNESCAPED_SLASHES; 895 } 896 897 wp_print_inline_script_tag( 898 (string) wp_json_encode( 899 $data, 900 $json_encode_flags 901 ), 902 array( 903 'type' => 'application/json', 904 'id' => "wp-script-module-data-{$module_id}", 905 ) 906 ); 907 } 908 } 909 } 910 911 /** 912 * @access private This is only intended to be called by the registered actions. 913 * 914 * @since 6.7.0 915 */ 916 public function print_a11y_script_module_html() { 917 if ( ! $this->a11y_available ) { 918 return; 919 } 920 echo '<div style="position:absolute;margin:-1px;padding:0;height:1px;width:1px;overflow:hidden;clip-path:inset(50%);border:0;word-wrap:normal !important;">' 921 . '<p id="a11y-speak-intro-text" class="a11y-speak-intro-text" hidden>' . esc_html__( 'Notifications' ) . '</p>' 922 . '<div id="a11y-speak-assertive" class="a11y-speak-region" aria-live="assertive" aria-relevant="additions text" aria-atomic="true"></div>' 923 . '<div id="a11y-speak-polite" class="a11y-speak-region" aria-live="polite" aria-relevant="additions text" aria-atomic="true"></div>' 924 . '</div>'; 925 } 926 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated : Thu Oct 23 08:20:05 2025 | Cross-referenced by PHPXref |