[ 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   = $this->get_data( $handle, $position );
 531          $script   = false === $script ? array() : (array) $script;
 532          $script[] = $data;
 533  
 534          return $this->add_data( $handle, $position, $script );
 535      }
 536  
 537      /**
 538       * Prints inline scripts registered for a specific handle.
 539       *
 540       * @since 4.5.0
 541       * @deprecated 6.3.0 Use methods get_inline_script_tag() or get_inline_script_data() instead.
 542       *
 543       * @param string $handle   Name of the script to print inline scripts for.
 544       *                         Must be lowercase.
 545       * @param string $position Optional. Whether to add the inline script
 546       *                         before the handle or after. Default 'after'.
 547       * @param bool   $display  Optional. Whether to print the script tag
 548       *                         instead of just returning the script data. Default true.
 549       * @return string|false Script data on success, false otherwise.
 550       */
 551  	public function print_inline_script( $handle, $position = 'after', $display = true ) {
 552          _deprecated_function( __METHOD__, '6.3.0', 'WP_Scripts::get_inline_script_data() or WP_Scripts::get_inline_script_tag()' );
 553  
 554          $output = $this->get_inline_script_data( $handle, $position );
 555          if ( empty( $output ) ) {
 556              return false;
 557          }
 558  
 559          if ( $display ) {
 560              echo $this->get_inline_script_tag( $handle, $position );
 561          }
 562          return $output;
 563      }
 564  
 565      /**
 566       * Gets data for inline scripts registered for a specific handle.
 567       *
 568       * @since 6.3.0
 569       *
 570       * @param string $handle   Name of the script to get data for.
 571       *                         Must be lowercase.
 572       * @param string $position Optional. Whether to add the inline script
 573       *                         before the handle or after. Default 'after'.
 574       * @return string Inline script, which may be empty string.
 575       */
 576  	public function get_inline_script_data( $handle, $position = 'after' ) {
 577          $data = $this->get_data( $handle, $position );
 578          if ( empty( $data ) || ! is_array( $data ) ) {
 579              return '';
 580          }
 581  
 582          /*
 583           * Print sourceURL comment regardless of concatenation.
 584           *
 585           * Inline scripts prevent scripts from being concatenated, so
 586           * sourceURL comments are safe to print for inline scripts.
 587           */
 588          $data[] = sprintf(
 589              '//# sourceURL=%s',
 590              rawurlencode( "{$handle}-js-{$position}" )
 591          );
 592  
 593          return trim( implode( "\n", $data ), "\n" );
 594      }
 595  
 596      /**
 597       * Gets tags for inline scripts registered for a specific handle.
 598       *
 599       * @since 6.3.0
 600       *
 601       * @param string $handle   Name of the script to get associated inline script tag for.
 602       *                         Must be lowercase.
 603       * @param string $position Optional. Whether to get tag for inline
 604       *                         scripts in the before or after position. Default 'after'.
 605       * @return string Inline script, which may be empty string.
 606       */
 607  	public function get_inline_script_tag( $handle, $position = 'after' ) {
 608          $js = $this->get_inline_script_data( $handle, $position );
 609          if ( empty( $js ) ) {
 610              return '';
 611          }
 612  
 613          $id = "{$handle}-js-{$position}";
 614  
 615          return wp_get_inline_script_tag( $js, compact( 'id' ) );
 616      }
 617  
 618      /**
 619       * Localizes a script, only if the script has already been added.
 620       *
 621       * @since 2.1.0
 622       *
 623       * @param string               $handle      Name of the script to attach data to.
 624       * @param string               $object_name Name of the variable that will contain the data.
 625       * @param array<string, mixed> $l10n        Array of data to localize.
 626       * @return bool True on success, false on failure.
 627       */
 628  	public function localize( $handle, $object_name, $l10n ) {
 629          if ( 'jquery' === $handle ) {
 630              $handle = 'jquery-core';
 631          }
 632  
 633          if ( is_array( $l10n ) && isset( $l10n['l10n_print_after'] ) ) { // back compat, preserve the code in 'l10n_print_after' if present.
 634              $after = $l10n['l10n_print_after'];
 635              unset( $l10n['l10n_print_after'] );
 636          }
 637  
 638          if ( ! is_array( $l10n ) ) {
 639              _doing_it_wrong(
 640                  __METHOD__,
 641                  sprintf(
 642                      /* translators: 1: $l10n, 2: wp_add_inline_script() */
 643                      __( 'The %1$s parameter must be an array. To pass arbitrary data to scripts, use the %2$s function instead.' ),
 644                      '<code>$l10n</code>',
 645                      '<code>wp_add_inline_script()</code>'
 646                  ),
 647                  '5.7.0'
 648              );
 649  
 650              if ( false === $l10n ) {
 651                  // This should really not be needed, but is necessary for backward compatibility.
 652                  $l10n = array( $l10n );
 653              }
 654          }
 655  
 656          if ( is_string( $l10n ) ) {
 657              $l10n = html_entity_decode( $l10n, ENT_QUOTES, 'UTF-8' );
 658          } elseif ( is_array( $l10n ) ) {
 659              foreach ( $l10n as $key => $value ) {
 660                  if ( ! is_scalar( $value ) ) {
 661                      continue;
 662                  }
 663  
 664                  $l10n[ $key ] = html_entity_decode( (string) $value, ENT_QUOTES, 'UTF-8' );
 665              }
 666          }
 667  
 668          $script = "var $object_name = " . wp_json_encode( $l10n, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ';';
 669  
 670          if ( ! empty( $after ) ) {
 671              $script .= "\n$after;";
 672          }
 673  
 674          $data = $this->get_data( $handle, 'data' );
 675  
 676          if ( ! empty( $data ) ) {
 677              $script = "$data\n$script";
 678          }
 679  
 680          return $this->add_data( $handle, 'data', $script );
 681      }
 682  
 683      /**
 684       * Sets handle group.
 685       *
 686       * @since 2.8.0
 687       *
 688       * @see WP_Dependencies::set_group()
 689       *
 690       * @param string    $handle    Name of the item. Should be unique.
 691       * @param bool      $recursion Internal flag that calling function was called recursively.
 692       * @param int|false $group     Optional. Group level: level (int), no groups (false).
 693       *                             Default false.
 694       * @return bool Not already in the group or a lower group.
 695       */
 696  	public function set_group( $handle, $recursion, $group = false ) {
 697          if ( isset( $this->registered[ $handle ]->args ) && 1 === $this->registered[ $handle ]->args ) {
 698              $calculated_group = 1;
 699          } else {
 700              $calculated_group = (int) $this->get_data( $handle, 'group' );
 701          }
 702  
 703          if ( false !== $group && $calculated_group > $group ) {
 704              $calculated_group = $group;
 705          }
 706  
 707          return parent::set_group( $handle, $recursion, $calculated_group );
 708      }
 709  
 710      /**
 711       * Sets a translation textdomain.
 712       *
 713       * @since 5.0.0
 714       * @since 5.1.0 The `$domain` parameter was made optional.
 715       *
 716       * @param string $handle Name of the script to register a translation domain to.
 717       * @param string $domain Optional. Text domain. Default 'default'.
 718       * @param string $path   Optional. The full file path to the directory containing translation files.
 719       * @return bool True if the text domain was registered, false if not.
 720       */
 721  	public function set_translations( $handle, $domain = 'default', $path = '' ) {
 722          if ( ! isset( $this->registered[ $handle ] ) ) {
 723              return false;
 724          }
 725  
 726          /** @var \_WP_Dependency $obj */
 727          $obj = $this->registered[ $handle ];
 728  
 729          if ( ! in_array( 'wp-i18n', $obj->deps, true ) ) {
 730              $obj->deps[] = 'wp-i18n';
 731          }
 732  
 733          return $obj->set_translations( $domain, $path );
 734      }
 735  
 736      /**
 737       * Prints translations set for a specific handle.
 738       *
 739       * @since 5.0.0
 740       *
 741       * @param string $handle  Name of the script to add the inline script to.
 742       *                        Must be lowercase.
 743       * @param bool   $display Optional. Whether to print the script
 744       *                        instead of just returning it. Default true.
 745       * @return string|false Script on success, false otherwise.
 746       */
 747  	public function print_translations( $handle, $display = true ) {
 748          if ( ! isset( $this->registered[ $handle ] ) || empty( $this->registered[ $handle ]->textdomain ) ) {
 749              return false;
 750          }
 751  
 752          $domain = $this->registered[ $handle ]->textdomain;
 753          $path   = '';
 754  
 755          if ( isset( $this->registered[ $handle ]->translations_path ) ) {
 756              $path = $this->registered[ $handle ]->translations_path;
 757          }
 758  
 759          $json_translations = load_script_textdomain( $handle, $domain, $path );
 760  
 761          if ( ! $json_translations ) {
 762              return false;
 763          }
 764  
 765          $output = <<<JS
 766  ( function( domain, translations ) {
 767      var localeData = translations.locale_data[ domain ] || translations.locale_data.messages;
 768      localeData[""].domain = domain;
 769      wp.i18n.setLocaleData( localeData, domain );
 770  } )( "{$domain}", {$json_translations} );
 771  JS;
 772  
 773          if ( $display ) {
 774              $source_url = rawurlencode( "{$handle}-js-translations" );
 775              $output    .= "\n//# sourceURL={$source_url}";
 776              wp_print_inline_script_tag( $output, array( 'id' => "{$handle}-js-translations" ) );
 777          }
 778  
 779          return $output;
 780      }
 781  
 782      /**
 783       * Determines script dependencies.
 784       *
 785       * @since 2.1.0
 786       *
 787       * @see WP_Dependencies::all_deps()
 788       *
 789       * @param string|string[] $handles   Item handle (string) or item handles (array of strings).
 790       * @param bool            $recursion Optional. Internal flag that function is calling itself.
 791       *                                   Default false.
 792       * @param int|false       $group     Optional. Group level: level (int), no groups (false).
 793       *                                   Default false.
 794       * @return bool True on success, false on failure.
 795       */
 796  	public function all_deps( $handles, $recursion = false, $group = false ) {
 797          $result = parent::all_deps( $handles, $recursion, $group );
 798          if ( ! $recursion ) {
 799              /**
 800               * Filters the list of script dependencies left to print.
 801               *
 802               * @since 2.3.0
 803               *
 804               * @param string[] $to_do An array of script dependency handles.
 805               */
 806              $this->to_do = apply_filters( 'print_scripts_array', $this->to_do );
 807          }
 808          return $result;
 809      }
 810  
 811      /**
 812       * Processes items and dependencies for the head group.
 813       *
 814       * @since 2.8.0
 815       *
 816       * @see WP_Dependencies::do_items()
 817       *
 818       * @return string[] Handles of items that have been processed.
 819       */
 820  	public function do_head_items() {
 821          $this->do_items( false, 0 );
 822          return $this->done;
 823      }
 824  
 825      /**
 826       * Processes items and dependencies for the footer group.
 827       *
 828       * @since 2.8.0
 829       *
 830       * @see WP_Dependencies::do_items()
 831       *
 832       * @return string[] Handles of items that have been processed.
 833       */
 834  	public function do_footer_items() {
 835          $this->do_items( false, 1 );
 836          return $this->done;
 837      }
 838  
 839      /**
 840       * Whether a handle's source is in a default directory.
 841       *
 842       * @since 2.8.0
 843       *
 844       * @param string $src The source of the enqueued script.
 845       * @return bool True if found, false if not.
 846       */
 847  	public function in_default_dir( $src ) {
 848          if ( ! $this->default_dirs ) {
 849              return true;
 850          }
 851  
 852          if ( str_starts_with( $src, '/' . WPINC . '/js/l10n' ) ) {
 853              return false;
 854          }
 855  
 856          return array_any( (array) $this->default_dirs, fn( $test ) => str_starts_with( $src, $test ) );
 857      }
 858  
 859      /**
 860       * This overrides the add_data method from WP_Dependencies, to support normalizing of $args.
 861       *
 862       * @since 6.3.0
 863       *
 864       * @param string $handle Name of the item. Should be unique.
 865       * @param string $key    The data key.
 866       * @param mixed  $value  The data value.
 867       * @return bool True on success, false on failure.
 868       */
 869  	public function add_data( $handle, $key, $value ) {
 870          if ( ! isset( $this->registered[ $handle ] ) ) {
 871              return false;
 872          }
 873  
 874          if ( 'conditional' === $key ) {
 875              // If a dependency is declared by a conditional script, remove it.
 876              $this->registered[ $handle ]->deps = array();
 877          }
 878  
 879          if ( 'strategy' === $key ) {
 880              if ( ! empty( $value ) && ! $this->is_delayed_strategy( $value ) ) {
 881                  _doing_it_wrong(
 882                      __METHOD__,
 883                      sprintf(
 884                          /* translators: 1: $strategy, 2: $handle */
 885                          __( 'Invalid strategy `%1$s` defined for `%2$s` during script registration.' ),
 886                          is_string( $value ) ? $value : gettype( $value ),
 887                          $handle
 888                      ),
 889                      '6.3.0'
 890                  );
 891                  return false;
 892              } elseif ( ! $this->registered[ $handle ]->src && $this->is_delayed_strategy( $value ) ) {
 893                  _doing_it_wrong(
 894                      __METHOD__,
 895                      sprintf(
 896                          /* translators: 1: $strategy, 2: $handle */
 897                          __( 'Cannot supply a strategy `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ),
 898                          is_string( $value ) ? $value : gettype( $value ),
 899                          $handle
 900                      ),
 901                      '6.3.0'
 902                  );
 903                  return false;
 904              }
 905          } elseif ( 'fetchpriority' === $key ) {
 906              if ( empty( $value ) ) {
 907                  $value = 'auto';
 908              }
 909              if ( ! $this->is_valid_fetchpriority( $value ) ) {
 910                  _doing_it_wrong(
 911                      __METHOD__,
 912                      sprintf(
 913                          /* translators: 1: $fetchpriority, 2: $handle */
 914                          __( 'Invalid fetchpriority `%1$s` defined for `%2$s` during script registration.' ),
 915                          is_string( $value ) ? $value : gettype( $value ),
 916                          $handle
 917                      ),
 918                      '6.9.0'
 919                  );
 920                  return false;
 921              } elseif ( ! $this->registered[ $handle ]->src ) {
 922                  _doing_it_wrong(
 923                      __METHOD__,
 924                      sprintf(
 925                          /* translators: 1: $fetchpriority, 2: $handle */
 926                          __( 'Cannot supply a fetchpriority `%1$s` for script `%2$s` because it is an alias (it lacks a `src` value).' ),
 927                          is_string( $value ) ? $value : gettype( $value ),
 928                          $handle
 929                      ),
 930                      '6.9.0'
 931                  );
 932                  return false;
 933              }
 934          } elseif ( 'module_dependencies' === $key ) {
 935              if ( ! is_array( $value ) ) {
 936                  _doing_it_wrong(
 937                      __METHOD__,
 938                      sprintf(
 939                          /* translators: 1: 'module_dependencies', 2: Script handle. */
 940                          __( 'The value for "%1$s" must be an array for the "%2$s" script.' ),
 941                          'module_dependencies',
 942                          $handle
 943                      ),
 944                      '7.0.0'
 945                  );
 946                  return false;
 947              }
 948  
 949              $sanitized_value = array();
 950              $has_invalid_ids = false;
 951              foreach ( $value as $module ) {
 952                  if (
 953                      is_string( $module ) ||
 954                      ( is_array( $module ) && isset( $module['id'] ) && is_string( $module['id'] ) )
 955                  ) {
 956                      $sanitized_value[] = $module;
 957                  } else {
 958                      $has_invalid_ids = true;
 959                  }
 960              }
 961  
 962              if ( $has_invalid_ids ) {
 963                  _doing_it_wrong(
 964                      __METHOD__,
 965                      sprintf(
 966                          /* translators: 1: Script handle, 2: 'module_dependencies' */
 967                          __( 'The script handle "%1$s" has one or more of its script module dependencies ("%2$s") which are invalid.' ),
 968                          $handle,
 969                          'module_dependencies'
 970                      ),
 971                      '7.0.0'
 972                  );
 973              }
 974  
 975              $value = $sanitized_value;
 976          }
 977          return parent::add_data( $handle, $key, $value );
 978      }
 979  
 980      /**
 981       * Gets all dependents of a script.
 982       *
 983       * This is not recursive.
 984       *
 985       * @since 6.3.0
 986       *
 987       * @param string $handle The script handle.
 988       * @return string[] Script handles.
 989       */
 990  	private function get_dependents( $handle ) {
 991          // Check if dependents map for the handle in question is present. If so, use it.
 992          if ( isset( $this->dependents_map[ $handle ] ) ) {
 993              return $this->dependents_map[ $handle ];
 994          }
 995  
 996          $dependents = array();
 997  
 998          // Iterate over all registered scripts, finding dependents of the script passed to this method.
 999          foreach ( $this->registered as $registered_handle => $args ) {
1000              if ( in_array( $handle, $args->deps, true ) ) {
1001                  $dependents[] = $registered_handle;
1002              }
1003          }
1004  
1005          // Add the handles dependents to the map to ease future lookups.
1006          $this->dependents_map[ $handle ] = $dependents;
1007  
1008          return $dependents;
1009      }
1010  
1011      /**
1012       * Checks if the strategy passed is a valid delayed (non-blocking) strategy.
1013       *
1014       * @since 6.3.0
1015       *
1016       * @param string|mixed $strategy The strategy to check.
1017       * @return bool True if $strategy is one of the delayed strategies, otherwise false.
1018       */
1019  	private function is_delayed_strategy( $strategy ): bool {
1020          return in_array(
1021              $strategy,
1022              $this->delayed_strategies,
1023              true
1024          );
1025      }
1026  
1027      /**
1028       * Checks if the provided fetchpriority is valid.
1029       *
1030       * @since 6.9.0
1031       *
1032       * @param string|mixed $priority Fetch priority.
1033       * @return bool Whether valid fetchpriority.
1034       */
1035  	private function is_valid_fetchpriority( $priority ): bool {
1036          return in_array( $priority, array( 'auto', 'low', 'high' ), true );
1037      }
1038  
1039      /**
1040       * Gets the best eligible loading strategy for a script.
1041       *
1042       * @since 6.3.0
1043       *
1044       * @param string $handle The script handle.
1045       * @return string The best eligible loading strategy.
1046       */
1047  	private function get_eligible_loading_strategy( $handle ) {
1048          $intended_strategy = (string) $this->get_data( $handle, 'strategy' );
1049  
1050          // Bail early if there is no intended strategy.
1051          if ( ! $intended_strategy ) {
1052              return '';
1053          }
1054  
1055          /*
1056           * If the intended strategy is 'defer', limit the initial list of eligible
1057           * strategies, since 'async' can fallback to 'defer', but not vice-versa.
1058           */
1059          $initial_strategy = ( 'defer' === $intended_strategy ) ? array( 'defer' ) : null;
1060  
1061          $eligible_strategies = $this->filter_eligible_strategies( $handle, $initial_strategy );
1062  
1063          // Return early once we know the eligible strategy is blocking.
1064          if ( empty( $eligible_strategies ) ) {
1065              return '';
1066          }
1067  
1068          return in_array( 'async', $eligible_strategies, true ) ? 'async' : 'defer';
1069      }
1070  
1071      /**
1072       * Filter the list of eligible loading strategies for a script.
1073       *
1074       * @since 6.3.0
1075       *
1076       * @param string                  $handle              The script handle.
1077       * @param string[]|null           $eligible_strategies Optional. The list of strategies to filter. Default null.
1078       * @param array<string, true>     $checked             Optional. An array of already checked script handles, used to avoid recursive loops.
1079       * @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.
1080       * @return string[] A list of eligible loading strategies that could be used.
1081       */
1082  	private function filter_eligible_strategies( $handle, $eligible_strategies = null, $checked = array(), array &$stored_results = array() ) {
1083          if ( isset( $stored_results[ $handle ] ) ) {
1084              return $stored_results[ $handle ];
1085          }
1086  
1087          // If no strategies are being passed, all strategies are eligible.
1088          if ( null === $eligible_strategies ) {
1089              $eligible_strategies = $this->delayed_strategies;
1090          }
1091  
1092          // If this handle was already checked, return early.
1093          if ( isset( $checked[ $handle ] ) ) {
1094              return $eligible_strategies;
1095          }
1096  
1097          // Mark this handle as checked.
1098          $checked[ $handle ] = true;
1099  
1100          // If this handle isn't registered, don't filter anything and return.
1101          if ( ! isset( $this->registered[ $handle ] ) ) {
1102              return $eligible_strategies;
1103          }
1104  
1105          // If the handle is not enqueued, don't filter anything and return.
1106          if ( ! $this->query( $handle, 'enqueued' ) ) {
1107              return $eligible_strategies;
1108          }
1109  
1110          $is_alias          = (bool) ! $this->registered[ $handle ]->src;
1111          $intended_strategy = $this->get_data( $handle, 'strategy' );
1112  
1113          // For non-alias handles, an empty intended strategy filters all strategies.
1114          if ( ! $is_alias && empty( $intended_strategy ) ) {
1115              return array();
1116          }
1117  
1118          // Handles with inline scripts attached in the 'after' position cannot be delayed.
1119          if ( $this->has_inline_script( $handle, 'after' ) ) {
1120              return array();
1121          }
1122  
1123          // If the intended strategy is 'defer', filter out 'async'.
1124          if ( 'defer' === $intended_strategy ) {
1125              $eligible_strategies = array( 'defer' );
1126          }
1127  
1128          $dependents = $this->get_dependents( $handle );
1129  
1130          // Recursively filter eligible strategies for dependents.
1131          foreach ( $dependents as $dependent ) {
1132              // Bail early once we know the eligible strategy is blocking.
1133              if ( empty( $eligible_strategies ) ) {
1134                  return array();
1135              }
1136  
1137              $eligible_strategies = $this->filter_eligible_strategies( $dependent, $eligible_strategies, $checked, $stored_results );
1138          }
1139          $stored_results[ $handle ] = $eligible_strategies;
1140          return $eligible_strategies;
1141      }
1142  
1143      /**
1144       * Gets the highest fetch priority for a given script and all of its dependent scripts.
1145       *
1146       * @since 6.9.0
1147       * @see self::filter_eligible_strategies()
1148       * @see WP_Script_Modules::get_highest_fetchpriority()
1149       *
1150       * @param string                $handle         Script module ID.
1151       * @param array<string, true>   $checked        Optional. An array of already checked script handles, used to avoid recursive loops.
1152       * @param array<string, string> $stored_results Optional. An array of already computed max priority by handle, used to increase performance in large dependency lists.
1153       * @return string|null Highest fetch priority for the script and its dependents.
1154       */
1155  	private function get_highest_fetchpriority_with_dependents( string $handle, array $checked = array(), array &$stored_results = array() ): ?string {
1156          if ( isset( $stored_results[ $handle ] ) ) {
1157              return $stored_results[ $handle ];
1158          }
1159  
1160          // If there is a recursive dependency, return early.
1161          if ( isset( $checked[ $handle ] ) ) {
1162              return null;
1163          }
1164  
1165          // Mark this handle as checked to guard against infinite recursion.
1166          $checked[ $handle ] = true;
1167  
1168          // Abort if the script is not enqueued or a dependency of an enqueued script.
1169          if ( ! $this->query( $handle, 'enqueued' ) ) {
1170              return null;
1171          }
1172  
1173          $fetchpriority = $this->get_data( $handle, 'fetchpriority' );
1174          if ( ! $this->is_valid_fetchpriority( $fetchpriority ) ) {
1175              $fetchpriority = 'auto';
1176          }
1177  
1178          static $priorities   = array(
1179              'low',
1180              'auto',
1181              'high',
1182          );
1183          $high_priority_index = count( $priorities ) - 1;
1184  
1185          $highest_priority_index = (int) array_search( $fetchpriority, $priorities, true );
1186          if ( $highest_priority_index !== $high_priority_index ) {
1187              foreach ( $this->get_dependents( $handle ) as $dependent_handle ) {
1188                  $dependent_priority = $this->get_highest_fetchpriority_with_dependents( $dependent_handle, $checked, $stored_results );
1189                  if ( is_string( $dependent_priority ) ) {
1190                      $highest_priority_index = max(
1191                          $highest_priority_index,
1192                          (int) array_search( $dependent_priority, $priorities, true )
1193                      );
1194                      if ( $highest_priority_index === $high_priority_index ) {
1195                          break;
1196                      }
1197                  }
1198              }
1199          }
1200          $stored_results[ $handle ] = $priorities[ $highest_priority_index ];
1201          return $priorities[ $highest_priority_index ];
1202      }
1203  
1204      /**
1205       * Gets data for inline scripts registered for a specific handle.
1206       *
1207       * @since 6.3.0
1208       *
1209       * @param string $handle   Name of the script to get data for. Must be lowercase.
1210       * @param string $position The position of the inline script.
1211       * @return bool Whether the handle has an inline script (either before or after).
1212       */
1213  	private function has_inline_script( $handle, $position = null ) {
1214          if ( $position && in_array( $position, array( 'before', 'after' ), true ) ) {
1215              return (bool) $this->get_data( $handle, $position );
1216          }
1217  
1218          return (bool) ( $this->get_data( $handle, 'before' ) || $this->get_data( $handle, 'after' ) );
1219      }
1220  
1221      /**
1222       * Resets class properties.
1223       *
1224       * @since 2.8.0
1225       */
1226  	public function reset() {
1227          $this->do_concat      = false;
1228          $this->print_code     = '';
1229          $this->concat         = '';
1230          $this->concat_version = '';
1231          $this->print_html     = '';
1232          $this->ext_version    = '';
1233          $this->ext_handles    = '';
1234      }
1235  
1236      /**
1237       * Gets a script-specific dependency warning message.
1238       *
1239       * @since 6.9.1
1240       *
1241       * @param string   $handle                     Script handle with missing dependencies.
1242       * @param string[] $missing_dependency_handles Missing dependency handles.
1243       * @return string Formatted, localized warning message.
1244       */
1245  	protected function get_dependency_warning_message( $handle, $missing_dependency_handles ) {
1246          return sprintf(
1247              /* translators: 1: Script handle, 2: List of missing dependency handles. */
1248              __( 'The script with the handle "%1$s" was enqueued with dependencies that are not registered: %2$s.' ),
1249              $handle,
1250              implode( wp_get_list_item_separator(), $missing_dependency_handles )
1251          );
1252      }
1253  }


Generated : Thu Sep 24 08:20:34 2026 Cross-referenced by PHPXref