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