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


Generated : Tue May 5 08:20:14 2026 Cross-referenced by PHPXref