[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> class-wp-scripts.php (source)

   1  <?php
   2  /**
   3   * Dependencies API: WP_Scripts class
   4   *
   5   * @since 2.6.0
   6   *
   7   * @package WordPress
   8   * @subpackage Dependencies
   9   */
  10  
  11  /**
  12   * Core class used to register scripts.
  13   *
  14   * @since 2.1.0
  15   *
  16   * @see WP_Dependencies
  17   */
  18  class WP_Scripts extends WP_Dependencies {
  19      /**
  20       * Base URL for scripts.
  21       *
  22       * Full URL with trailing slash.
  23       *
  24       * @since 2.6.0
  25       * @see wp_default_scripts()
  26       * @var string|null
  27       */
  28      public $base_url;
  29  
  30      /**
  31       * URL of the content directory.
  32       *
  33       * @since 2.8.0
  34       * @see wp_default_scripts()
  35       * @var string|null
  36       */
  37      public $content_url;
  38  
  39      /**
  40       * Default version string for scripts.
  41       *
  42       * @since 2.6.0
  43       * @see wp_default_scripts()
  44       * @var string|null
  45       */
  46      public $default_version;
  47  
  48      /**
  49       * Holds handles of scripts which are enqueued in footer.
  50       *
  51       * @since 2.8.0
  52       * @var string[]
  53       */
  54      public $in_footer = array();
  55  
  56      /**
  57       * Holds a list of script handles which will be concatenated.
  58       *
  59       * @since 2.8.0
  60       * @var string
  61       */
  62      public $concat = '';
  63  
  64      /**
  65       * Holds a string which contains script handles and their version.
  66       *
  67       * @since 2.8.0
  68       * @deprecated 3.4.0
  69       * @var string
  70       */
  71      public $concat_version = '';
  72  
  73      /**
  74       * Whether to perform concatenation.
  75       *
  76       * @since 2.8.0
  77       * @var bool
  78       */
  79      public $do_concat = false;
  80  
  81      /**
  82       * Holds HTML markup of scripts and additional data if concatenation
  83       * is enabled.
  84       *
  85       * @since 2.8.0
  86       * @var string
  87       */
  88      public $print_html = '';
  89  
  90      /**
  91       * Holds inline code if concatenation is enabled.
  92       *
  93       * @since 2.8.0
  94       * @var string
  95       */
  96      public $print_code = '';
  97  
  98      /**
  99       * Holds a list of script handles which are not in the default directory
 100       * if concatenation is enabled.
 101       *
 102       * Unused in core.
 103       *
 104       * @since 2.8.0
 105       * @var string
 106       */
 107      public $ext_handles = '';
 108  
 109      /**
 110       * Holds a string which contains handles and versions of scripts which
 111       * are not in the default directory if concatenation is enabled.
 112       *
 113       * Unused in core.
 114       *
 115       * @since 2.8.0
 116       * @var string
 117       */
 118      public $ext_version = '';
 119  
 120      /**
 121       * List of default directories.
 122       *
 123       * @since 2.8.0
 124       * @see wp_default_scripts()
 125       * @var string[]|null
 126       */
 127      public $default_dirs;
 128  
 129      /**
 130       * Holds a mapping of dependents (as handles) for a given script handle.
 131       * Used to optimize recursive dependency tree checks.
 132       *
 133       * @since 6.3.0
 134       * @var array<string, string[]>
 135       */
 136      private $dependents_map = array();
 137  
 138      /**
 139       * Holds a reference to the delayed (non-blocking) script loading strategies.
 140       * Used by methods that validate loading strategies.
 141       *
 142       * @since 6.3.0
 143       * @var string[]
 144       */
 145      private $delayed_strategies = array( 'defer', 'async' );
 146  
 147      /**
 148       * Constructor.
 149       *
 150       * @since 2.6.0
 151       */
 152  	public function __construct() {
 153          $this->init();
 154          add_action( 'init', array( $this, 'init' ), 0 );
 155      }
 156  
 157      /**
 158       * Initialize the class.
 159       *
 160       * @since 3.4.0
 161       */
 162  	public function init() {
 163          /**
 164           * Fires when the WP_Scripts instance is initialized.
 165           *
 166           * @since 2.6.0
 167           *
 168           * @param WP_Scripts $wp_scripts WP_Scripts instance (passed by reference).
 169           */
 170          do_action_ref_array( 'wp_default_scripts', array( &$this ) );
 171      }
 172  
 173      /**
 174       * Prints scripts.
 175       *
 176       * Prints the scripts passed to it or the print queue. Also prints all necessary dependencies.
 177       *
 178       * @since 2.1.0
 179       * @since 2.8.0 Added the `$group` parameter.
 180       *
 181       * @param string|string[]|false $handles Optional. Scripts to be printed: queue (false),
 182       *                                       single script (string), or multiple scripts (array of strings).
 183       *                                       Default false.
 184       * @param int|false             $group   Optional. Group level: level (int), no groups (false).
 185       *                                       Default false.
 186       * @return string[] Handles of scripts that have been printed.
 187       */
 188  	public function print_scripts( $handles = false, $group = false ) {
 189          return $this->do_items( $handles, $group );
 190      }
 191  
 192      /**
 193       * Prints extra scripts of a registered script.
 194       *
 195       * @since 2.1.0
 196       * @since 2.8.0 Added the `$display` parameter.
 197       * @deprecated 3.3.0
 198       *
 199       * @see print_extra_script()
 200       *
 201       * @param string $handle  The script's registered handle.
 202       * @param bool   $display Optional. Whether to print the extra script
 203       *                        instead of just returning it. Default true.
 204       * @return bool|string|null Null if no data exists, extra scripts if `$display` is false,
 205       *                          true otherwise.
 206       * @phpstan-return ( $display is true ? true|null : string|null )
 207       */
 208  	public function print_scripts_l10n( $handle, $display = true ) {
 209          _deprecated_function( __FUNCTION__, '3.3.0', 'WP_Scripts::print_extra_script()' );
 210          return $this->print_extra_script( $handle, $display );
 211      }
 212  
 213      /**
 214       * Prints extra scripts of a registered script.
 215       *
 216       * @since 3.3.0
 217       *
 218       * @param string $handle  The script's registered handle.
 219       * @param bool   $display Optional. Whether to print the extra script
 220       *                        instead of just returning it. Default true.
 221       * @return bool|string|null Null if no data exists, extra scripts if `$display` is false,
 222       *                          true otherwise.
 223       * @phpstan-return ( $display is true ? true|null : string|null )
 224       */
 225  	public function print_extra_script( $handle, $display = true ) {
 226          $output = $this->get_data( $handle, 'data' );
 227          if ( ! $output ) {
 228              return null;
 229          }
 230  
 231          /*
 232           * Do not print a sourceURL comment if concatenation is enabled.
 233           *
 234           * Extra scripts may be concatenated into a single script.
 235           * The line-based sourceURL comments may break concatenated scripts
 236           * and do not make sense when multiple scripts are joined together.
 237           */
 238          if ( ! $this->do_concat ) {
 239              $output .= sprintf(
 240                  "\n//# sourceURL=%s",
 241                  rawurlencode( "{$handle}-js-extra" )
 242              );
 243          }
 244  
 245          if ( ! $display ) {
 246              return $output;
 247          }
 248  
 249          wp_print_inline_script_tag( $output, array( 'id' => "{$handle}-js-extra" ) );
 250  
 251          return true;
 252      }
 253  
 254      /**
 255       * Checks whether all dependents of a given handle are in the footer.
 256       *
 257       * If there are no dependents, this is considered the same as if all dependents were in the footer.
 258       *
 259       * @since 6.4.0
 260       *
 261       * @param string $handle Script handle.
 262       * @return bool Whether all dependents are in the footer.
 263       */
 264  	private function are_all_dependents_in_footer( $handle ) {
 265          foreach ( $this->get_dependents( $handle ) as $dep ) {
 266              if ( isset( $this->groups[ $dep ] ) && 0 === $this->groups[ $dep ] ) {
 267                  return false;
 268              }
 269          }
 270          return true;
 271      }
 272  
 273      /**
 274       * Processes a script dependency.
 275       *
 276       * @since 2.6.0
 277       * @since 2.8.0 Added the `$group` parameter.
 278       *
 279       * @see WP_Dependencies::do_item()
 280       *
 281       * @param string    $handle The script's registered handle.
 282       * @param int|false $group  Optional. Group level: level (int), no groups (false).
 283       *                          Default false.
 284       * @return bool True on success, false on failure.
 285       */
 286  	public function do_item( $handle, $group = false ) {
 287          if ( ! parent::do_item( $handle ) ) {
 288              return false;
 289          }
 290  
 291          if ( 0 === $group && $this->groups[ $handle ] > 0 ) {
 292              $this->in_footer[] = $handle;
 293              return false;
 294          }
 295  
 296          if ( false === $group && in_array( $handle, $this->in_footer, true ) ) {
 297              $this->in_footer = array_diff( $this->in_footer, (array) $handle );
 298          }
 299  
 300          $obj = $this->registered[ $handle ];
 301          if ( $obj->extra['conditional'] ?? false ) {
 302              return false;
 303          }
 304  
 305          if ( null === $obj->ver ) {
 306              $ver = '';
 307          } else {
 308              $ver = $obj->ver ? $obj->ver : $this->default_version;
 309          }
 310  
 311          if ( isset( $this->args[ $handle ] ) ) {
 312              $ver = $ver ? $ver . '&amp;' . $this->args[ $handle ] : $this->args[ $handle ];
 313          }
 314  
 315          $src               = $obj->src;
 316          $strategy          = $this->get_eligible_loading_strategy( $handle );
 317          $intended_strategy = (string) $this->get_data( $handle, 'strategy' );
 318  
 319          if ( ! $this->is_delayed_strategy( $intended_strategy ) ) {
 320              $intended_strategy = '';
 321          }
 322  
 323          /*
 324           * Move this script to the footer if:
 325           * 1. The script is in the header group.
 326           * 2. The current output is the header.
 327           * 3. The intended strategy is delayed.
 328           * 4. The actual strategy is not delayed.
 329           * 5. All dependent scripts are in the footer.
 330           */
 331          if (
 332              0 === $group &&
 333              0 === $this->groups[ $handle ] &&
 334              $intended_strategy &&
 335              ! $this->is_delayed_strategy( $strategy ) &&
 336              $this->are_all_dependents_in_footer( $handle )
 337          ) {
 338              $this->in_footer[] = $handle;
 339              return false;
 340          }
 341  
 342          $before_script = $this->get_inline_script_tag( $handle, 'before' );
 343          $after_script  = $this->get_inline_script_tag( $handle, 'after' );
 344  
 345          if ( $before_script || $after_script ) {
 346              $inline_script_tag = $before_script . $after_script;
 347          } else {
 348              $inline_script_tag = '';
 349          }
 350  
 351          /*
 352           * Prevent concatenation of scripts if the text domain is defined
 353           * to ensure the dependency order is respected.
 354           */
 355          $translations_stop_concat = ! empty( $obj->textdomain );
 356  
 357          $translations = $this->print_translations( $handle, false );
 358          if ( $translations ) {
 359              /*
 360               * The sourceURL comment is not included by WP_Scripts::print_translations()
 361               * when `$display` is `false` to prevent issues where the script tag contents are used
 362               * by extenders for other purposes, for example concatenated with other script content.
 363               *
 364               * Include the sourceURL comment here as it would be when printed directly.
 365               */
 366              $source_url    = rawurlencode( "{$handle}-js-translations" );
 367              $translations .= "\n//# sourceURL={$source_url}";
 368              $translations  = wp_get_inline_script_tag( $translations, array( 'id' => "{$handle}-js-translations" ) );
 369          }
 370  
 371          if ( $this->do_concat ) {
 372              /**
 373               * Filters the script loader source.
 374               *
 375               * @since 2.2.0
 376               *
 377               * @param string $src    Script loader source path.
 378               * @param string $handle Script handle.
 379               */
 380              $filtered_src = apply_filters( 'script_loader_src', $src, $handle );
 381  
 382              if (
 383                  is_string( $filtered_src )
 384                  && $this->in_default_dir( $filtered_src )
 385                  && ( $before_script || $after_script || $translations_stop_concat || $this->is_delayed_strategy( $strategy ) )
 386              ) {
 387                  $this->do_concat = false;
 388  
 389                  // Have to print the so-far concatenated scripts right away to maintain the right order.
 390                  _print_scripts();
 391                  $this->reset();
 392              } elseif ( $this->in_default_dir( $filtered_src ) ) {
 393                  $this->print_code     .= $this->print_extra_script( $handle, false );
 394                  $this->concat         .= "$handle,";
 395                  $this->concat_version .= "$handle$ver";
 396                  return true;
 397              } else {
 398                  $this->ext_handles .= "$handle,";
 399                  $this->ext_version .= "$handle$ver";
 400              }
 401          }
 402  
 403          $this->print_extra_script( $handle );
 404  
 405          // A single item may alias a set of items, by having dependencies, but no source.
 406          if ( ! $src ) {
 407              if ( $inline_script_tag ) {
 408                  if ( $this->do_concat ) {
 409                      $this->print_html .= $inline_script_tag;
 410                  } else {
 411                      echo $inline_script_tag;
 412                  }
 413              }
 414  
 415              return true;
 416          }
 417  
 418          if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $this->content_url && str_starts_with( $src, $this->content_url ) ) ) {
 419              $src = $this->base_url . $src;
 420          }
 421  
 422          $ver_to_add = '';
 423          if ( empty( $obj->ver ) && null !== $obj->ver && is_string( $this->default_version ) ) {
 424              $ver_to_add = $this->default_version;
 425          } elseif ( is_scalar( $obj->ver ) ) {
 426              $ver_to_add = (string) $obj->ver;
 427          }
 428  
 429          $added_args = (string) ( $this->args[ $handle ] ?? '' );
 430  
 431          if ( '' !== $ver_to_add || '' !== $added_args ) {
 432              $fragment = strstr( $src, '#' );
 433              if ( false !== $fragment ) {
 434                  $src = substr( $src, 0, -strlen( $fragment ) );
 435              }
 436  
 437              if ( '' !== $ver_to_add ) {
 438                  $src .= ( str_contains( $src, '?' ) ? '&' : '?' ) . 'ver=' . rawurlencode( $ver_to_add );
 439              }
 440              if ( '' !== $added_args ) {
 441                  $src .= ( str_contains( $src, '?' ) ? '&' : '?' ) . $added_args;
 442              }
 443  
 444              if ( false !== $fragment ) {
 445                  $src .= $fragment;
 446              }
 447          }
 448  
 449          /** This filter is documented in wp-includes/class-wp-scripts.php */
 450          $src = esc_url_raw( apply_filters( 'script_loader_src', $src, $handle ) );
 451  
 452          if ( ! $src ) {
 453              return true;
 454          }
 455  
 456          $attr = array(
 457              'src' => $src,
 458              'id'  => "{$handle}-js",
 459          );
 460          if ( $strategy ) {
 461              $attr[ $strategy ] = true;
 462          }
 463          if ( $intended_strategy ) {
 464              $attr['data-wp-strategy'] = $intended_strategy;
 465          }
 466  
 467          // Determine fetchpriority.
 468          $original_fetchpriority = $obj->extra['fetchpriority'] ?? null;
 469          if ( null === $original_fetchpriority || ! $this->is_valid_fetchpriority( $original_fetchpriority ) ) {
 470              $original_fetchpriority = 'auto';
 471          }
 472          $actual_fetchpriority = $this->get_highest_fetchpriority_with_dependents( $handle );
 473          if ( null === $actual_fetchpriority ) {
 474              // If null, it's likely this script was not explicitly enqueued, so in this case use the original priority.
 475              $actual_fetchpriority = $original_fetchpriority;
 476          }
 477          if ( is_string( $actual_fetchpriority ) && 'auto' !== $actual_fetchpriority ) {
 478              $attr['fetchpriority'] = $actual_fetchpriority;
 479          }
 480  
 481          if ( $original_fetchpriority !== $actual_fetchpriority ) {
 482              $attr['data-wp-fetchpriority'] = $original_fetchpriority;
 483          }
 484  
 485          $tag  = $translations . $before_script;
 486          $tag .= wp_get_script_tag( $attr );
 487          $tag .= $after_script;
 488  
 489          /**
 490           * Filters the HTML script tag of an enqueued script.
 491           *
 492           * @since 4.1.0
 493           *
 494           * @param string $tag    The `<script>` tag for the enqueued script.
 495           * @param string $handle The script's registered handle.
 496           * @param string $src    The script's source URL.
 497           */
 498          $tag = apply_filters( 'script_loader_tag', $tag, $handle, $src );
 499  
 500          if ( $this->do_concat ) {
 501              $this->print_html .= $tag;
 502          } else {
 503              echo $tag;
 504          }
 505  
 506          return true;
 507      }
 508  
 509      /**
 510       * Adds extra code to a registered script.
 511       *
 512       * @since 4.5.0
 513       *
 514       * @param string $handle   Name of the script to add the inline script to.
 515       *                         Must be lowercase.
 516       * @param string $data     String containing the JavaScript to be added.
 517       * @param string $position Optional. Whether to add the inline script
 518       *                         before the handle or after. Default 'after'.
 519       * @return bool True on success, false on failure.
 520       */
 521  	public function add_inline_script( $handle, $data, $position = 'after' ) {
 522          if ( ! $data ) {
 523              return false;
 524          }
 525  
 526          if ( 'after' !== $position ) {
 527              $position = 'before';
 528          }
 529  
 530          $script   = (array) $this->get_data( $handle, $position );
 531          $script[] = $data;
 532  
 533          return $this->add_data( $handle, $position, $script );
 534      }
 535  
 536      /**
 537       * Prints inline scripts registered for a specific handle.
 538       *
 539       * @since 4.5.0
 540       * @deprecated 6.3.0 Use methods get_inline_script_tag() or get_inline_script_data() instead.
 541       *
 542       * @param string $handle   Name of the script to print inline scripts for.
 543       *                         Must be lowercase.
 544       * @param string $position Optional. Whether to add the inline script
 545       *                         before the handle or after. Default 'after'.
 546       * @param bool   $display  Optional. Whether to print the script tag
 547       *                         instead of just returning the script data. Default true.
 548       * @return string|false Script data on success, false otherwise.
 549       */
 550  	public function print_inline_script( $handle, $position = 'after', $display = true ) {
 551          _deprecated_function( __METHOD__, '6.3.0', 'WP_Scripts::get_inline_script_data() or WP_Scripts::get_inline_script_tag()' );
 552  
 553          $output = $this->get_inline_script_data( $handle, $position );
 554          if ( empty( $output ) ) {
 555              return false;
 556          }
 557  
 558          if ( $display ) {
 559              echo $this->get_inline_script_tag( $handle, $position );
 560          }
 561          return $output;
 562      }
 563  
 564      /**
 565       * Gets data for inline scripts registered for a specific handle.
 566       *
 567       * @since 6.3.0
 568       *
 569       * @param string $handle   Name of the script to get data for.
 570       *                         Must be lowercase.
 571       * @param string $position Optional. Whether to add the inline script
 572       *                         before the handle or after. Default 'after'.
 573       * @return string Inline script, which may be empty string.
 574       */
 575  	public function get_inline_script_data( $handle, $position = 'after' ) {
 576          $data = $this->get_data( $handle, $position );
 577          if ( empty( $data ) || ! is_array( $data ) ) {
 578              return '';
 579          }
 580  
 581          /*
 582           * Print sourceURL comment regardless of concatenation.
 583           *
 584           * Inline scripts prevent scripts from being concatenated, so
 585           * sourceURL comments are safe to print for inline scripts.
 586           */
 587          $data[] = sprintf(
 588              '//# sourceURL=%s',
 589              rawurlencode( "{$handle}-js-{$position}" )
 590          );
 591  
 592          return trim( implode( "\n", $data ), "\n" );
 593      }
 594  
 595      /**
 596       * Gets tags for inline scripts registered for a specific handle.
 597       *
 598       * @since 6.3.0
 599       *
 600       * @param string $handle   Name of the script to get associated inline script tag for.
 601       *                         Must be lowercase.
 602       * @param string $position Optional. Whether to get tag for inline
 603       *                         scripts in the before or after position. Default 'after'.
 604       * @return string Inline script, which may be empty string.
 605       */
 606  	public function get_inline_script_tag( $handle, $position = 'after' ) {
 607          $js = $this->get_inline_script_data( $handle, $position );
 608          if ( empty( $js ) ) {
 609              return '';
 610          }
 611  
 612          $id = "{$handle}-js-{$position}";
 613  
 614          return wp_get_inline_script_tag( $js, compact( 'id' ) );
 615      }
 616  
 617      /**
 618       * Localizes a script, only if the script has already been added.
 619       *
 620       * @since 2.1.0
 621       *
 622       * @param string               $handle      Name of the script to attach data to.
 623       * @param string               $object_name Name of the variable that will contain the data.
 624       * @param array<string, mixed> $l10n        Array of data to localize.
 625       * @return bool True on success, false on failure.
 626       */
 627  	public function localize( $handle, $object_name, $l10n ) {
 628          if ( 'jquery' === $handle ) {
 629              $handle = 'jquery-core';
 630          }
 631  
 632          if ( is_array( $l10n ) && isset( $l10n['l10n_print_after'] ) ) { // back compat, preserve the code in 'l10n_print_after' if present.
 633              $after = $l10n['l10n_print_after'];
 634              unset( $l10n['l10n_print_after'] );
 635          }
 636  
 637          if ( ! is_array( $l10n ) ) {
 638              _doing_it_wrong(
 639                  __METHOD__,
 640                  sprintf(
 641                      /* translators: 1: $l10n, 2: wp_add_inline_script() */
 642                      __( 'The %1$s parameter must be an array. To pass arbitrary data to scripts, use the %2$s function instead.' ),
 643                      '<code>$l10n</code>',
 644                      '<code>wp_add_inline_script()</code>'
 645                  ),
 646                  '5.7.0'
 647              );
 648  
 649              if ( false === $l10n ) {
 650                  // This should really not be needed, but is necessary for backward compatibility.
 651                  $l10n = array( $l10n );
 652              }
 653          }
 654  
 655          if ( is_string( $l10n ) ) {
 656              $l10n = html_entity_decode( $l10n, ENT_QUOTES, 'UTF-8' );
 657          } elseif ( is_array( $l10n ) ) {
 658              foreach ( $l10n as $key => $value ) {
 659                  if ( ! is_scalar( $value ) ) {
 660                      continue;
 661                  }
 662  
 663                  $l10n[ $key ] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8' );
 664              }
 665          }
 666  
 667          $script = "var $object_name = " . wp_json_encode( $l10n, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';';
 668  
 669          if ( ! empty( $after ) ) {
 670              $script .= "\n$after;";
 671          }
 672  
 673          $data = $this->get_data( $handle, 'data' );
 674  
 675          if ( ! empty( $data ) ) {
 676              $script = "$data\n$script";
 677          }
 678  
 679          return $this->add_data( $handle, 'data', $script );
 680      }
 681  
 682      /**
 683       * Sets handle group.
 684       *
 685       * @since 2.8.0
 686       *
 687       * @see WP_Dependencies::set_group()
 688       *
 689       * @param string    $handle    Name of the item. Should be unique.
 690       * @param bool      $recursion Internal flag that calling function was called recursively.
 691       * @param int|false $group     Optional. Group level: level (int), no groups (false).
 692       *                             Default false.
 693       * @return bool Not already in the group or a lower group.
 694       */
 695  	public function set_group( $handle, $recursion, $group = false ) {
 696          if ( isset( $this->registered[ $handle ]->args ) && 1 === $this->registered[ $handle ]->args ) {
 697              $calculated_group = 1;
 698          } else {
 699              $calculated_group = (int) $this->get_data( $handle, 'group' );
 700          }
 701  
 702          if ( false !== $group && $calculated_group > $group ) {
 703              $calculated_group = $group;
 704          }
 705  
 706          return parent::set_group( $handle, $recursion, $calculated_group );
 707      }
 708  
 709      /**
 710       * Sets a translation textdomain.
 711       *
 712       * @since 5.0.0
 713       * @since 5.1.0 The `$domain` parameter was made optional.
 714       *
 715       * @param string $handle Name of the script to register a translation domain to.
 716       * @param string $domain Optional. Text domain. Default 'default'.
 717       * @param string $path   Optional. The full file path to the directory containing translation files.
 718       * @return bool True if the text domain was registered, false if not.
 719       */
 720  	public function set_translations( $handle, $domain = 'default', $path = '' ) {
 721          if ( ! isset( $this->registered[ $handle ] ) ) {
 722              return false;
 723          }
 724  
 725          /** @var \_WP_Dependency $obj */
 726          $obj = $this->registered[ $handle ];
 727  
 728          if ( ! in_array( 'wp-i18n', $obj->deps, true ) ) {
 729              $obj->deps[] = 'wp-i18n';
 730          }
 731  
 732          return $obj->set_translations( $domain, $path );
 733      }
 734  
 735      /**
 736       * Prints translations set for a specific handle.
 737       *
 738       * @since 5.0.0
 739       *
 740       * @param string $handle  Name of the script to add the inline script to.
 741       *                        Must be lowercase.
 742       * @param bool   $display Optional. Whether to print the script
 743       *                        instead of just returning it. Default true.
 744       * @return string|false Script on success, false otherwise.
 745       */
 746  	public function print_translations( $handle, $display = true ) {
 747          if ( ! isset( $this->registered[ $handle ] ) || empty( $this->registered[ $handle ]->textdomain ) ) {
 748              return false;
 749          }
 750  
 751          $domain = $this->registered[ $handle ]->textdomain;
 752          $path   = '';
 753  
 754          if ( isset( $this->registered[ $handle ]->translations_path ) ) {
 755              $path = $this->registered[ $handle ]->translations_path;
 756          }
 757  
 758          $json_translations = load_script_textdomain( $handle, $domain, $path );
 759  
 760          if ( ! $json_translations ) {
 761              return false;
 762          }
 763  
 764          $output = <<<JS
 765  ( function( domain, translations ) {
 766      var localeData = translations.locale_data[ domain ] || translations.locale_data.messages;
 767      localeData[""].domain = domain;
 768      wp.i18n.setLocaleData( localeData, domain );
 769  } )( "{$domain}", {$json_translations} );
 770  JS;
 771  
 772          if ( $display ) {
 773              $source_url = rawurlencode( "{$handle}-js-translations" );
 774              $output    .= "\n//# sourceURL={$source_url}";
 775              wp_print_inline_script_tag( $output, array( 'id' => "{$handle}-js-translations" ) );
 776          }
 777  
 778          return $output;
 779      }
 780  
 781      /**
 782       * Determines script dependencies.
 783       *
 784       * @since 2.1.0
 785       *
 786       * @see WP_Dependencies::all_deps()
 787       *
 788       * @param string|string[] $handles   Item handle (string) or item handles (array of strings).
 789       * @param bool            $recursion Optional. Internal flag that function is calling itself.
 790       *                                   Default false.
 791       * @param int|false       $group     Optional. Group level: level (int), no groups (false).
 792       *                                   Default false.
 793       * @return bool True on success, false on failure.
 794       */
 795  	public function all_deps( $handles, $recursion = false, $group = false ) {
 796          $result = parent::all_deps( $handles, $recursion, $group );
 797          if ( ! $recursion ) {
 798              /**
 799               * Filters the list of script dependencies left to print.
 800               *
 801               * @since 2.3.0
 802               *
 803               * @param string[] $to_do An array of script dependency handles.
 804               */
 805              $this->to_do = apply_filters( 'print_scripts_array', $this->to_do );
 806          }
 807          return $result;
 808      }
 809  
 810      /**
 811       * Processes items and dependencies for the head group.
 812       *
 813       * @since 2.8.0
 814       *
 815       * @see WP_Dependencies::do_items()
 816       *
 817       * @return string[] Handles of items that have been processed.
 818       */
 819  	public function do_head_items() {
 820          $this->do_items( false, 0 );
 821          return $this->done;
 822      }
 823  
 824      /**
 825       * Processes items and dependencies for the footer group.
 826       *
 827       * @since 2.8.0
 828       *
 829       * @see WP_Dependencies::do_items()
 830       *
 831       * @return string[] Handles of items that have been processed.
 832       */
 833  	public function do_footer_items() {
 834          $this->do_items( false, 1 );
 835          return $this->done;
 836      }
 837  
 838      /**
 839       * Whether a handle's source is in a default directory.
 840       *
 841       * @since 2.8.0
 842       *
 843       * @param string $src The source of the enqueued script.
 844       * @return bool True if found, false if not.
 845       */
 846  	public function in_default_dir( $src ) {
 847          if ( ! $this->default_dirs ) {
 848              return true;
 849          }
 850  
 851          if ( str_starts_with( $src, '/' . WPINC . '/js/l10n' ) ) {
 852              return false;
 853          }
 854  
 855          return array_any( (array) $this->default_dirs, fn( $test ) => str_starts_with( $src, $test ) );
 856      }
 857  
 858      /**
 859       * This overrides the add_data method from WP_Dependencies, to support normalizing of $args.
 860       *
 861       * @since 6.3.0
 862       *
 863       * @param string $handle Name of the item. Should be unique.
 864       * @param string $key    The data key.
 865       * @param mixed  $value  The data value.
 866       * @return bool True on success, false on failure.
 867       */
 868  	public function add_data( $handle, $key, $value ) {
 869          if ( ! isset( $this->registered[ $handle ] ) ) {
 870              return false;
 871          }
 872  
 873          if ( 'conditional' === $key ) {
 874              // If a dependency is declared by a conditional script, remove it.
 875              $this->registered[ $handle ]->deps = array();
 876          }
 877  
 878          if ( 'strategy' === $key ) {
 879              if ( ! empty( $value ) && ! $this->is_delayed_strategy( $value ) ) {
 880                  _doing_it_wrong(
 881                      __METHOD__,
 882                      sprintf(
 883                          /* translators: 1: $strategy, 2: $handle */
 884                          __( 'Invalid strategy `%1$s` defined for `%2$s` during script registration.' ),
 885                          is_string( $value ) ? $value : gettype( $value ),
 886                          $handle
 887                      ),
 888                      '6.3.0'
 889                  );
 890                  return false;
 891              } elseif ( ! $this->registered[ $handle ]->src && $this->is_delayed_strategy( $value ) ) {
 892                  _doing_it_wrong(
 893                      __METHOD__,
 894                      sprintf(
 895                          /* translators: 1: $strategy, 2: $handle */
 896                          __( 'Cannot supply a strategy `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ),
 897                          is_string( $value ) ? $value : gettype( $value ),
 898                          $handle
 899                      ),
 900                      '6.3.0'
 901                  );
 902                  return false;
 903              }
 904          } elseif ( 'fetchpriority' === $key ) {
 905              if ( empty( $value ) ) {
 906                  $value = 'auto';
 907              }
 908              if ( ! $this->is_valid_fetchpriority( $value ) ) {
 909                  _doing_it_wrong(
 910                      __METHOD__,
 911                      sprintf(
 912                          /* translators: 1: $fetchpriority, 2: $handle */
 913                          __( 'Invalid fetchpriority `%1$s` defined for `%2$s` during script registration.' ),
 914                          is_string( $value ) ? $value : gettype( $value ),
 915                          $handle
 916                      ),
 917                      '6.9.0'
 918                  );
 919                  return false;
 920              } elseif ( ! $this->registered[ $handle ]->src ) {
 921                  _doing_it_wrong(
 922                      __METHOD__,
 923                      sprintf(
 924                          /* translators: 1: $fetchpriority, 2: $handle */
 925                          __( 'Cannot supply a fetchpriority `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ),
 926                          is_string( $value ) ? $value : gettype( $value ),
 927                          $handle
 928                      ),
 929                      '6.9.0'
 930                  );
 931                  return false;
 932              }
 933          } elseif ( 'module_dependencies' === $key ) {
 934              if ( ! is_array( $value ) ) {
 935                  _doing_it_wrong(
 936                      __METHOD__,
 937                      sprintf(
 938                          /* translators: 1: 'module_dependencies', 2: Script handle. */
 939                          __( 'The value for "%1$s" must be an array for the "%2$s" script.' ),
 940                          'module_dependencies',
 941                          $handle
 942                      ),
 943                      '7.0.0'
 944                  );
 945                  return false;
 946              }
 947  
 948              $sanitized_value = array();
 949              $has_invalid_ids = false;
 950              foreach ( $value as $module ) {
 951                  if (
 952                      is_string( $module ) ||
 953                      ( is_array( $module ) && isset( $module['id'] ) && is_string( $module['id'] ) )
 954                  ) {
 955                      $sanitized_value[] = $module;
 956                  } else {
 957                      $has_invalid_ids = true;
 958                  }
 959              }
 960  
 961              if ( $has_invalid_ids ) {
 962                  _doing_it_wrong(
 963                      __METHOD__,
 964                      sprintf(
 965                          /* translators: 1: Script handle, 2: 'module_dependencies' */
 966                          __( 'The script handle "%1$s" has one or more of its script module dependencies ("%2$s") which are invalid.' ),
 967                          $handle,
 968                          'module_dependencies'
 969                      ),
 970                      '7.0.0'
 971                  );
 972              }
 973  
 974              $value = $sanitized_value;
 975          }
 976          return parent::add_data( $handle, $key, $value );
 977      }
 978  
 979      /**
 980       * Gets all dependents of a script.
 981       *
 982       * This is not recursive.
 983       *
 984       * @since 6.3.0
 985       *
 986       * @param string $handle The script handle.
 987       * @return string[] Script handles.
 988       */
 989  	private function get_dependents( $handle ) {
 990          // Check if dependents map for the handle in question is present. If so, use it.
 991          if ( isset( $this->dependents_map[ $handle ] ) ) {
 992              return $this->dependents_map[ $handle ];
 993          }
 994  
 995          $dependents = array();
 996  
 997          // Iterate over all registered scripts, finding dependents of the script passed to this method.
 998          foreach ( $this->registered as $registered_handle => $args ) {
 999              if ( in_array( $handle, $args->deps, true ) ) {
1000                  $dependents[] = $registered_handle;
1001              }
1002          }
1003  
1004          // Add the handles dependents to the map to ease future lookups.
1005          $this->dependents_map[ $handle ] = $dependents;
1006  
1007          return $dependents;
1008      }
1009  
1010      /**
1011       * Checks if the strategy passed is a valid delayed (non-blocking) strategy.
1012       *
1013       * @since 6.3.0
1014       *
1015       * @param string|mixed $strategy The strategy to check.
1016       * @return bool True if $strategy is one of the delayed strategies, otherwise false.
1017       */
1018  	private function is_delayed_strategy( $strategy ): bool {
1019          return in_array(
1020              $strategy,
1021              $this->delayed_strategies,
1022              true
1023          );
1024      }
1025  
1026      /**
1027       * Checks if the provided fetchpriority is valid.
1028       *
1029       * @since 6.9.0
1030       *
1031       * @param string|mixed $priority Fetch priority.
1032       * @return bool Whether valid fetchpriority.
1033       */
1034  	private function is_valid_fetchpriority( $priority ): bool {
1035          return in_array( $priority, array( 'auto', 'low', 'high' ), true );
1036      }
1037  
1038      /**
1039       * Gets the best eligible loading strategy for a script.
1040       *
1041       * @since 6.3.0
1042       *
1043       * @param string $handle The script handle.
1044       * @return string The best eligible loading strategy.
1045       */
1046  	private function get_eligible_loading_strategy( $handle ) {
1047          $intended_strategy = (string) $this->get_data( $handle, 'strategy' );
1048  
1049          // Bail early if there is no intended strategy.
1050          if ( ! $intended_strategy ) {
1051              return '';
1052          }
1053  
1054          /*
1055           * If the intended strategy is 'defer', limit the initial list of eligible
1056           * strategies, since 'async' can fallback to 'defer', but not vice-versa.
1057           */
1058          $initial_strategy = ( 'defer' === $intended_strategy ) ? array( 'defer' ) : null;
1059  
1060          $eligible_strategies = $this->filter_eligible_strategies( $handle, $initial_strategy );
1061  
1062          // Return early once we know the eligible strategy is blocking.
1063          if ( empty( $eligible_strategies ) ) {
1064              return '';
1065          }
1066  
1067          return in_array( 'async', $eligible_strategies, true ) ? 'async' : 'defer';
1068      }
1069  
1070      /**
1071       * Filter the list of eligible loading strategies for a script.
1072       *
1073       * @since 6.3.0
1074       *
1075       * @param string                  $handle              The script handle.
1076       * @param string[]|null           $eligible_strategies Optional. The list of strategies to filter. Default null.
1077       * @param array<string, true>     $checked             Optional. An array of already checked script handles, used to avoid recursive loops.
1078       * @param array<string, string[]> $stored_results      Optional. An array of already computed eligible loading strategies by handle, used to increase performance in large dependency lists.
1079       * @return string[] A list of eligible loading strategies that could be used.
1080       */
1081  	private function filter_eligible_strategies( $handle, $eligible_strategies = null, $checked = array(), array &$stored_results = array() ) {
1082          if ( isset( $stored_results[ $handle ] ) ) {
1083              return $stored_results[ $handle ];
1084          }
1085  
1086          // If no strategies are being passed, all strategies are eligible.
1087          if ( null === $eligible_strategies ) {
1088              $eligible_strategies = $this->delayed_strategies;
1089          }
1090  
1091          // If this handle was already checked, return early.
1092          if ( isset( $checked[ $handle ] ) ) {
1093              return $eligible_strategies;
1094          }
1095  
1096          // Mark this handle as checked.
1097          $checked[ $handle ] = true;
1098  
1099          // If this handle isn't registered, don't filter anything and return.
1100          if ( ! isset( $this->registered[ $handle ] ) ) {
1101              return $eligible_strategies;
1102          }
1103  
1104          // If the handle is not enqueued, don't filter anything and return.
1105          if ( ! $this->query( $handle, 'enqueued' ) ) {
1106              return $eligible_strategies;
1107          }
1108  
1109          $is_alias          = (bool) ! $this->registered[ $handle ]->src;
1110          $intended_strategy = $this->get_data( $handle, 'strategy' );
1111  
1112          // For non-alias handles, an empty intended strategy filters all strategies.
1113          if ( ! $is_alias && empty( $intended_strategy ) ) {
1114              return array();
1115          }
1116  
1117          // Handles with inline scripts attached in the 'after' position cannot be delayed.
1118          if ( $this->has_inline_script( $handle, 'after' ) ) {
1119              return array();
1120          }
1121  
1122          // If the intended strategy is 'defer', filter out 'async'.
1123          if ( 'defer' === $intended_strategy ) {
1124              $eligible_strategies = array( 'defer' );
1125          }
1126  
1127          $dependents = $this->get_dependents( $handle );
1128  
1129          // Recursively filter eligible strategies for dependents.
1130          foreach ( $dependents as $dependent ) {
1131              // Bail early once we know the eligible strategy is blocking.
1132              if ( empty( $eligible_strategies ) ) {
1133                  return array();
1134              }
1135  
1136              $eligible_strategies = $this->filter_eligible_strategies( $dependent, $eligible_strategies, $checked, $stored_results );
1137          }
1138          $stored_results[ $handle ] = $eligible_strategies;
1139          return $eligible_strategies;
1140      }
1141  
1142      /**
1143       * Gets the highest fetch priority for a given script and all of its dependent scripts.
1144       *
1145       * @since 6.9.0
1146       * @see self::filter_eligible_strategies()
1147       * @see WP_Script_Modules::get_highest_fetchpriority()
1148       *
1149       * @param string                $handle         Script module ID.
1150       * @param array<string, true>   $checked        Optional. An array of already checked script handles, used to avoid recursive loops.
1151       * @param array<string, string> $stored_results Optional. An array of already computed max priority by handle, used to increase performance in large dependency lists.
1152       * @return string|null Highest fetch priority for the script and its dependents.
1153       */
1154  	private function get_highest_fetchpriority_with_dependents( string $handle, array $checked = array(), array &$stored_results = array() ): ?string {
1155          if ( isset( $stored_results[ $handle ] ) ) {
1156              return $stored_results[ $handle ];
1157          }
1158  
1159          // If there is a recursive dependency, return early.
1160          if ( isset( $checked[ $handle ] ) ) {
1161              return null;
1162          }
1163  
1164          // Mark this handle as checked to guard against infinite recursion.
1165          $checked[ $handle ] = true;
1166  
1167          // Abort if the script is not enqueued or a dependency of an enqueued script.
1168          if ( ! $this->query( $handle, 'enqueued' ) ) {
1169              return null;
1170          }
1171  
1172          $fetchpriority = $this->get_data( $handle, 'fetchpriority' );
1173          if ( ! $this->is_valid_fetchpriority( $fetchpriority ) ) {
1174              $fetchpriority = 'auto';
1175          }
1176  
1177          static $priorities   = array(
1178              'low',
1179              'auto',
1180              'high',
1181          );
1182          $high_priority_index = count( $priorities ) - 1;
1183  
1184          $highest_priority_index = (int) array_search( $fetchpriority, $priorities, true );
1185          if ( $highest_priority_index !== $high_priority_index ) {
1186              foreach ( $this->get_dependents( $handle ) as $dependent_handle ) {
1187                  $dependent_priority = $this->get_highest_fetchpriority_with_dependents( $dependent_handle, $checked, $stored_results );
1188                  if ( is_string( $dependent_priority ) ) {
1189                      $highest_priority_index = max(
1190                          $highest_priority_index,
1191                          (int) array_search( $dependent_priority, $priorities, true )
1192                      );
1193                      if ( $highest_priority_index === $high_priority_index ) {
1194                          break;
1195                      }
1196                  }
1197              }
1198          }
1199          $stored_results[ $handle ] = $priorities[ $highest_priority_index ];
1200          return $priorities[ $highest_priority_index ];
1201      }
1202  
1203      /**
1204       * Gets data for inline scripts registered for a specific handle.
1205       *
1206       * @since 6.3.0
1207       *
1208       * @param string $handle   Name of the script to get data for. Must be lowercase.
1209       * @param string $position The position of the inline script.
1210       * @return bool Whether the handle has an inline script (either before or after).
1211       */
1212  	private function has_inline_script( $handle, $position = null ) {
1213          if ( $position && in_array( $position, array( 'before', 'after' ), true ) ) {
1214              return (bool) $this->get_data( $handle, $position );
1215          }
1216  
1217          return (bool) ( $this->get_data( $handle, 'before' ) || $this->get_data( $handle, 'after' ) );
1218      }
1219  
1220      /**
1221       * Resets class properties.
1222       *
1223       * @since 2.8.0
1224       */
1225  	public function reset() {
1226          $this->do_concat      = false;
1227          $this->print_code     = '';
1228          $this->concat         = '';
1229          $this->concat_version = '';
1230          $this->print_html     = '';
1231          $this->ext_version    = '';
1232          $this->ext_handles    = '';
1233      }
1234  
1235      /**
1236       * Gets a script-specific dependency warning message.
1237       *
1238       * @since 6.9.1
1239       *
1240       * @param string   $handle                     Script handle with missing dependencies.
1241       * @param string[] $missing_dependency_handles Missing dependency handles.
1242       * @return string Formatted, localized warning message.
1243       */
1244  	protected function get_dependency_warning_message( $handle, $missing_dependency_handles ) {
1245          return sprintf(
1246              /* translators: 1: Script handle, 2: List of missing dependency handles. */
1247              __( 'The script with the handle "%1$s" was enqueued with dependencies that are not registered: %2$s.' ),
1248              $handle,
1249              implode( wp_get_list_item_separator(), $missing_dependency_handles )
1250          );
1251      }
1252  }


Generated : Thu Sep 3 08:20:25 2026 Cross-referenced by PHPXref